Skip to main content
A firewall rule matches a tool call on two axes: which tool (a tool_name_glob) and which arguments (args_match clauses over JSONPath fields). This page is the precise grammar for both — what a pattern matches, what it doesn’t, and how each operator coerces types — so a rule you author in the console behaves exactly as you read it here. The headline use-case: turn a blunt “block shell.exec” into a surgical “block shell.exec only when the command looks like rm -rf.” The glob picks the tool family; the argument operators pick the dangerous call inside it.
This is the syntax reference. For where these clauses live on a rule, rule ordering, and verdicts, see the Firewall rules deep reference. For the rule fields themselves, see the Firewall overview.

1. Where this syntax applies

Both forms are authored on a firewall rule, in the console under /console/firewall (writes require Developer+). You never call the matcher directly — the gateway evaluates it on every tool call against the resolved policy. A rule matches when its surface, its tool_name_glob, its optional skill glob, and its args_match clauses all match; first match wins.
A rule with no args_match clauses matches on the tool-name glob alone. Add argument clauses only when the tool name isn’t specific enough.

2. Tool-name glob grammar

The tool_name_glob is a deliberately small, predictable grammar — not a full regex. Patterns are matched case-sensitively (MCP tool names are conventionally lowercase-with-dots, so a case-fold would surprise you when you copy a name from the Discovered tools tab).
An empty pattern or a bare * matches all tools. Use it on a catch-all rule, or rely on the policy’s default_verdict instead.
shell.* matches shell.exec, shell.read, shell.write. It does not match the bare shell (the dot is required — a prefix glob only covers namespaced children).
*.exec matches the namespaced shell.exec and the bare, un-namespaced exec (provider-native function calls and non-namespacing MCP servers expose tools under the bare verb, so a suffix rule covers both shapes). The suffix stays anchored at a dot or the string start, so *.exec does not match shell.execute.
*.shell.* matches any <server>.shell.<verb> shape — local.shell.exec, byo.shell.run. It requires at least one character on each side of the infix, so *.shell.* does not match the bare shell or .shell. alone. Only the symmetric *.X.* shape is treated as an infix; a mixed pattern like foo.*.bar falls through to exact match.
Any pattern that isn’t one of the four wildcard shapes above (including a literal tool name, or a malformed combo like foo.*.bar) is compared as an exact, case-sensitive string.
There is no ? single-char wildcard, no character classes, and no mid-token * (e.g. sh*l.exec). A pattern that isn’t one of the four supported shapes is matched literally — sh*l.exec only matches a tool literally named sh*l.exec. Author with the shapes above, not regex muscle memory.

3. JSONPath argument clauses

args_match is a set of clauses, each one a {path, op, value} triple. The path is a JSONPath into the tool call’s argument object; op is one of seven operators; value is what to compare against. All clauses in a rule are AND-ed together — every clause must match for the rule to fire.
{
  "clauses": [
    {"path": "$.command", "op": "regex", "value": "rm -rf|drop table"},
    {"path": "$.connection", "op": "in", "value": ["prod", "replica"]}
  ]
}

Supported JSONPath subset

ShapeMatches
$.fooA top-level key.
$.foo.barA nested key.
$.foo[0]An array element by index.
$.arr[1].kIndex then key (combinations of the above).
That’s the whole subset. There are no wildcards ($.*), filters ($.foo[?(...)]), slices ($.foo[0:2]), or recursive descent ($..foo).
Fail-closed on the path. If a clause’s path resolves to nothing — the key is missing, the arguments aren’t valid JSON, or the value is the wrong type for the operator — that clause evaluates to false, the rule does not fire, and evaluation falls through to the next rule or the policy default. A malformed argument never auto-denies and never crashes the engine.

4. Argument operators

Seven operators, a closed set. The console validator and the live engine share the exact same vocabulary, so a clause that saves is a clause that runs.
OperatorComparesType rules
eqExact equality.Typed: string↔string, bool↔bool, or number↔number. Mixed types never match.
containsSubstring containment.Both sides must be strings; anything else is a non-match. An empty value matches any string.
regexAn RE2 pattern (linear-time, no backreferences) against a string.Value and resolved arg must both be strings. An invalid pattern disables the clause (never matches).
inMembership — the value equals any element of a list.Value must be a JSON array; each element compares with eq semantics.
cidr_matchThe resolved value is an IP inside the given network.Value is a CIDR string (IPv4 or IPv6, e.g. 10.0.0.0/8, fd00::/8); the arg must parse as an IP.
gtGreater-than, numeric.Both sides must coerce to a number. A numeric-looking string is not coerced — it’s a type mismatch (non-match).
ltLess-than, numeric.Same numeric-only rule as gt.
gt and lt are strictly numeric. If a tool sends {"max_rows": "10000"} (a string), a gt 5000 clause does not fire — the string is never coerced. Compare numbers against numbers; use regex or contains for string-shaped values.

5. A worked example

Block a destructive database export, but only when it targets a production connection over a private-network host:
{
  "tool_name_glob": "db.*",
  "args_match": {
    "clauses": [
      {"path": "$.statement", "op": "regex", "value": "(?i)drop|truncate|delete from"},
      {"path": "$.connection.name", "op": "in", "value": ["prod", "prod-replica"]},
      {"path": "$.connection.host_ip", "op": "cidr_match", "value": "10.0.0.0/8"}
    ]
  }
}
Read it top-down: the glob db.* scopes the rule to the database tool family; the three clauses AND together so the verdict (a deny, say) fires only when the statement is destructive and the connection is one of two named prod targets and its host falls in the private 10.0.0.0/8 range. A db.query against a dev connection on a public IP sails past this rule untouched.
Prove the clause before you trust it: the Test tab on a policy dry-runs a sample tool call and shows the matched rule and verdict, persisting nothing and dispatching nothing. See Firewall observability.

6. Egress (host / CIDR) rules

The cidr_match operator above matches an IP that a tool reports in its arguments. That’s different from an egress-surface rule, which evaluates the outbound destination a tool actually reaches with a host/CIDR allow or deny list — the primary SSRF and data-exfiltration defense. Egress rules use the same CIDR notation but live on the egress surface; see Firewall rules for the egress list format.
No preset ships CIDR egress rules for you — the tight autonomy level denies the common fetch-shaped tool names (http_fetch, fetch_url, web_search, request), not network ranges. Author your own host/CIDR deny rule when you need to pin egress to specific destinations.

7. Quick reference

Glob shapes

* all · foo.* prefix · *.exec suffix (+ bare verb) · *.X.* infix · anything else exact. Case-sensitive.

JSONPath

$.foo · $.foo.bar · $.foo[0] · $.arr[1].k. No wildcards, filters, slices, or recursive descent.

String ops

eq (typed) · contains (substring) · regex (RE2) · in (list membership).

Numeric & network ops

gt / lt (numeric only, no string coercion) · cidr_match (IPv4/IPv6 in range).

Firewall rules

The full rule model — surfaces, ordering, sanitizers, sequences, and egress lists.

Dangerous tool calls

The threat these clauses defend against, and how to scope a rule to it.

Verdict glossary

What allow, audit, deny, sanitize, and the rest do once a rule matches.

Why was this blocked?

Trace a specific block back to the rule and clause that fired.