Custom Rules

Create custom security rules using Qryon's Semgrep-compatible YAML format to detect organization-specific vulnerabilities and code patterns.

Rule File Structure

Custom rules are written in YAML format. Qryon loads rules from:

  • ./rules/ directory in your project
  • ~/.config/rma/rules/ for user-wide rules
  • Any path specified with --rules-path

Basic Rule Syntax

# rules/my-rules.yaml
rules:
  - id: my-organization-sql-injection
    message: "SQL query built with string concatenation"
    severity: ERROR
    languages:
      - javascript
      - typescript
    pattern: |
      $DB.query($SQL + ...)
    metadata:
      cwe: CWE-89
      owasp: A03:2021
      category: security
      confidence: HIGH

Pattern Syntax

Basic Patterns

# Match exact code
pattern: console.log("debug")

# Match with metavariables
pattern: console.log($MSG)

# Match any number of arguments
pattern: console.log(...)

# Match specific metavariable
pattern: dangerous_function($USER_INPUT)

Metavariables

SyntaxMatchesExample
$VARAny single expressionx, foo(), 1+2
$...VARZero or more expressionsa, b, c
...Any code (statements/expressions)Multiple lines
$_Unnamed/ignored metavariableMatch but don't capture

Pattern Operators

rules:
  - id: complex-pattern
    message: "Complex vulnerability pattern"
    severity: ERROR
    languages: [javascript]

    # Match ALL of these patterns (AND)
    patterns:
      - pattern: $DB.query($SQL)
      - pattern-inside: |
          function $FUNC(...) {
            ...
          }

    # Match ANY of these patterns (OR)
    pattern-either:
      - pattern: db.query($SQL)
      - pattern: db.run($SQL)

    # Exclude these patterns
    pattern-not:
      - pattern: db.query($SQL, [...])  # Parameterized

    # Must be inside this context
    pattern-inside: |
      app.$METHOD($PATH, function(...) {
        ...
      })

    # Must NOT be inside this context
    pattern-not-inside: |
      if (isValidated($INPUT)) {
        ...
      }

Taint Mode Rules

Taint mode rules track data flow from sources to sinks:

rules:
  - id: taint-sql-injection
    message: "User input flows to SQL query without sanitization"
    severity: ERROR
    languages: [javascript, typescript]
    mode: taint

    pattern-sources:
      - pattern: req.params.$PARAM
      - pattern: req.query.$PARAM
      - pattern: req.body.$PARAM

    pattern-sinks:
      - pattern: $DB.query($SINK, ...)

    pattern-sanitizers:
      - pattern: parseInt($X, ...)
      - pattern: sanitize($X)
      - pattern: escape($X)
      - pattern: $DB.escape($X)

Metavariable Conditions

rules:
  - id: weak-crypto
    message: "Weak cryptographic algorithm: $ALGO"
    severity: WARNING
    languages: [javascript]

    pattern: crypto.createHash($ALGO)

    metavariable-regex:
      metavariable: $ALGO
      regex: "(md5|sha1|md4)"

    metavariable-comparison:
      metavariable: $SIZE
      comparison: $SIZE < 2048

Available Conditions

# Regex match
metavariable-regex:
  metavariable: $VAR
  regex: "pattern"

# Comparison
metavariable-comparison:
  metavariable: $NUM
  comparison: $NUM < 100

# Type checking (TypeScript/Java)
metavariable-type:
  metavariable: $VAR
  type: string

# Pattern match on metavariable
metavariable-pattern:
  metavariable: $FUNC
  pattern: dangerous_

Language-Specific Rules

JavaScript/TypeScript

rules:
  - id: react-unsafe-render
    message: "User input in unsafe render method"
    severity: ERROR
    languages: [javascript, typescript, tsx]
    mode: taint

    pattern-sources:
      - pattern: props.$PROP
      - pattern: this.state.$STATE
      - pattern: useState(...)[0]

    pattern-sinks:
      - pattern: $ELEMENT.unsafeRender($SINK)

Python

rules:
  - id: flask-sql-injection
    message: "SQL injection in Flask route"
    severity: ERROR
    languages: [python]
    mode: taint

    pattern-sources:
      - pattern: request.args.get(...)
      - pattern: request.form[...]
      - pattern: request.json

    pattern-sinks:
      - pattern: cursor.run($SINK, ...)
      - pattern: db.engine.run($SINK)

    pattern-sanitizers:
      - pattern: int($X)
      - pattern: bleach.clean($X)

Java

rules:
  - id: spring-sql-injection
    message: "SQL injection in Spring application"
    severity: ERROR
    languages: [java]
    mode: taint

    pattern-sources:
      - pattern: |
          @RequestParam $TYPE $PARAM
      - pattern: |
          @PathVariable $TYPE $PARAM

    pattern-sinks:
      - pattern: jdbcTemplate.query($SINK, ...)
      - pattern: entityManager.createNativeQuery($SINK)

    pattern-sanitizers:
      - pattern: Integer.parseInt($X)
      - pattern: Long.parseLong($X)

Rule Metadata

rules:
  - id: my-rule
    message: "Description shown to users"
    severity: ERROR  # ERROR, WARNING, INFO
    languages: [javascript]
    pattern: ...

    metadata:
      # Standard fields
      cwe: CWE-89
      owasp: A03:2021
      category: security
      confidence: HIGH  # HIGH, MEDIUM, LOW
      likelihood: HIGH
      impact: HIGH

      # Custom fields
      team: security-team
      ticket: SEC-1234

      # References
      references:
        - https://owasp.org/Top10/A03_2021-Injection/
        - https://internal.wiki/security/sql-injection

      # Fix suggestion
      fix: |
        Use parameterized queries:
        db.query('SELECT * FROM users WHERE id = ?', [id])

Testing Rules

Include test cases directly in your rule files:

rules:
  - id: sql-injection
    message: "SQL injection detected"
    severity: ERROR
    languages: [javascript]
    pattern: $DB.query($SQL + ...)

    # Test cases
    examples:
      - code: |
          // Should match
          db.query("SELECT * FROM users WHERE id = " + userId);
        should_match: true

      - code: |
          // Should NOT match (parameterized)
          db.query("SELECT * FROM users WHERE id = ?", [userId]);
        should_match: false
# Run rule tests
rma rules test ./rules/my-rules.yaml

# Test specific rule
rma rules test ./rules/my-rules.yaml --rule sql-injection

# Verbose test output
rma rules test ./rules/my-rules.yaml --verbose

Loading Custom Rules

# Load from directory
rma scan . --rules-path ./my-rules/

# Load from multiple locations
rma scan . --rules-path ./rules/ --rules-path ~/.config/rma/rules/

# Load specific file
rma scan . --rules-path ./rules/custom-sql.yaml

# Disable default rules, use only custom
rma scan . --no-default-rules --rules-path ./rules/

Sharing Rules

Rule Packs

Bundle related rules into a pack:

# rules/pack.yaml
pack:
  name: my-org-security
  version: 1.0.0
  description: Security rules for My Organization

rules:
  - id: rule-1
    ...
  - id: rule-2
    ...

Publishing Rules

# Validate rules before publishing
rma rules validate ./rules/

# Create rule pack
rma rules pack ./rules/ -o my-org-rules.tar.gz

# Install published rules
rma rules install https://example.com/rules/my-org-rules.tar.gz

Debugging Rules

# Test pattern against code snippet
rma rules debug --pattern 'db.query($SQL + ...)' --code 'db.query("SELECT " + id)'

# Show AST for pattern development
rma ast ./test-file.js

# Verbose pattern matching
rma scan . --rules-path ./rules/ --debug-rules

Next Steps