Est.

Egress Firewall Rules for Sandboxed LLM Tool Calls

LLM agents need egress rules designed for runtime attacks, not network perimeters.

· 10 min read · Updated
Cover illustration for “Egress Firewall Rules for Sandboxed LLM Tool Calls”
Running AI-Generated Code Safely · August 15, 2026 · 10 min read · 2,292 words

Egress firewall rules for sandboxed LLM tool calls govern which outside hosts an autonomous agent can reach, and they need a different design discipline than conventional network perimeter rules do. The threat comes from content the agent reads at runtime, not from a known actor making a request, so the rules have to be built around that fact rather than borrowed from older firewall doctrine.

Egress control for LLM tool calls as a distinct engineering problem

A perimeter firewall assumes a mostly human, mostly known actor is on the other end of each request. An LLM tool call sandbox hosts something else entirely: an agent whose next outbound action can be shaped by a prompt injection buried in a web page, a poisoned tool description, or any other untrusted content it happens to read mid-task. That distinction is not cosmetic. It changes what the firewall has to anticipate, because the attacker never touches the network directly. They touch the agent's input, and the agent carries the attack out through its own legitimate permissions.

Egress rules in this context have to cover three concrete risks. The third is lateral movement, where the agent uses credentials it legitimately inherited to reach internal systems it was never supposed to touch.

Johann Rehberger's ZombAIs demonstration shows how fast this chain can run without any sandbox in place. Claude, mid-browsing-task, hit a malicious page containing an embedded prompt injection, downloaded a binary, ran chmod +x on it, executed it, and connected to a C2 server, on the first attempt. Nothing about that sequence required breaking out of a container or exploiting a kernel bug, since the agent did what a browsing agent does: read a page and acted on instructions found there, instructions that happened to come from an attacker rather than a user.

Cosmonic frames the underlying failure as a confused deputy problem: the LLM is a trusted process, and its legitimate permissions can be weaponized by anyone able to plant instructions into its context. The compromise lives entirely in what it was told to do with them.

SpecStory calls the resulting situation the "lethal trifecta": an agent with access to private data, exposure to untrusted content, and the ability to make an outbound HTTP request has everything needed for exfiltration, regardless of how carefully its internal logic was written. Egress control is the one lever in that trifecta that can be cut without asking the agent to somehow recognize manipulation it was never designed to detect. Block the outbound request, and the data has nowhere to go even if the agent has been fully turned. This piece works through how to build rules that assume the agent won't be trustworthy, not how to make the agent trustworthy.

Diagram: The 'Lethal Trifecta': Three Conditions for Exfiltration. Visualizes: Illustrate the three-part condition SpecStory calls the 'lethal trifecta': an agent with (1) access to private data, (2) exposure to untrusted content, and (3) the…

How the isolation layer under egress rules determines how much those rules can enforce

An egress rule is a promise, and the isolation boundary underneath it decides whether that promise can be broken. If the process running the agent's code can reach the kernel or the host network stack directly, a proxy-level allowlist is advisory at best. The isolation technology is part of egress design, a decision made alongside the rules themselves rather than separate infrastructure settled beforehand.

Three approaches dominate current practice, each with a different enforcement guarantee. MicroVMs, Firecracker being the reference implementation, give each workload its own kernel running on hardware virtualization through KVM, so a kernel exploit inside one VM cannot reach the host or any other VM. That isolation comes at the cost of a cold start heavy enough to matter in latency-sensitive workloads. Browser isolates offer a different trade-off, with startup under 50 milliseconds per the sourced figures but narrower language support.

The practical consequence follows directly from where enforcement sits. The rule and the boundary it depends on need to be evaluated together, not separately.

Firecrawl and SpecStory both describe per-sandbox egress allowlists as standard practice for untrusted code, and neither insists that enforcement happen specifically at the hypervisor layer rather than the proxy or application layer. A team that adopts an allowlist without also asking what isolation technology enforces it is solving only half the problem.

The deny-by-default allowlist as the foundational egress posture

