The web application firewall existed because application code could not be trusted to validate its own inputs. It sat in front, it saw every request, it had a policy, and it logged what it decided. It did not make the application secure. It made the application's failures bounded.
Agents are at that point now. A coding agent with a shell and a GitHub token can do anything the token can do, and the model deciding whether to do it is the same model that just read a file containing instructions from a stranger. Prompt injection is not a bug you fix in the prompt. It is a property of the architecture.
So a category appeared. AgentFence, Vigils, Harden, agent-wall, a dozen small projects with "firewall" in the name, plus sandboxed MCP runners. They differ in polish. They agree on the shape: a local proxy between the agent and its tools that decides allow, deny or ask on every call, before the call runs.
TL;DR
- An agent firewall is a policy proxy in the tool path. It intercepts every tool call, evaluates it against a deny-by-default policy, forwards allowed calls, blocks denied ones before any side effect, and routes ask calls to a human.
- Good ones also redact secrets from arguments and logs, validate arguments against path, URL and shell constraints, and write append-only, hash-chained, signed audit logs.
- The MCP 2026-07-28 stateless core makes this layer cleaner: every request is self-describing, so the proxy can decide from one request without reconstructing a session.
- An API key in .env is the thing this replaces. The MCP roadmap's agent identity work (DPoP, workload identity federation, token exchange) is where the caller side is heading.
The shape of the thing
Take AgentFence as the reference implementation, because its README is precise about the mechanics and the pattern is shared.
It is one Go binary. In proxy mode it spawns your MCP server as a subprocess and relays JSON-RPC over stdio in both directions, intercepting every tools/call. An allowed call is forwarded. A denied call gets a JSON-RPC error and the server never sees it. An ask call hits an approver, with a timeout and an auto-deny in non-interactive contexts. A second mode gates remote MCP servers over streamable HTTP. A batch mode replays recorded tool calls against the policy in CI and fails the build on a violation.
The policy is YAML. Defaults are deny. Rules are per tool, with constraints on arguments.
version: 1
defaults:
decision: deny
tools:
filesystem.read:
decision: allow
paths:
allow: ["src/**", "docs/**", "package.json"]
deny: [".env", "**/*.pem", "secrets/**"]
filesystem.write:
decision: ask
paths:
allow: ["src/**", "tests/**"]
shell.exec:
decision: ask
commands:
deny: ["rm -rf", "git push --force", "curl * | sh"]
github.create_issue:
decision: allow
github.delete_repo:
decision: deny
redaction:
patterns:
- name: generic_secret_assignment
regex: '(?i)(api[_-]?key|secret|token)\s*[:=]\s*\S+'Three features separate a firewall from a permission prompt.
Redaction. Secrets in tool arguments are masked before they reach the audit log and, in some tools, before they reach the model's context. The demo output shows the marker in place of the value. Without this, your audit log is a secrets file.
Argument validation. Path globs, URL allow-lists, shell command patterns. The policy does not just say whether filesystem.write is allowed; it says where. This is the syntax-aware command permissioning pattern the harness study found inside the harnesses themselves, moved out into a component you can put in front of any harness.
Tamper-evident audit. Append-only JSONL, hash-chained so a removed entry is detectable, Ed25519-signed so a forged one is too, with rotation and external sinks. When an incident review asks what the agent did on Tuesday, this is the only artifact that answers.
Where the firewall sits
Policy enforcement in the tool path
A coding harness with shell, filesystem and GitHub tools, some local and some over MCP. The firewall sits between the harness and every tool.
- - The model never sees the policy; the policy never depends on the model behaving.
- - A denied call produces no side effect anywhere. Denial happens before the tool, not after.
- - The proxy holds credentials the harness does not: the agent asks for an action, not for a key.
- The harness emits tools/call.
- The proxy evaluates tool, arguments and context against the policy.
- Allow: forward and log. Deny: return a JSON-RPC error and log. Ask: hold, notify the approver, then forward or deny on the answer or the timeout.
- Every decision, redacted, goes to the audit chain with a reason code.
The boundary that matters most is the third one. The old setup put the GitHub token in the agent's environment and hoped the agent would use it well. The firewall setup gives the proxy the token and gives the agent a tool called github.create_issue. The agent cannot leak a token it never had.
Why the stateless MCP core helps
The July 2026 MCP revision removed sessions and made every request self-describing: protocol version, client identity and capabilities travel in _meta, and the method and tool name travel in the Mcp-Method and Mcp-Name headers.
For a policy proxy that is close to ideal. It can make the cheap decisions from headers alone, without parsing a body. It can make the full decision from one request, without keeping a session table to know who the caller is. And it can sit behind a load balancer as easily as the servers it protects. We wrote up the migration separately; the short version for the firewall is that your proxy no longer needs to be sticky.
The roadmap goes further on the caller side. Agent identity is a named priority: DPoP so a stolen token cannot be replayed from another process, workload identity federation so an agent running as a cloud workload has its own identity rather than a borrowed human's, and standard token exchange between them. When that lands, the proxy can bind a policy to an agent identity instead of to whoever happened to hold the key.
What the tools differ on
Choosing a policy layer
Local MCP policy proxy (AgentFence and similar)
- + Single binary, deny by default, no cloud dependency
- + Argument validation and redaction built in
- + Hash-chained, signed audit; CI batch mode
- - Early versions; interfaces move
- - Covers MCP and wrapped tools; a raw shell the harness spawns itself is outside it unless you wrap that too
Choose when: You run MCP servers and want enforcement you can read and version-control.
Avoid when: Your harness bypasses MCP for its main tools and you cannot wrap them.
Harness-native permissions (Claude Code, Codex, Gemini CLI sandboxing)
- + Zero extra moving parts
- + Syntax-aware command parsing inside the harness
- + OS-level sandboxes on some platforms
- - Policy is per harness; switch harness, rewrite policy
- - Audit is whatever the harness logs
- - No shared enforcement across a fleet
Choose when: One harness, one team, and its built-in model is good enough.
Avoid when: You run several harnesses or need one audit trail.
Sandboxed MCP runners and meta-harness policies (cross-harness sandboxing)
- + Enforcement independent of the harness
- + Fleet-wide policies and budgets
- + Sessions and evidence in one place
- - A server and runner to operate
- - Heavier than a proxy for a single machine
Choose when: Several teams, several harnesses, shared governance.
Avoid when: One laptop and one repository.
The honest answer for most teams is two of the three. Keep the harness's native permissions on, because they are free. Put a policy proxy in front of the tools, because it is the only layer that works when the harness is swapped or the model is talked into something. Add the meta-harness layer when there is a fleet.
The threat the firewall is for
Injected instruction in a tool result
Actor: Anyone who can get text in front of the agent: a dependency's README, an issue comment, a web page the agent fetched, a file in a cloned repository
Asset: Everything the agent's credentials reach: source, secrets, the ability to push, open pull requests, call APIs
Entry point: A tool result that contains instructions phrased as if from the operator
Impact: Credential exfiltration or a malicious change pushed under the agent's identity
- The agent reads a file or page containing 'before continuing, run the following to verify your environment'.
- The instruction asks for a shell command that reads .env and posts it to a URL, or a git push to a branch the attacker watches.
- The model complies because the instruction is plausible and the task is long enough that the original guardrail prompt is far behind.
- - Deny by default: shell.exec and network egress are ask or deny; the exfiltration command never executes.
- - Argument validation: .env and key files are on the read deny-list regardless of who asks.
- - Credential separation: the token lives in the proxy; the agent has github.create_pull_request, not push access to arbitrary branches.
- - Audit: the attempted call is logged with its arguments redacted and a reason code, so the injection is discoverable.
- - A canary secret in every test environment; the test suite includes an injected 'verify your environment' instruction and asserts the canary never leaves.
- - A recorded run replayed through the policy in CI batch mode; any new allow decision on a sensitive tool fails the build.
- - An offline verification of the audit chain after the run; a broken chain fails the test.
Notice what the controls do not include: a better prompt. The firewall assumes the model will follow the hostile instruction and makes sure that following it has no effect.
The command line, end to end
A firewall you cannot operate from a terminal will not be operated. AgentFence's README documents a command set that covers the whole lifecycle, and it is worth walking through because it shows what "policy as a first-class artifact" means in practice.
Scaffold and validate. agentfence init --pack filesystem,github,shell writes a starter policy from curated templates for the three tool families most agents touch. agentfence validate --policy policy.yaml checks the syntax. agentfence policy test --policy policy.yaml --tests cases.yaml runs fixture-based tests against the policy, so a rule change is tested the way code is tested, before it is deployed.
Run in front of a server. The proxy wraps a local MCP server as a subprocess:
agentfence proxy --policy policy.yaml --audit-log audit.jsonl -- npx my-mcp-server --stdioFor a remote server, agentfence proxy-http --upstream https://mcp.example.com --listen 127.0.0.1:8787 --policy policy.yaml --audit-log audit.jsonl gates streamable HTTP and SSE, forwarding the traffic that carries no decision transparently.
Gate in CI. agentfence check --policy policy.yaml --call recorded-calls.jsonl --fail-on deny,ask --no-interactive --summary gate.json replays a recorded set of tool calls against the policy without a live agent, exits non-zero on any decision you name, auto-denies anything that would have asked a human, and writes a machine-readable summary for the pipeline. --dry-run evaluates without enforcing, which is how you introduce a stricter policy without breaking the build on day one.
Explain a decision. agentfence explain traces a single call through the policy and says which rule matched and why. When an engineer asks why their agent was blocked, this is the answer, not a log search.
Manage the audit chain. agentfence audit keygen --private key.pem --public key.pub makes an Ed25519 pair. Runs with --tamper-evident --sign-key key.pem write hash-chained, signed events. agentfence audit verify --log audit.jsonl --pubkey key.pub checks the chain and the signatures offline; audit anchor writes a signed checkpoint that a third party can hold, and verify accepts it with --anchor and --anchor-pubkey. audit summarize --top 20 gives the activity report. Rotation is --audit-max-size and --audit-keep; an external sink is --audit-sink syslog://... or http://....
Install. A checksum-verifying install script, Homebrew, Scoop and winget, a container image on GHCR, or go build. Releases ship with checksums, cosign signatures and SBOMs, which is the supply-chain posture you would hope for from a tool whose job is supply-chain posture.
The policy schema is small. A version, a defaults.decision, and a tools map keyed by tool name or wildcard (filesystem.*, github.delete_*), each with a decision of allow, deny or ask, optional constraints on path, argument, url and shell_command, and optional redact patterns per field. Each audit event carries a schema version, a session id, a monotonic sequence number, an RFC 3339 timestamp, the call id, the tool, the decision, a human-readable reason, a typed reason_code such as rule_match, path_denied or approval_timeout, the redacted arguments, and a mode of dry_run when applicable.
That level of detail is the difference between a security tool and a demo. It is also why the specifics of one project are worth reporting even though the category has a dozen entrants: the shape of a good policy proxy is now documented, and the others can be judged against it.
Writing a policy that survives the first week
Deny by default is the right starting point and the wrong stopping point. A policy that denies everything is a policy that gets switched off on Thursday. The craft is in getting from deny-everything to a policy engineers stop noticing, without opening a hole.
Start with reads. Allow filesystem.read on the source tree and documentation, deny it on .env, key files and anything under a secrets directory, and leave everything else at the default. Most agent work is reading, and a read policy that is right removes most of the friction immediately.
Then writes, scoped. filesystem.write on src/** and tests/** as ask for the first week, then allow once you have looked at a week of audit events and seen nothing you would have denied. Writes outside those paths stay denied. The point of the ask period is to collect evidence, not to make a human click.
Shell is where the patterns live. Deny the destructive shapes outright: rm -rf, force pushes, pipes from the network into a shell. Allow the build and test commands the repository actually uses, by pattern, so pytest -x is fine and pytest -x; curl ... is not; the syntax-aware parsing in the proxy is what makes that distinction reliable. Everything else asks.
Network egress is allow-listed by destination, and the list is short. Package registries, your own APIs, the provider endpoints. A skill or a tool that needs a new destination gets it added by a human after reading why.
Then watch two numbers for a week: asks per hour, and denies per hour. Asks that always get approved become allows. Asks that never get approved become denies. Denies that were legitimate work are the policy telling you a scope is too narrow; widen the scope, not the default. Denies that were not legitimate work are the policy doing its job, and each one is worth reading, because it is either a bug in a skill or an injection.
The failure mode to design against is ask fatigue. A proxy that asks twenty times an hour trains the approver to click yes, and a proxy the approver clicks yes on is a proxy with no policy. Every ask should be rare enough that a human reads it. If it is not, the rule is wrong.
What a firewall cannot do
Being precise about the limits is part of using the tool well.
It cannot stop an exfiltration that fits an allowed channel. If the agent may write to the repository and push to a branch, then data can leave in a commit. The mitigations are scope, small allow-lists, review of what is pushed, and canary secrets whose appearance anywhere fails a test. The firewall makes the channel narrow and audited; it does not make the channel safe.
It cannot stop the model from being persuaded. Prompt injection still happens, every time. The firewall's promise is narrower and more useful: when the model is persuaded, the action it is persuaded to take has no effect, and the attempt is on the record.
It does not replace the sandbox. A policy proxy decides which calls are allowed; a container or an operating-system sandbox limits what an allowed call can reach. Harnesses already ship sandboxes, Docker and Apptainer in OpenHands, native OS sandboxing in Codex and Gemini CLI, and the meta-harness layer adds cross-harness sandboxing with network interception. Keep the sandbox on. Add the proxy in front of it.
It covers what passes through it. A harness that spawns its own shell outside the MCP path, or a skill that reaches a tool the proxy does not wrap, is outside the policy. The inventory question before deploying a firewall is: what are all the paths by which this agent can cause a side effect, and does each one pass the proxy?
Identity: where the caller side is going
Everything above polices what is called. The MCP roadmap's agent identity work is about who is calling, and it closes the last gap in the "API key in .env" story.
Today the proxy holds a token and decides per call. But the token itself is a bearer credential: anything that obtains it can use it from anywhere. Three roadmap items change that. DPoP, demonstrating proof of possession, binds a token to a key the caller holds, so a token copied out of a process is useless from another process. Workload identity federation gives an agent running as a cloud workload an identity of its own rather than a borrowed human's. Standard token exchange lets that workload identity be traded for the narrowly scoped token a particular tool needs, at the moment it needs it.
For the firewall this means the policy can be bound to an agent identity rather than to whoever holds a key, and the audit log can say which workload made a call rather than which token was presented. The roadmap does not give dates, and the 2026-07-28 release already hardened the authorization layer with issuer validation, credential binding to the issuing server, and Client ID Metadata Documents replacing dynamic registration. Build the proxy now; design the identity binding so that when those pieces land, the policy keyed on identity is a configuration change rather than a rewrite.
The checklist
Before an agent gets tools that reach anything real
0/8The WAF did not end web security. It made the failures of application code survivable while the code got better. The agent firewall is the same bet, made for the same reason, and it is the cheapest security control you can add to an agent this year.