Input & Output Controls
Agent Firewall
agent-firewall checks the tool calls in model traffic and allows or blocks each one. For example, an agent may call search and read_* tools but never a deletion tool. It can also limit how many actions one request contains, stop on suspicious commands, and escalate large payments for approval.
Every limit applies to one request or response. The control keeps no counters between requests, so max_daily_total means the total in this request, not in a day.
Configuration
The control has a simple shape and an expanded shape. Use one of them in a block. Lint rejects a block that mixes them.
Start with the simple shape. Save it as policy-config.yaml in a test directory, and use the config-first workflow to apply it.
pack:
name: agent-firewall-simple
version: 1.0.0
enabled: true
policies:
chain:
- agent-firewall
policy:
agent-firewall:
allowed_tools:
- read_*
- search
blocked_tools:
- rm_rf
max_actions_per_session: 50search and read_document are allowed. rm_rf is blocked, and write_document is rejected because no rule allows it.
Expanded shape: role, count, and transaction rules
Use this shape for rules by role. Do not combine it with the simple shape.
pack:
name: agent-firewall-expanded
version: 1.0.0
enabled: true
policies:
chain:
- agent-firewall
policy:
agent-firewall:
tools:
roles:
analyst:
allowed:
- read_database
- export_csv
denied:
- delete_database
rate_limits:
export_csv: 1
transaction_limits:
max_single_transaction: 5000.0
max_daily_total: 20000.0
require_approval_above: 1000.0
kill_switches:
halt_on_suspicious_pattern: true
halt_on_pii_in_action: trueTry it without a provider
The control reads tool calls, so give each test case a raw request with an assistant tool call. Add this to the simple configuration:
testing:
suites:
- name: agent-firewall
cases:
- name: allows-search
input:
request:
model: test-model
messages:
- role: user
content: "Find the report."
- role: assistant
tool_calls:
- id: call_1
type: function
function: {name: search, arguments: "{\"q\":\"report\"}"}
expected: {verdict: allow, reason_code: ok}
- name: blocks-rm-rf
input:
request:
model: test-model
messages:
- role: user
content: "Clean up."
- role: assistant
tool_calls:
- id: call_1
type: function
function: {name: rm_rf, arguments: "{}"}
expected: {verdict: block, reason_code: agent_firewall.blocked_tool.rm_rf}
- name: blocks-unlisted-tool
input:
request:
model: test-model
messages:
- role: user
content: "Save it."
- role: assistant
tool_calls:
- id: call_1
type: function
function: {name: write_document, arguments: "{}"}
expected: {verdict: block, reason_code: agent_firewall.tool_not_allowed}Run verdictan policy test --json. All three cases pass. Then send a real tool call through a test gateway before you connect a tool executor.
Fields
| Field | Type | Default | Description |
|---|---|---|---|
allowed_tools | string[] | ["*"] | Simple shape. Tool name patterns that are allowed. * matches any sequence, including an empty one. |
blocked_tools | string[] | [] | Simple shape. Tool name patterns that are blocked. A block wins over an allow. |
max_actions_per_session | integer | 0 | Both shapes. Maximum number of assistant messages in the request. 0 turns the check off. The schema shows 500. |
tools.roles.<role>.allowed | string[] | [] | Expanded shape. Tool patterns allowed for one role of the verified identity |
tools.roles.<role>.denied | string[] | [] | Expanded shape. Tool patterns denied for the role |
rate_limits.default | integer | 0 | Expanded shape. Maximum tool actions in one evaluation. 0 turns it off. |
rate_limits.<tool> | integer | Expanded shape. Maximum calls of one tool in one evaluation | |
transaction_limits.max_single_transaction | number | 0.0 | Expanded shape. Largest dollar amount allowed in the tool text. 0.0 turns it off. |
transaction_limits.max_daily_total | number | 0.0 | Expanded shape. Largest allowed sum of dollar amounts in the tool text. 0.0 turns it off. |
transaction_limits.require_approval_above | number | 0.0 | Expanded shape. Escalates when the largest amount reaches this value. 0.0 turns it off. |
kill_switches.halt_on_suspicious_pattern | boolean | false | Expanded shape. Blocks when the tool text contains a built-in suspicious pattern |
kill_switches.halt_on_pii_in_action | boolean | false | Expanded shape. Blocks when a tool argument contains PII |
The schema shows defaults that the runtime does not use: rate_limits.default: 60, transaction limits of 10000, 50000, and 5000, and both kill switches true. At runtime, a missing field turns its check off. Set every limit you need.
Lint also accepts kill_switches.max_errors_before_halt and a monitoring section. The runtime ignores them.
How it works
- The control reads tool calls from the request or response:
messages[].tool_callsof assistant messages,choices[].message.tool_calls, Responses APIoutputandinputitems, and Anthropiccontentblocks. A tool call that it cannot parse blocks withagent_firewall.malformed_tool_call. - It also finds tool names in assistant text with patterns such as
"name": "<tool>",function_call: <tool>,tool: <tool>, andcalling <tool>. - With
tools.roles, it keeps the roles of the verified identity that appear in the table. - With
halt_on_suspicious_pattern, tool text that containsrm -rf,sudo,drop table,exfiltrate, ordisable securityblocks withagent_firewall.kill_switch. - Too many actions in total, or too many calls of one tool, blocks with
agent_firewall.rate_limit_exceeded. - More assistant messages than
max_actions_per_sessionblocks withagent_firewall.session_limit_exceeded. - The control collects each
$amount in the tool text. Over a limit, it blocks withagent_firewall.transaction_value_exceededoragent_firewall.daily_total_exceeded. At or aboverequire_approval_above, it escalates withagent_firewall.approval_required. - With
halt_on_pii_in_action, a PII match in a tool argument blocks withagent_firewall.pii_in_tool_args. - With
tools.rolesand tool actions present, an identity without a listed role is blocked withagent_firewall.authoritative_role_required. - Each tool action is checked in this order: role
denied, roleallowed,blocked_tools,allowed_tools. The reason codes areagent_firewall.role_denied_tool,agent_firewall.role_tool_not_allowed,agent_firewall.blocked_tool.<tool>, andagent_firewall.tool_not_allowed. - Otherwise, the request is allowed with
agent_firewall.allowed.
The allow details contain action_count, session_action_count, role, authoritative_roles, authoritative_permissions, actions, and structured_action_count.
Phase and verdicts
| Item | Value |
|---|---|
| Phase | tool, in the input and output stages |
| Request families | Chat Completions, Responses, Messages |
| Verdicts | allow, block, escalate |
| Reason codes | agent_firewall.<check>, listed below |
agent_firewall.allowedagent_firewall.malformed_tool_callagent_firewall.kill_switchagent_firewall.rate_limit_exceededagent_firewall.session_limit_exceededagent_firewall.transaction_value_exceededagent_firewall.daily_total_exceededagent_firewall.approval_requiredagent_firewall.pii_in_tool_argsagent_firewall.authoritative_role_requiredagent_firewall.role_denied_toolagent_firewall.role_tool_not_allowedagent_firewall.blocked_tool.<tool>agent_firewall.tool_not_allowed
A block returns HTTP 400 with error code content_policy_violation. An escalation returns HTTP 200 with content: null, finish_reason: content_filter, and a verdictan.escalation object with the escalation ID and status queued.
Limits and failure behavior
- Counts cover one request or response only. The gateway keeps no counter between requests, so these are not durable rate limits.
- Transaction checks read only
$amounts, such as$1,250.00. Other currency symbols and amounts without$pass. - In the simple shape, an empty
allowed_toolslist allows nothing. Leave the field out to keep the["*"]default. - With
tools.roles, identity roles that the table does not list are ignored. If none is listed, every tool action is blocked. If several are listed, every one of them must allow the tool. - Role rules use only the verified identity. An
X-User-Roleheader does not select a role. - The suspicious pattern check matches five fixed substrings.
sudomatches only when a space follows it. - The PII check on tool arguments uses the base detector, without
healthcare_modeorpci_mode. - With
stream: true, the gateway holds the streamed tool calls until the stream ends, checks them, and then sends the stream or a final error. - A request without tool calls is never blocked by this control. To block on identity alone, use RBAC.
Next steps
- RBAC: allow tools by verified role, with data sensitivity limits.
- Tool Security: check request payloads for unsafe tool definitions.
- Resolve an Escalation: approve the payments that
require_approval_aboveescalates.