Documentation

Agent Context

Govern what an agent can do, not just what it says — the tools it offers the model, the MCP servers wired into the session, the skills it exposes, and which client is driving.

Guardrails govern what a request says. This section governs what it can do.

Every request an AI coding agent makes carries a list of the tools it is offering the model: its own (Bash, Read, Edit), the flattened tools of every MCP server the developer has wired in, and any remote MCP server the provider is being asked to call. These APIs are stateless, so the client re-sends that whole list on every turn. The firewall reads it, can refuse it, and records it.

That closes a gap most organisations do not know they have. A developer adds an MCP server to a local config on a Tuesday. It holds a token for the issue tracker. It is now in every prompt that developer’s agent sends, and it appears in no manifest, no repository scan, and no change ticket.

Note The inventory is free on every plan — an organisation that cannot yet write rules is exactly the one that most needs to see what its agents are carrying. The rules on this page require the Teams plan, alongside content guardrails. See what is free and what is paid.

The model: four families × four actions

FamilyRule typesMatches against
Toolstool_allow, tool_denyEvery tool name in the request, including MCP tools the client has flattened in
MCP serversmcp_allow, mcp_denyRemote MCP servers the request declares by URL
Skillsskill_allow, skill_denyNamed skills offered through a dispatching tool
Clientsclient_allow, client_denyThe calling agent, derived from request headers
ActionWhat happens
flagThe request proceeds. The match is recorded.
stripThe tool or MCP server is removed from the request and the request proceeds. The model is never offered it. Tools and MCP servers only.
blockThe request is rejected with a 403, naming the rule.

redact is a content action and does not apply here — there is no text to rewrite. strip is its analogue on this plane.

Tip Start every rule at flag, and start from the inventory. You almost certainly do not yet know which tools your developers carry. A rule written from a guess breaks a working session; a rule written from a list of what is actually running does not. See rolling out a rule.

Patterns are globs, not regexes

Content guardrails take an RE2 regex. Capability rules do not — they take a glob, where only * is special and every other character is literal.

PatternMatchesDoes not match
BashBashBashful, bash, BashOutput
mcp__github__*every tool from the github MCP servermcp__gitlab__create_issue
*__delete_*mcp__jira__delete_issuemcp__jira__create_issue
^Bash$a tool literally named ^Bash$Bash

A name list is the one place regex power is a liability. Someone typing a tool name writes Bash and means exactly Bash; an unanchored regex would also match Bashful, and a . typed by habit would match any character. Matching is case-sensitive, because wire names are.

See the glob reference.

Allowlist mode

Any enabled *_allow rule switches its family into allowlist mode: from then on, anything in that family not matched by an allow rule is refused.

This is the same sentence as the model allow/deny lists, deliberately. One rule to learn, not two.

If several allow rules in a family disagree about the action, the strictest one governs what happens to an unlisted capability. Adding a permissive rule can never weaken enforcement an existing one already provides.

How rules are evaluated

  • Only enabled rules run.
  • Capability rules run before content rules, as a group. Two reasons: allowlist mode is a property of a whole family rather than of any one rule, so it cannot be expressed inside a single priority-ordered pass; and a request offering a forbidden tool should be refused before the firewall pays to scan its text.
  • Within the capability plane, explicit denies are evaluated in ascending priority, then allowlist gaps. A deny you wrote by hand always reports itself as the cause rather than being masked by “not in the allowlist”.
  • A block short-circuits.

priority therefore orders rules within a plane, not across the two. A content rule at priority 1 still runs after a capability rule at priority 999.

When a request is blocked

HTTP 403, in the dialect of whatever client asked, with a code naming the family so a caller can branch on it:

{
  "type": "error",
  "error": {
    "type": "permission_error",
    "message": "request blocked by AI firewall policy: tool \"Bash\" denied by policy \"no-shell\"",
    "code": "tool_denied",
    "blocked_by": "no-shell",
    "violations": [
      {
        "policy_name": "no-shell",
        "rule_type": "tool_deny",
        "action": "block",
        "detail": "tool \"Bash\" denied by policy \"no-shell\""
      }
    ]
  }
}

Codes: tool_denied, mcp_denied, skill_denied, client_denied. The full list is in the error reference.

Writing rules

Dashboard

The Tools & MCP tab of the AI Firewall dashboard.

Start at the inventory panel: it lists what your agents actually carry, and each row has a one-click Deny or Allow that writes an ordinary rule set to flag.

CLI
vulnetix ai-firewall policy guardrail no-jira-writes \
  --rule-type tool_deny \
  --action strip \
  --pattern 'mcp__jira__*' \
  --priority 100 \
  --enable

Read the inventory first:

vulnetix ai-firewall inventory --kind mcp_server
Policy file
# .vulnetix/ai-firewall.yaml
apiVersion: vulnetix.com/v1
kind: AiFirewallPolicy
spec:
  guardrails:
    - name: no-jira-writes
      ruleType: tool_deny
      action: strip
      pattern: 'mcp__jira__*'
      priority: 100
      enabled: true

See policy as code.

The pages

What this cannot do

Stated here rather than only at the end, because it changes what the feature is for:

A tool runs on the developer’s machine before the firewall ever sees it. When Claude Code runs Bash, it runs locally, and the result is what gets sent. A rule can stop the tool being offered so the model never calls it again, and can refuse a request whose history shows it was called — but it cannot un-run a command.

Client identity comes from headers, which any caller can set. It is attribution, and it is genuinely useful for that. It is not a security boundary. Client identity says so at the top of the page.

The body-side controls are the ones with teeth, and not because a body cannot be faked either: a lie there is self-defeating. A client that hides a tool from the firewall has also hidden it from the model.