Writing Rules
A well-written rule is specific, testable, and accompanied by a clear remediation message. This guide walks through the process of designing rules from scratch.
Step 1 — Identify the constraint
Before writing a pattern, articulate the constraint in plain English:
- "We do not allow
eval()in production code." - "All React components must be functional, not class-based."
- "The
momentpackage is banned; usedate-fnsinstead." - "Passwords must not be hardcoded as string literals."
A constraint that is hard to articulate clearly will produce a rule that is hard to maintain.
Step 2 — Choose the right category
Pick the category that best fits:
- Code quality / UI patterns →
frontendorbackend - Security vulnerabilities →
security - AI model integration →
llms - PII, schemas, migrations →
data - Infrastructure, containers →
deployment - Performance, caching →
scaling - npm packages →
dependencies
Step 3 — Write the pattern
Patterns are ECMAScript regular expressions. The engine tests each added or modified line against the pattern.
Simple literal match
pattern: "console\\.log"
Matches any line containing console.log.
Alternation
pattern: "eval\\(|Function\\("
Matches lines with either eval( or Function(.
Word boundaries
Use \\b to avoid partial matches:
# Matches: eval(
# Does NOT match: preeval(, evaluator
pattern: "\\beval\\("
Character classes
# Matches: document.getElementById, document.querySelector, document.querySelectorAll
pattern: "document\\.(getElementById|querySelector)"
Common pitfalls
| Problem | Wrong | Right |
|---|---|---|
| Dots match any char | "import.moment" | "import\\.moment" |
| Greedy matching | "secret.*value" | "secret\\s*=\\s*['\"][^'\"]{6,}" |
| Case sensitivity | "eval" | "eval" + test with actual code |
| Escaping in YAML | Single backslash | Double backslash: "\\b" |
YAML escaping
In YAML double-quoted strings, backslashes must be doubled. The regex \beval\( becomes "\\beval\\(" in YAML:
pattern: "\\beval\\s*\\(" # regex: \beval\s*\(
In YAML single-quoted strings, no escaping is needed — but you cannot use special YAML characters:
pattern: '\beval\s*\(' # works, but harder to read for complex patterns
Step 4 — Set the severity
Ask: "If this pattern appears in a merged PR, how bad is it?"
- Would it introduce a security vulnerability? →
block - Would it break a team convention but not cause a bug? →
warn - Is it a style suggestion? →
info
When in doubt, start at warn. You can upgrade to block later (the ratchet allows upgrades).
Step 5 — Write the remediation message
The remediation message appears in the check output next to the violation. It should:
- Explain why the pattern is banned
- Show what to do instead with a concrete example
- Be brief — 2-4 sentences max
remediation: >
eval() allows arbitrary code execution and is a critical
XSS vulnerability. Use JSON.parse() for deserializing JSON,
or a safe expression library (e.g. expr-eval) for computed
expressions.
Step 6 — Add exclusions
Most rules have legitimate exceptions. Add them as glob patterns:
exclude:
- "**/*.test.ts" # test files may use eval for mocking
- "**/*.spec.tsx"
- "**/fixtures/**"
- "**/scripts/migrate-*"
Use ** for recursive matching. The glob is matched against the file path relative to the project root.
Step 7 — Add compliance mappings
If your organization reports against SOC 2, EU AI Act, HIPAA, or DORA, map each rule to the relevant controls:
maps-to:
- framework: soc2
control: CC6.1
- framework: hipaa
control: "164.312(a)(2)(iv)"
See Framework Mappings for the full list of available controls.
Step 8 — Test the rule
Use the dry-run mode to test before committing:
vyb check --dry-run
Or use the MCP server to test a proposed rule interactively:
proposeRule → dryRunRule → (review output) → commitRule
See Dry Run for the complete testing workflow.
Full example: a complete security rule
- id: sec-007
name: no-prototype-pollution
description: >
Detect patterns that may enable prototype pollution —
assigning to __proto__, constructor.prototype, or
Object.setPrototypeOf with user-controlled input.
severity: block
pattern: "__proto__|constructor\\.prototype|Object\\.setPrototypeOf"
pattern-mode: line
remediation: >
Prototype pollution allows attackers to inject properties
into base objects. Use Object.create(null) for data maps,
validate input schema with Zod, and never merge untrusted
objects without explicit key allowlisting.
exclude:
- "**/*.test.ts"
- "**/polyfills/**"
enabled: true
maps-to:
- framework: soc2
control: CC7.1
- framework: eu-ai-act
control: Art.9.5
Tips for teams using AI IDEs
When your team uses Cursor or Claude Code, some AI-generated patterns may be subtle. Consider:
- Semantic rules are hard: Regex matches text, not semantics. A rule banning
evalwon't catchwindow["ev"+"al"](). Use multiple patterns for known evasions. - Test with real AI output: Run a sample generation in your IDE and check the output against your rules. AI tools sometimes choose unexpected idioms.
- Use the MCP server for iteration: The Claude Desktop MCP lets you
dryRunRuleagainst your actual codebase before committing.
Next: Dry Run Mode