Skip to main content
Naming a tool and denying it is blunt: it kills the tool for every call. Most of the time the tool is fine and one shape of call is the problem — shell.exec is fine until the command is rm -rf, db.query is fine until it hits prod. That distinction is what an argument clause expresses: a jsonpath tool argument predicate that matches on the values an agent passes, so the verdict fires only on the dangerous call and leaves the rest alone. This page is a cookbook — a handful of copy-paste args_match_json recipes for the cases that come up most. For the full clause grammar, operator table, and fail-closed semantics, see Validate arguments and the Rule schema reference.

1. How a jsonpath tool argument clause works

A rule’s args_match_json is a JSON-encoded string carrying a set of clauses, all AND-ed together. The decoded value is an object, {"clauses": [ … ]}, where each clause is a { path, op, value } triple:
  • path — a small JSONPath subset over the tool’s argument object: $.command, $.foo.bar, $.items[0], or $ for the whole object. No wildcards, filters, slices, or recursive descent.
  • op — one of a closed set: eq, contains, regex, in, cidr_match, gt, lt.
  • value — the literal to compare against (a string, number, bool, or — for in — a JSON array).
A clause is AND-ed onto the rule’s tool_name_glob: the rule fires only when the tool name matches and every clause holds. Omit args_match_json entirely (or leave it an empty "{}") and the rule matches on the glob alone.
Clauses fail closed — the rule, not the request. If a path doesn’t resolve, the arguments are malformed, or a value is the wrong type, the clause evaluates false and the rule simply doesn’t fire — the call falls through to the next rule or the default verdict. A broken clause never auto-denies. Write your hard backstop as a plain glob deny, not as a clause you’re relying on to fail a certain way.

2. Recipe: block one destructive command

The canonical case. Allow shell.exec in general, deny it only when the command looks destructive. A regex clause on $.command does it:
{
  "label": "block destructive shell commands",
  "tool_name_glob": "*.exec",
  "verdict": "deny",
  "args_match_json": "{\"clauses\":[{\"path\":\"$.command\",\"op\":\"regex\",\"value\":\"rm -rf|mkfs|dd if=\"}]}"
}
Regexes are Go RE2 — linear-time, no backreferences, no catastrophic backtracking — so they’re safe to run on every tool call. A non-string $.command (or a missing one) never matches, so a malformed call falls through rather than getting wrongly blocked.
Prefer contains over regex when you’re matching a fixed substring — it’s simpler to read and can’t be tripped by an unescaped metacharacter. Reach for regex only when you genuinely need alternation or anchors.

3. Recipe: deny a tool against a named environment

Let db.query run, but only against safe connections — deny it when the target is prod or a replica. The in operator matches the resolved value against any element of a JSON array:
{
  "label": "no agent queries against prod",
  "tool_name_glob": "db.query",
  "verdict": "deny",
  "args_match_json": "{\"clauses\":[{\"path\":\"$.connection\",\"op\":\"in\",\"value\":[\"prod\",\"replica\"]}]}"
}
The in value must be a JSON array — a non-array is rejected when you save the rule. Elements compare with scalar equality, so numbers and strings each match their own type.

4. Recipe: deny a private-IP or metadata destination

When a tool takes a target IP as an argument, cidr_match tests whether it falls inside a CIDR — the SSRF shape of “an agent fetching 10.x or the cloud metadata address”:
{
  "label": "deny tool calls aimed at RFC-1918",
  "tool_name_glob": "*",
  "verdict": "deny",
  "args_match_json": "{\"clauses\":[{\"path\":\"$.target_ip\",\"op\":\"cidr_match\",\"value\":\"10.0.0.0/8\"}]}"
}
The argument value must parse as an IP literal that sits inside the CIDR; a hostname or a non-IP string never matches.
cidr_match only inspects a value that’s already in the tool arguments. For governing the destination a tool actually reaches at the network layer — host/CIDR allow and deny lists — use a dedicated egress rule on the egress surface instead. The two are complementary: argument inspection on the way in, egress control on the way out.

5. Recipe: cap a numeric argument

gt and lt compare numbers. Use them to refuse an absurd batch size, an oversized limit, or any runaway count — here, deny a bulk delete that targets more than 100 rows:
{
  "label": "block large bulk deletes",
  "tool_name_glob": "*.bulk_delete",
  "verdict": "deny",
  "args_match_json": "{\"clauses\":[{\"path\":\"$.count\",\"op\":\"gt\",\"value\":100}]}"
}
Both sides must be numbers — a numeric-looking string like "500" is a type mismatch and does not match, so the rule won’t fire on a stringly-typed argument. Keep the argument numeric, or normalize it before the tool sees it.

6. Recipe: combine clauses (AND)

All clauses in one rule AND together, so you can narrow a verdict to a very specific call — for example, deny shell.exec only when it’s a destructive command and it’s pointed at a prod host:
{
  "label": "destructive shell on a prod host",
  "tool_name_glob": "*.exec",
  "verdict": "deny",
  "args_match_json": "{\"clauses\":[{\"path\":\"$.command\",\"op\":\"regex\",\"value\":\"rm -rf|drop table\"},{\"path\":\"$.host\",\"op\":\"in\",\"value\":[\"db-prod-1\",\"db-prod-2\"]}]}"
}
Need an OR instead? There’s no OR inside a single args_match_json — author two rules (or two globs) at different priorities. The engine walks rules in priority order and the first match wins, so put the narrow rules first. See Rule priority.

7. Pick the verdict for the matched shape

A clause decides which calls match; the rule’s verdict decides what happens. deny is the cookbook default, but the same clause can carry a softer verdict:
When the matched argument carries a secret or PII rather than a dangerous instruction, sanitize redacts the matched substrings from the tool arguments and forwards the cleaned call. It redacts arguments only — never the content a tool returns. See Sanitize responses.
Hold exactly the risky shape for review instead of blocking it outright: a reviewer approves or rejects out-of-band, and the agent re-submits the approved call once. See Approvals.
Set the verdict to audit to record the matched call without blocking while you tune the clause. Pair with shadow mode to measure a deny against live traffic before it changes anything.

8. Test the clause before you depend on it

The console Test tab dry-runs a policy against a sample tool call and returns the verdict, the matched rule, and the reason — nothing is dispatched, nothing is persisted. Paste a realistic argument object and confirm the clause fires on the calls you mean and only those, since a clause that can’t resolve a value silently doesn’t fire. See Test rules.
Clauses are validated strictly on save — unknown operators, bad paths, a non-array in value, an uncompilable regex, and invalid CIDRs are all rejected, so a malformed clause can’t be persisted in the first place.

9. Who can author argument clauses

All of this runs in the console under your session (/api/workspace/firewall/*):
ActionRole
Read policies, presets, discovered toolsMember
Create / edit / delete rulesDeveloper+
Test sandbox (dry-run a policy)Developer+
Read events and run aggregatesDeveloper+
Authoring or changing an argument clause is a Developer+ write. The Test sandbox is also Developer+ — it can preview against a draft policy and its verdict trace exposes policy names and rule labels, so it sits on the write side of the line, not the read side.

Validate arguments

The argument-matching use-case in full.

Block tools

Deny a whole tool by name — no clause needed.

Egress control

Govern outbound host / IP / CIDR destinations.

Rule schema

Every field a rule can carry.

Rule priority

First match wins — order narrow before broad.

Dangerous tool calls

The threat these recipes address.