shell.exec is fine for ls; it is a
disaster for rm -rf /. db.query is fine against a replica; against
prod it’s a liability. The difference lives in the arguments, and a
tool-name rule can’t see it.
The Firewall’s argument clauses (args_match_json) close that gap. They
inspect the concrete arguments the model chose for a tool call and decide
the verdict from their values — so you can permit a tool broadly while
denying the one dangerous shape it can take. This page is the focused guide
to authoring those clauses; for the full rule vocabulary see
Firewall rules, and for the policy model around
them, Firewall.
Argument values only exist once the model has chosen how to call a tool,
so argument clauses belong on the
response and mcp
stages. On inbound — where the agent only
advertises tool definitions — there are no call-time arguments to check.1. When to validate tool call arguments
Reach for an argument clause whenever a tool is safe in general but dangerous in a specific shape:Destructive commands
Allow
shell.exec, but deny when the command matches rm -rf,
mkfs, or dd if=.Production blast radius
Allow
db.query, but deny (or hold for approval) when the connection
target is prod.Internal destinations
Allow a fetch tool, but deny when its
url/ip argument falls inside
an RFC-1918 range or the cloud-metadata IP.Oversized operations
Allow a bulk tool, but deny when a
limit or count argument exceeds
a numeric ceiling.2. The shape of a clause set
args_match_json is a JSON-encoded string whose decoded value is an
object holding a list of clauses. Each clause is a { path, op, value }
triple, and all clauses AND together — the rule fires only when every
clause is true. Decoded, the value looks like:
"args_match_json": "{\"clauses\":[{\"path\":\"$.command\",\"op\":\"regex\",\"value\":\"rm -rf\"}]}".
An empty or absent args_match_json is vacuously true — the rule
matches on its tool-name glob alone, exactly as a name-only rule does.
3. Operators
Seven operators make up the closed vocabulary. The console validates the operator and its value shape when you save, so a malformed clause never persists.| Operator | Matches when |
|---|---|
eq | Scalar equality (numbers compared numerically; a type mismatch is no match). |
contains | Substring — both operands must be strings. |
regex | A Go RE2 pattern matches the string value (linear-time, no backreferences). |
in | The value is an element of the given JSON array. |
cidr_match | The string IP falls inside the given CIDR. |
gt / lt | Numeric greater-than / less-than (strings are not coerced). |
4. Path syntax
A clause’spath is a small JSONPath subset over the tool’s argument
object:
$.foo, $.foo.bar — field access
$.foo, $.foo.bar — field access
Read a top-level or nested object field by name.
$.foo[0], $.arr[1].k — array indexing
$.foo[0], $.arr[1].k — array indexing
Index into an array, optionally continuing into the element’s fields.
$ — the whole arguments object
$ — the whole arguments object
Match against the entire argument blob (useful with
contains or
regex for a coarse scan).5. A worked example
You let your agents runshell.exec freely, but a recursive force-delete
should never reach the shell. Author one response-stage rule that denies
shell.exec only when the command argument looks destructive.
Open the rule editor
In the console, open the firewall policy attached to your agent’s key
(or the workspace default) and add a rule. Editing policies is a
Developer+ action — Members can read policies but not write them.
Match the tool on the response stage
Set the stage to
response and the tool glob to shell.exec. The
response stage carries the model’s chosen arguments, which the clause
needs.Add the argument clause
Add one
regex clause on $.command, then set the verdict to deny:args_match_json is a JSON-encoded string; its decoded value is the
{ "clauses": [ … ] } object shown in §2.Dry-run it before you depend on it
Use the Test tab to evaluate the rule
against a sample
shell.exec call. It returns the verdict, the matched
rule, and the reason — nothing is dispatched and nothing is persisted.shell.exec with "command": "ls -la" flows through as before, while
"command": "rm -rf /var" is denied. A deny on response lets the model
see a tool error and react — pick another tool, ask the user, or stop —
rather than crashing.
6. Clauses fail closed — the rule, not the request
If a clause can’t be evaluated — the path doesn’t resolve, the arguments are malformed, or a regex / CIDR is invalid — the clause evaluates to false and the rule simply does not fire. The call falls through to the next rule or the policy’sdefault_verdict. A broken clause never
auto-denies and never disturbs the relay.
7. Combining clauses with the rest of a rule
Argument clauses stack with everything else a rule expresses — they’re one AND-ed condition among several:| Combine with | Effect |
|---|---|
tool_name_glob | The clause only runs once the tool name matches — name first, arguments second. |
skill_name_glob | Gate the same tool’s arguments differently by owning skill (e.g. stricter on community.*). |
verdict | Pair clauses with deny, sanitize, pending_approval, or cap_cost, not just deny. |
| Multiple clauses | All must hold — combine a regex command check with an in environment check to scope a deny tightly. |
8. Where this fits
Firewall rules
The complete rule reference — globs, clauses, sanitizers, egress, and
sequences.
Argument cookbook
Copy-paste
args_match_json recipes for the common dangerous shapes.Firewall stages
Why argument clauses live on
response and mcp, not inbound.Block tools
Deny a tool outright when no argument is safe.
