Smart Rules · node9 documentation
Smart Rules
Condition-based policy that matches raw tool arguments — not just extracted tokens.
Smart rules operate on the raw JSON arguments the AI sends, before any tokenization. Unlike dangerousWords (which scan extracted tokens), smart rules let you express structural conditions like "block DELETE only when no WHERE clause is present" — directly in your config. They are the primary way to write custom policy in Node9.
Built-in default
Node9 ships with one default smart rule active out of the box: no-delete-without-where. It blocks any SQL DELETE or UPDATE that lacks a WHERE clause — without requiring any config changes.
Anatomy of a Smart Rule
Full smart rule example
{
"name": "block-prod-kubectl",
"tool": "bash",
"conditions": [
{
"field": "command",
"op": "matches",
"value": "kubectl.*--namespace[= ]production",
"flags": "i"
}
],
"conditionMode": "all",
"verdict": "block",
"reason": "Production kubectl operations require a manual release process"
}Fields
toolrequired
The tool name to match. Supports glob patterns:
"bash", "mcp__postgres__*", "*" (all tools).conditionsrequired
Array of conditions evaluated against the raw args object. All must pass by default (
conditionMode: "all").conditionModeoptional
"all" — every condition must be true (AND logic). Default."any" — at least one condition must be true (OR logic).verdictrequired
What happens when the rule matches:
"review" — show the human approval prompt"block" — hard deny immediately, no prompt shown"allow" — skip all further policy checksreasonoptional
Human-readable explanation shown in the approval prompt, negotiation message, and audit log.
nameoptional
A label for the rule, shown in
node9 explain output and audit entries.Condition Operators
Available
op values| op | Meaning |
|---|---|
| matches | Field value matches a regex pattern (use flags: "i" for case-insensitive) |
| notMatches | Field value does NOT match the regex pattern |
| contains | Field value contains the given substring |
| notContains | Field value does NOT contain the substring |
| exists | Field is present and non-empty |
| notExists | Field is absent or empty |
| matchesGlob | Field value matches a glob pattern (e.g. "**/node_modules/**") |
| notMatchesGlob | Field value does NOT match the glob pattern |
Dot-notation field paths
The
field key supports dot-notation to access nested args: "params.query.sql" navigates into { params: { query: { sql: '...' } } }. Field values are whitespace-normalized before matching, so multi-space SQL won't bypass regex rules.Examples
Block DELETE/UPDATE without WHERE (built-in default)
{
"name": "no-delete-without-where",
"tool": "*",
"conditions": [
{ "field": "sql", "op": "matches", "value": "^(DELETE|UPDATE)\\s", "flags": "i" },
{ "field": "sql", "op": "notMatches", "value": "\\bWHERE\\b", "flags": "i" }
],
"conditionMode": "all",
"verdict": "review",
"reason": "DELETE/UPDATE without WHERE clause — would affect every row in the table"
}Block writes outside the project directory
{
"name": "sandbox-writes",
"tool": "write_file",
"conditions": [
{ "field": "path", "op": "notMatches", "value": "^/home/user/my-project" }
],
"verdict": "review",
"reason": "writing outside project directory"
}Block wget piped to shell (extends the built-in curl coverage)
Node9 ships with
review-curl-pipe-shell built in, which catches curl ... | bash. The default does not cover wget, so the rule below adds hard-block coverage for the same attack pattern using a different fetcher:{
"name": "block-wget-pipe-shell",
"tool": "bash",
"conditions": [
{ "field": "command", "op": "matches", "value": "wget.+\\|.*(bash|sh)", "flags": "i" }
],
"verdict": "block",
"reason": "wget piped to shell — remote code execution risk"
}Note that built-in defaults are evaluated first in the smart-rules waterfall, so a project rule cannot downgrade or upgrade a verdict that a default already produced — it can only cover cases the default misses.Merge Behaviour
Smart rules from all layers are concatenated in order: built-in defaults → active shields → global config → project config. First match wins. You cannot remove a built-in or shield rule from a lower layer.
Debugging with node9 explain
Dry-run the policy engine
Use
node9 explain to see exactly which smart rule (or other tier) would trigger for any tool call:# Check if a DELETE would be flagged
node9 explain execute_sql '{"sql":"DELETE FROM users"}'
# Check a safe scoped DELETE
node9 explain execute_sql '{"sql":"DELETE FROM orders WHERE id=1"}'The output shows the full waterfall evaluation step-by-step, including which smart rule matched and its verdict.