Skip to main content

Creating custom rules

Level Intermediate
Reading time ⏱ 12 min
words 1220
Topics custom-ruleswizard

Specification: Custom Rules — Creating user-defined rules

Purpose

Enable customers to create, test, and deploy their own detection rules without any knowledge of the internal format, regex syntax, or analysis engine architecture.


1. Overview

Three complementary tools

Tool Command Role
Wizard --create-rule Guided step-by-step creation, no technical knowledge required
Quick test --test-rule <id> Instant feedback on a single rule, without running a full audit
Examples --init-rule --examples Learning by example (commented .sca files)

Existing tools (already implemented)

Tool Command Role
Skeleton --init-rule <id> Generates an empty pre-filled .sca file
Validation --rules-validate Validates all custom rules (syntax, regex, fields)
Listing --rules-list Displays every loaded rule (builtin + custom)

2. Interactive wizard (--create-rule)

2.1 User workflow

$ ./staticcodeaudit /my/project --create-rule

  StaticCodeAudit — Rule creation

? Rule name (snake_case): detect_console_error

? Target language:
  > python
    javascript
    java
    csharp
    php
    html
    yaml

? What to look for in the code?
  Enter the text or pattern to detect.
  Examples: "TODO", "console.error", "SELECT.*FROM.*WHERE"
  > console.error(

? Search scope:
  > Each line independently (line)
    4-line window (context-4)
    6-line window (context-6)

? Severity:
  > LOW — Best practice, suggested improvement
    MEDIUM — Issue to fix within a reasonable timeframe
    HIGH — Critical issue, must be fixed immediately

? Must the file contain a specific pattern?
  (Prerequisite condition — leave empty to skip)
  > 

? Must the file NOT contain?
  (Exclusion — leave empty to skip)
  > 

? Short description (English): Residual console.error call
? Short description (French): Appel console.error residuel
? Risk (English): Error logging in production exposes internal state.
? Solution (English): Remove console.error or use a proper logging framework.

  Rule preview:

  rule detect_console_error
    language  javascript
    severity  LOW

    match
      pattern  console\.error\(
      scope    line
    end

    message
      en "Residual console.error call"
      fr "Appel console.error residuel"
    end
  end

? Create this rule? (Y/n): Y

  Rule created: audit-rules/detect_console_error.sca
  Validated: 1 pattern, scope line, LOW

  Next steps:
  1. Test it: ./staticcodeaudit /my/project --test-rule detect_console_error
  2. Run a full audit to include this rule in the report

2.2 Wizard behavior

Automatic regex escaping

The customer types raw text. The wizard automatically escapes regex special characters:

Customer input Generated regex Explanation
console.error( console\.error\( Dot and parenthesis escaped
System.out.println System\.out\.println Dots escaped
SELECT.*FROM SELECT.*FROM .* recognized as intentional
\bfoo\b \bfoo\b Regex construct preserved

Escaping rules:

  • Characters (, ), [, ], {, }, ., +, ?, ^, $, |, \ are escaped automatically
  • EXCEPT when the customer uses explicit regex constructs: .*, .+, \s, \w, \d, \b, [...], (a|b)
  • The wizard detects these constructs and preserves them
  • When in doubt, the wizard asks: "This looks like a regex. Use it as-is? (Y/n)"

Real-time validation

Each answer is validated immediately:

  • Name: [a-z][a-z0-9_]* — otherwise an error message appears and the prompt is repeated
  • Pattern: compiled as regex — on error, the error message is shown and the prompt is repeated
  • Severity: choice among the 3 values — no free-form input

Advanced mode (optional)

If the customer types --create-rule --advanced, additional questions appear:

  • Confidence (0-100, default 80)
  • Pattern-not (exclusion pattern — "Do NOT detect if this line contains...")
  • Codebase scope (for requires — check across all files, not file by file)
  • CWE/OWASP metadata (compliance)

2.3 Error handling

Situation Behavior
Name already in use "This name already exists. Choose a different name."
Invalid regex "Invalid pattern: [error]. Fix it or type plain text."
Ctrl+C "Cancelled. No file was created."
Missing audit-rules/ directory Created automatically

3. Quick test (--test-rule <id>)

3.1 User workflow

$ ./staticcodeaudit /my/project --test-rule detect_console_error

  Testing rule 'detect_console_error' against 42 files...

  src/components/App.jsx:18      — console.error("Failed to load", err);
  src/utils/api.js:67            — console.error(response.statusText);
  src/utils/api.js:102           — console.error("Network error:", e);

  3 findings detected in 2 files (0.2s)

3.2 Behavior

  • Loads ONLY the requested rule (no builtin rules, no other custom rules)
  • Scans files for the matching language under paths.include
  • Displays each finding: file, line, code
  • No HTML report, no baseline, no JSON export
  • Times the execution
  • Exit code: 0 if at least 1 finding, 1 if 0 findings

3.3 Options

Option Behavior
--test-rule <id> Tests the rule located in audit-rules/
--test-rule <id> --file src/app.py Tests against a single file
--test-rule <id> --verbose Also displays scanned files with no match

3.4 Error cases

Situation Behavior
Rule not found "Rule 'xxx' not found in audit-rules/. Available rules: ..."
Invalid rule Displays validation errors (same as --rules-validate)
0 files for the language "No .py file found in the configured paths."

4. Commented examples (--init-rule --examples)

4.1 Installation

$ ./staticcodeaudit /my/project --init-rule --examples

  5 examples installed in audit-rules/_examples/
    01_simple_pattern.sca       — Detection of a simple keyword
    02_multiline_context.sca    — Pattern over multiple lines
    03_file_condition.sca       — match + requires (combo)
    04_exclusion_pattern.sca    — Detect unless safe
    05_requires_only.sca        — File condition only

    Run: ./staticcodeaudit /my/project --rules-validate
    to verify the examples are valid.

4.2 Example contents

01_simple_pattern.sca

Detects console.log() in JavaScript.
The most basic format: a single-line pattern.

02_multiline_context.sca

Detects .execute(f"...") in Python over 4 lines (scope context-4).
The "with" keyword adds an AND condition on the same window.

03_file_condition.sca

Combines match + requires: detects Flask() only in
files that do not import CSRFProtect.
The finding points to the exact line of Flask(), not line 1.

04_exclusion_pattern.sca

Detects hashlib.md5() UNLESS "usedforsecurity=False" is present.
The "pattern-not" keyword excludes lines containing the safe pattern.

05_requires_only.sca

A standalone requires block (without match) produces a file-level finding (line 1).
Detects Django files with MIDDLEWARE but without HSTS.

5. Technical architecture

5.1 Files to create/modify

File Action Description
sca/rules/examples/ Create 5 commented example .sca files
locales/script/{en,fr,es,de}.json Modify Wizard and tester messages
tests/test_rule_wizard.py Create Wizard tests (escaping, generation, validation)
tests/test_rule_tester.py Create Tester tests (execution, output, edge cases)

5.2 Dependencies

  • Zero external dependency: the wizard uses input() and print() (stdlib)
  • No curses, no rich, no prompt_toolkit
  • Compatible with every terminal (no mandatory ANSI sequences)

5.3 Regex escaping — algorithm

smart_escape(user_input):
    1. If it contains explicit regex constructs (\s, \w, .*, .+, [...], (a|b))
       → return as-is (the user knows regex)
    2. Otherwise → escape every special character with re.escape()

5.4 Integration with the existing engine

--test-rule reuses the existing executors (sca/executors/): 1. Loads the rule via load_rules_from_directory() 2. Compiles the regexes via _compile_rule_patterns() 3. Iterates files via _get_files_for_language() (reused from runner) 4. Executes via regex.execute() / file_check.execute() (reuses the executors) 5. Displays findings without generating a report


6. Tier-based gating (license)

Tier Custom rules Features
Demo 0 No custom rules
Solo 20 regex --create-rule, --test-rule, --rules-validate
Team 100 regex + 30 taint Same as Solo + advanced examples
Enterprise Unlimited Everything + encrypted rules

Gating is already implemented in rule_engine._apply_license_gating().


7. Verification and tests

Unit tests (tests/test_rule_wizard.py)

  • Regex escaping: plain text, intentional regex, edge cases
  • .sca generation: valid format, correct fields, file created
  • Creation-time validation: invalid name, invalid pattern, file already exists

Unit tests (tests/test_rule_tester.py)

  • Execution against a fixture: finding detected, correct file/line
  • Rule not found: clear error message
  • 0 files for the language: informative message
  • --file option: tests a single file

Functional tests (tests/test_functional.py)

  • E2E pipeline: custom rule in audit-rules/ → finding present in HTML and JSON reports
  • Wizard: input simulation → valid .sca file generated

Manual verification

  1. --create-rule → answer the questions → file created and valid
  2. --test-rule <id> → findings displayed quickly
  3. --init-rule --examples → examples installed and valid
  4. Full audit → custom rules present in the report