Every sandboxed LLM tool call should begin with no outbound access at all, earning only the specific hosts its task demonstrably requires. Any looser starting point hands the agent's own runtime behavior, which an attacker can influence, the power to widen its own attack surface.

NIST's firewall guidance recommends blocking any traffic a policy does not expressly permit, a posture generally known as deny by default. That guidance was written for network perimeters generally, but it applies with particular force to agent sandboxes, because the agent is running code nobody on the team has read and acting on text written by someone else entirely. A deny-by-default posture does not require anticipating every malicious destination in advance. It only requires enumerating the legitimate ones, which is a far shorter and more tractable list.

Mechanically, this looks like a sandbox with no direct route to the internet. An agent writing a Python script has no legitimate reason to reach an unknown IP address over port 443, so the allowlist is scoped to the specific APIs the declared task actually needs: a package registry, a code host, the model API endpoint itself, and little else.

Some hosted coding agents adopt a staged version of this posture, described by SpecStory: network access is permitted during a setup phase, when install scripts run and dependencies get pulled down, then blocked once the agent begins its actual work. That staging reflects a real operational fact rather than a compromise on principle. Dependency installation genuinely needs outbound access, but that need is bounded in time, and the rule can be bounded to match it.

Deny-by-default settles the starting posture; the quality of the resulting allowlist, tight or sloppy, is a separate matter. The posture is necessary. The quality of what gets added to the list is where the actual engineering work happens, and that work is the subject of the next section.

Scoping Allowlist Entries to Tool Call Needs

An allowlist entry broader than the tool call's actual need becomes its own attack surface, which makes scoping the central design discipline in egress control rather than a cleanup step after the fact. The individual tool call is the unit worth analyzing, not the agent session as a whole, since different tools inside the same agent carry different legitimate egress needs and a single session-wide list conflates all of them into one overly generous policy.

SpecStory's worked example grounds this concretely. An agent tasked with editing a delivery address feature legitimately needs registry.npmjs.org for packages, github.com for the code host, and the model API endpoint. A request to browsers.example.com, made to fetch end-to-end test downloads, gets denied and logged, leaving the decision of whether to add it to a developer reviewing the log rather than to the agent itself. That review step matters as much as the denial. It keeps a human in the loop on every expansion of the list, rather than letting the system quietly grow permissive over time.

A well-scoped entry specifies more than just a hostname. It names the hostname itself; IP ranges shared across a CDN open every site sitting behind that CDN, not just the one intended. It specifies protocol and port, allowing HTTPS on 443 rather than leaving every port open. Where a decrypting proxy is in use, it can restrict by method: a tool that only reads data gets GET but not POST, which blocks most upload paths without touching the function the tool actually needs. Where the API surface itself is bounded, the entry can restrict by path, allowing something like /v1/completions rather than the entire domain behind it.

MCP tool calls raise a scoping problem of their own. The correct scoping target is the specific upstream a given tool legitimately calls, which is typically far narrower than the full access the MCP server could theoretically use. Treating the server's requested scope as the allowlist baseline, rather than the tool's actual need, reintroduces exactly the over-permissioning that deny-by-default was supposed to eliminate.

The PORTCIO reference monitor pattern, described by Santos-Grueiro in 2026, points toward where scoping is headed as agents take on longer, more structured tasks. The animating concern is what the pattern calls lingering authority: a capability that stays exposed after the episode that justified it has already ended, which is an egress risk in exactly the same way it is a permissions risk. A static allowlist, however well scoped at creation time, accumulates exactly this kind of lingering authority if nothing ever revokes an entry once the task that needed it is done. That is a different failure mode than being too broad at the outset, and it is one static scoping alone cannot fix.

The gaps that allowlists alone do not close

A correctly scoped, deny-by-default allowlist still leaves exploitable gaps, because proxy enforcement is blind to certain channels entirely and blind to the content of traffic it does allow. Closing those gaps takes additional controls layered on top of the allowlist.

