Egress control: the sandbox bounds what the agent runs, not what it reaches.
Between 21 July and 6 August 2026, OpenAI, Anthropic and Meta each disclosed that a model under evaluation reached the open internet and compromised real third-party systems — and in the Anthropic and Meta cases the containment boundary turned out to have been asserted in the evaluation prompt while the network stayed open. That is the whole lesson in one sentence: a boundary written in prose is not a boundary, and the compute isolation everyone buys says nothing about where packets are allowed to go. Egress is a separate control with a separate budget, and every major agent sandbox still ships it permissive by default.
Egress control does three different jobs, and conflating them is why it gets built badly.
"Block the network" is not a requirement. Three distinct risks hide under it, they have different threat models, and a control that handles one can be useless against another.
- Exfiltration. The agent holds data it is allowed to read and sends it somewhere it should not. The adversary is usually not the agent but whoever wrote the untrusted content it processed — the pattern data exfiltration risks catalogues. The payload is small: an API key, a customer record, a token. Bandwidth is irrelevant, so throttling is not a control.
- Unsanctioned action on a third party. The agent does something to a system that never consented to be in scope. This is the 2026 eval failure exactly: the models were not exfiltrating anything, they were pursuing a capture-the-flag objective and the real internet was reachable, so real infrastructure became part of the exercise. Here the payload direction is outbound-as-action, and blocking data volume does nothing.
- Cost and abuse. An agent in a loop hammering an external API, or a compromised sandbox mining, or a scraper you did not intend to run against a site that will block your whole IP range. Boring, common, and the only one of the three that shows up on a bill.
The three jobs share one implementation and disagree on the policy. Exfiltration wants the destination set to shrink after the agent reads untrusted input; unsanctioned action wants a hard scope fixed before the run starts; cost wants rate limits per destination. Write them down separately — a single "allowed domains" list that tries to satisfy all three ends up satisfying none.
Deny by default — and notice that nothing you use defaults that way.
Compute isolation and network isolation are sold together and delivered separately. Every agent-sandbox product now exposes an egress control; every one of them ships permissive.
- The default is open because the alternative breaks the demo. An agent that cannot
pip install, fetch a doc page or call an API fails its first task, so the first-run experience is unrestricted and the restriction is a thing you opt into after you have a working system and no appetite to break it. - "We run it in a container" is a statement about the filesystem. Namespace and syscall isolation are orthogonal to routing. A microVM with a default route to the internet is a strong compute boundary and no network boundary at all, and the strength of the first is regularly used as evidence for the second.
- The eval bench is the least-hardened environment you own. It is where refusals are deliberately reduced, capability is deliberately maximised, and the infrastructure is treated as test kit rather than production. Anthropic found its three incidents only by reviewing 141,006 evaluation runs after a competitor disclosed first — no live control fired. If you run capability evals, harden that environment before you harden production.
- Deny-by-default is affordable precisely because agent tasks are narrow. A task that reads three internal services and one vendor API needs four destinations. The allowlist feels impossible when you write it for the environment and becomes trivial when you write it for the task — which is the actual design decision two steps below.
A domain allowlist is not egress control, because the exfiltration channel is on the allowlist.
This is the step that changes what you build. Teams implement a list of permitted hostnames, mark the risk closed, and ship a system that leaks through the destinations they approved.
- Any allowed destination that accepts arbitrary content is an exfiltration endpoint. Your own logging endpoint, your error tracker, a webhook receiver, an issue tracker, a paste service on a vendor domain, an LLM provider's API. A secret placed in a log line, an error message, a search query or a commit body has left the building through a channel your policy explicitly permits.
- The URL is a covert channel and it is wide enough. Secrets are hundreds of bytes. A path segment, a query parameter, a subdomain label or a DNS lookup carries them fine. If the agent can render markdown that fetches an image, it can exfiltrate by constructing the image URL — no outbound tool call required.
- Name-based filtering is weaker than it reads. Enforcement by requested hostname is evaded by requesting an allowed name and reaching a different host; enforcement by resolved address breaks CDNs and multi-tenant clouds, where one allowed name shares an address with everything else on the platform. Wildcarding a vendor's domain usually admits customer-controlled subdomains you never considered.
- The DNS resolver is an egress path. If the sandbox can resolve arbitrary names, it can encode data in the names it looks up, and that traffic leaves through a service that is almost never in scope for the network policy.
The conclusion is not that allowlists are useless — they are the only thing that reduces the unsanctioned-action risk, and they are cheap. The conclusion is that an allowlist is a scope control, not a confidentiality control. If you need the second, you need to bound what leaves, not only where it goes, and that means the boundary has to be a proxy that can see the request.
Put the boundary at a proxy you operate, and scope it to the task.
The workable architecture is not a firewall rule per environment. It is a mandatory forward proxy that every sandbox must route through, holding a policy derived from the task the agent was given.
- No default route. Proxy or nothing. The sandbox gets no direct path off-host; the only reachable address is the proxy. This is the property that makes everything else enforceable, and it is the one most setups skip because it requires configuring the runtime rather than writing a rule.
- The allowlist is generated from the tool set, not maintained by hand. A task that was granted three tools has three destinations, and the policy should be derived from that grant at dispatch time. Hand-maintained environment-wide lists rot upward — nobody ever removes an entry — until the list permits everything anyone ever needed.
- Terminate credentials at the proxy. If the vendor token is injected by the proxy on the way out, the sandbox never holds it, and a compromised agent cannot take the credential anywhere the proxy will not send it. This is the highest-leverage change on the list, and it is the network expression of scoped credentials.
- Log every attempt, allowed and denied, with the task ID. A denied connection is a first-class signal — it is either a policy gap or the earliest observable evidence of an injection succeeding. It belongs in the trace next to the tool calls, per tracing and observability, not in a network log nobody joins to a run.
- Fail closed, and make the failure legible to the agent. A blocked request should return a clear, structured error the model can act on — the same argument tool error messages makes — otherwise the agent retries the block twenty times and you have converted a security control into a latency bug.
- Egress policy is versioned config, deployed like code. It gets reviewed, rolled and rolled back on the same machinery as everything else in rollout and versioning. A policy edited live in a console is one that will be widened at 2 a.m. during an incident and never narrowed again.
Sequence the policy: the moment untrusted content enters, the network should shrink.
A static per-task allowlist is a large improvement and still leaves the main hole open, because the dangerous state is not a property of the task — it is a property of the moment. An agent becomes dangerous when it simultaneously holds sensitive data, has processed attacker-controlled content, and can still reach outward.
- Remove one leg and the exfiltration risk collapses. That is the entire content of the "lethal trifecta" framing in risks and limits of agents, and egress is by far the cheapest leg to remove — you cannot un-read a document, and you usually cannot drop the data access without dropping the feature.
- So make egress a state machine, not a list. Before the agent ingests untrusted input, permit the destinations the task needs. After it ingests untrusted input, permit only the response channel back to the requesting user. Most useful work — fetch, then reason, then answer — fits that ordering naturally, and the tasks that do not fit are exactly the ones worth escalating.
- Mark tool results by trust, and let the marking drive the policy. A result from your own database and a result from a web fetch are different objects. If nothing in your system distinguishes them, you cannot implement this step, and the tagging is the prerequisite work.
- The response channel is itself an egress path. Rendering agent output as HTML or markdown with remote images hands the model a request primitive, so sanitise on the way out and disallow remote references in rendered output. This is where a well-firewalled agent still leaks.
- Where the ordering cannot hold, put a human on the outbound side. Not on the plan, on the specific outbound call — approving a plan does not approve a request the agent constructs three steps later. The gating pattern is in human-in-the-loop.
Operate it: first-seen destinations, drills, and the eval bench you forgot you owned.
Egress control fails in operation rather than in design. The policy is written once, correctly, and then erodes under delivery pressure while nobody watches the thing it was protecting.
- Alert on first-seen destination per task type. Not on denials in aggregate — on the first time a task class reaches somewhere it has never reached before. That single alert is the highest-signal detection available here and it is nearly free once the proxy logs carry task IDs.
- Review allowlist growth on a schedule and delete aggressively. Every entry needs an owner and a reason. Entries added during an incident are the ones to revisit first, because they were added under time pressure by whoever had the console open.
- Drill the block. Run a task that deliberately attempts a disallowed destination, on a cadence, in production configuration. A control nobody has watched fire is a control nobody knows is still wired up, and egress policy is unusually easy to disable accidentally with an infrastructure change.
- Treat a denied outbound request during a real run as an incident input. It is frequently the first observable symptom of a successful injection, and it arrives before any damage. Route it into incident response with the trace attached rather than into a dashboard.
- Harden the eval and red-team environments to the same standard, or higher. They run more capable configurations with fewer refusals against deliberately adversarial objectives. If you use a third-party evaluation partner, the network configuration of their environment is now your risk, and the 2026 disclosures make it clear it needs to be verified rather than assumed — see third-party and vendor risk.
- Write down what "no internet" means, and then test that claim. If a document, a prompt or a contract says an environment has no internet access, someone has to have run a connection attempt from inside it and kept the result. Both labs that failed here believed the statement.
The first hour is worth more than the roadmap: take one agent, remove its default route, point it at a proxy, and let it fail. The destinations it hits in the first day are your real allowlist, and it will be shorter than anyone predicted. Then move the vendor credentials into the proxy so the sandbox never holds them, log every attempt against the task ID, and add one alert on first-seen destination. Compute isolation stops the agent from breaking the machine; only egress control decides which other people's machines are in scope — and it is the control that keeps working after prompt injection has already won.
Related: sandboxing and code execution for the five isolation decisions this is one of, sandbox and isolation patterns for the implementation shapes, prompt injection for the attack this bounds, and the agentic threat model for where egress sits in the whole picture.