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: HIGHPattern 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
| Syntax | Matches | Example |
|---|---|---|
$VAR | Any single expression | x, foo(), 1+2 |
$...VAR | Zero or more expressions | a, b, c |
... | Any code (statements/expressions) | Multiple lines |
$_ | Unnamed/ignored metavariable | Match 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 < 2048Available 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 --verboseLoading 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.gzDebugging 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