DNS exfiltration is the clearest example. The fix is to restrict the sandbox's resolver to one that refuses to look up names outside the allowlist in the first place, closing the channel the proxy can't inspect.

Uploads to hosts that are already allowed present a subtler version of the same problem. Method restriction at a decrypting proxy, GET allowed and POST blocked, closes most uploads, but query strings in GET requests can still carry data out.

Research on policy enforcement reports denial failure rates as high as 98 percent across real-world denylists in 2026. The sandbox therefore also needs to drop all outbound packets at the network layer itself, so a direct connection fails rather than quietly succeeding around the proxy.

A further gap sits at the level of timing rather than channel. A sandbox that correctly enforces egress rules at the moment it checks policy can still be exploited if the resource it checked differs from the resource it ultimately uses, a time-of-check to time-of-use gap that the research brief identifies as still underexplored. Part of the reason is institutional as much as technical: the isolation research community, the access control community, and the TOCTOU research community are not yet talking to each other with much consistency, so a gap visible from one angle rarely gets addressed with the tools built in another.

None of these gaps argue against building the allowlist well. They argue that the allowlist, however well scoped, is one layer in a stack, and each gap above points at content the proxy structurally cannot see, whether that's the payload inside a DNS query, the payload inside a query string, or a connection that bypasses the proxy altogether. The last of these, the inability to see inside allowed traffic for semantic intent, carries over directly into the next layer of control.

Adding a semantic enforcement layer above the network rules

Network egress rules can stop an outbound connection, but they have no way to inspect the intent encoded in the tokens that triggered that connection. A semantic enforcement layer fills exactly that gap, intercepting risky flows before they ever reach a privileged sink, rather than only at the moment they try to leave the network.

The mcp-jira-sync incident from March 2026 shows why this layer matters. A malicious MCP server's tool description carried a hidden instruction that caused connected agents to append the contents of local AWS credentials to every API call sent back to the attacker's server. By the time an egress rule had any decision to make, the compromise had already been encoded into the agent's next action. No hostname restriction, however tightly scoped, inspects the tool description the agent reads to decide what to send.

TokenWall, described in a paper at arXiv:2607.08395, proposes treating security enforcement as a semantic firewall operating over an agent's token flows directly, which is to say memory writes, tool arguments, retrieved files, and messages passed between components. It constructs audit records tracing each flow from its source to its eventual sink and applies inspection before that flow crosses a persistent or external boundary. That gives it a kind of visibility network rules cannot match. It can flag a tool argument that encodes credentials inside a base64 string headed to a host the allowlist already permits. It can catch a memory write that quietly propagates a malicious instruction forward into tool calls the agent hasn't made yet. Both operate at the moment of semantic transfer, before any state has actually changed.

That tiering keeps overhead low on the overwhelming majority of benign traffic while reserving the more expensive inspection for the cases that actually warrant it, at the cost of some modest added latency even on the benign path.

Cosmonic takes a different route to a related end, arguing that a tool stating what it needs directly in its interface definition, with the platform enforcing that declared boundary, reduces misconfiguration by removing the need for explicit firewall rules in the first place. Firecrawl and SpecStory push back on this as a complete answer, arguing that declarative boundaries defined at the application layer aren't sufficient on their own for code that is genuinely untrusted. Both positions can be right at once, because they're addressing different parts of the same problem rather than competing for the same ground. A declarative interface can reduce the odds that a legitimate tool gets misconfigured into asking for too much. It does nothing to stop an attacker's hidden instruction from directing a legitimately-scoped tool to do something it was never meant to do, which is precisely the gap semantic inspection is built to close.

Sources

  1. What is egress control?
  2. Token-Flow Firewall: Semantic Runtime Auditing for Persistent AI Agents
  3. Lingering Authority: Revocable Resource-and-Effect Capabilities for Coding Agents
  4. The Balkanization of Execution-Security Research for AI Coding Agents: Isolation, Access Control, and Time-of-Check-to-Time-of-Use Vulnerabilities

More in Running AI-Generated Code Safely