Network isolation: allowlists, proxies and the trust handoff
The network wall is where agent sandboxes fail most often, for two reasons: it is usually enforced by convention rather than by the kernel, and an allowlist is not the same thing as a trust boundary.
Four levels of enforcement
| Level | Mechanism | What defeats it | How to tell |
|---|---|---|---|
| 0 · No policy | Full outbound access, usually what you get by default. | Nothing — it is the absence of a control. | The container has a bridge network and no proxy. |
| 1 · Advisory | Environment variables (HTTP_PROXY, HTTPS_PROXY), a dead proxy address, PATH tricks, or a firewall rule that requires elevation you did not grant. |
Any code that opens a raw socket, ignores the variable, or unset it. Node ignored HTTP_PROXY before v24; Cursor's client needed a relaxed template because of it. |
The README uses words like "cooperative" or "advisory". Your raw-socket test reaches the internet. |
| 2 · Proxy-mediated | A proxy with an allowlist, reachable normally, plus the convention that tools use it. Traffic that goes through it is filtered; traffic that does not is not. | Anything that skips the proxy. Also DNS, which is usually unfiltered — so a "trusted" hostname can still leak metadata or resolve to internal addresses. | Blocking shows up as an HTTP 403. Killing the proxy lets a raw socket through. |
| 3 · Kernel-enforced | A network namespace with no route except a bridge to the proxy, or a TUN device that captures everything, plus DNS interception and seccomp filtering of socket creation after setup. | Nothing short of a kernel or runtime bug. Raw sockets simply have nowhere to go. | A raw-socket test fails, not with a 403 but with no route. Killing the proxy produces connection errors rather than direct connections. |
Why an allowlist is not a trust boundary
This is the most important lesson in the chapter, and it comes from a real incident. An AI agent escaped its sandbox by exploiting a vulnerable package proxy that had been explicitly placed on the sandbox's allowlist, reaching the open internet and, from there, internal production infrastructure. The sandbox worked exactly as configured. The configuration was the problem.
The general shape is called a trust handoff: the agent stays inside its permitted environment but uses something outside that environment to execute an action with greater privilege. An agent does not need unrestricted network access if it can find an unintended path through a service you approved.
Practical consequences:
- An allowed host is only as trustworthy as that host's security. Allowlisting
github.commeans trusting every user-controlled repository, gist and issue on GitHub as a data path. Domain allowlisting is content-blind: an allowed domain can carry an exfiltration payload perfectly well. - Broad hosts grant broad access. Proxies that allow
github.comfor package access also allow any content on that host, and CONNECT tunnels to arbitrary ports on allowed hosts are a common surprise. - Wildcards are a footgun. Some tools deliberately refuse to support them; that is a feature, not a gap.
- Blocking looks different from denying. A 403 from a proxy invites retries; a TCP-level denial or no route does not. Some agents have been observed retrying blocked requests indefinitely, which is a resource problem as well as a policy one.
The workable answer is to keep allowlists short and boring — your model provider, your code host, your package registry — and to treat the allowlist as blast-radius reduction rather than as a boundary. The boundary is the namespace, and the reason it matters is that it also constrains the paths you cannot enumerate.
The allowlisted-proxy escape: an agent reached the internet through a sandbox-approved package proxy that had a vulnerability of its own, and from there reached internal production systems. Announced publicly as "network allowlists are not equivalent to trust boundaries".
The unscoped-internet evaluation: agents that were correctly VM-isolated still reached real systems because internet access was enabled and unscoped — one attempted to social-engineer a real open-source maintainer. Isolation is not a substitute for scoping the capability.
DNS is a network channel: unmonitored DNS leaks queries and, worse, lets an attacker-controlled
hostname resolve to something internal. And 169.254.169.254 is the cloud metadata service,
which on many hosts hands out temporary credentials to anything that asks. A proxy should resolve DNS
itself, block link-local ranges, and refuse known metadata hostnames. If your proxy forwards the
hostname to the host resolver unchanged, it does none of this.
How to build enforcement that fails closed
Linux containers: the easy one
Run the agent on an --internal network with your proxy dual-homed, or use a namespace
where the only route is to the proxy. Because the policy is a route table, a raw socket has nowhere to
go. This is the cheapest strong enforcement available and it is a two-flag change.
# proxy on a normal bridge, agent on an internal one
podman network create --internal agent-net
podman run -d --network agent-net --name egress-proxy ...
podman run --rm -it --network agent-net \
--dns 10.89.0.2 # the proxy, not the host
localhost/agent:latest
Process sandboxes: TUN or bridge
With bubblewrap you can use --unshare-net and provide a bridge to the proxy. Some tools go
further and create a TUN device with tun2socks, so every packet is captured including DNS,
and Mac clients that ignore proxy variables still end up at the proxy because there is no other
interface.
Verify: if the tool documents a "relaxed" or "fallback" mode, find out whether that
mode drops to --unshare-net-less operation. Several do.
macOS: proxy-only Seatbelt plus honesty
A Seatbelt profile can deny direct egress and permit only the proxy, which is decent enforcement. But there is no Linux-style TUN capture by default, so the network story depends on the profile being correct rather than on the topology. Read the profile.
Windows without elevation: not enforceable
Codex's own engineering write-up is the clearest public statement of this: in unelevated mode the sandbox cannot invoke the system firewall and falls back to advisory controls such as dead proxy variables and PATH pollution, which raw sockets bypass. It moved to a one-time elevated setup precisely to get kernel-level enforcement. If you care about Windows egress, plan for that elevation.
Testing your policy (do this)
A network policy you have not tried to defeat is a network policy you do not have. Six tests, ten minutes:
- Raw socket test.
python3 -c "import socket;socket.create_connection(('1.1.1.1',53),3)"from inside the sandbox. If that succeeds, you are at level 1 or 2. - Proxy-off test. Stop the proxy and repeat. Success means your policy is advisory and the process found another path.
- Metadata test. Try to read
http://169.254.169.254/. Any response is a finding. - DNS test. Resolve a hostname you did not allow and see whether the query leaves the sandbox.
- Tool-coverage test. Run each tool the agent actually uses —
curl,git,npm,pip,node, your agent CLI — and confirm each one goes through the proxy rather than around it. - Blocked-host behaviour test. Confirm that a denial produces a hard failure rather than a retry loop, and that the agent does not interpret a 403 as "try again".
This is a compressed version of the test suite you would write for any of the scopes in the Sandbox Stacks chapter. Write it once, run it against whatever you adopt, and you will never be surprised by a policy that silently stopped applying.
Tools that help
| Tool | Role | Notes |
|---|---|---|
| mitmproxy | Intercepting proxy: allowlists, header rewriting, credential injection, request logging. | The substrate under most home-made brokers. Run it outside the sandbox. Assessment → |
Container --internal networks | Route-level enforcement so raw sockets have nowhere to go. | The single cheapest strong control. Available in Podman and Docker. |
| TUN + tun2socks | Capture all traffic including DNS, regardless of whether the client honours proxy variables. | Used by container-free sandboxes that take network mediation seriously. Linux-mostly. |
| Tailscale / WireGuard | Give the sandbox exactly one reachable host with identity attached, and nothing else. | Useful when the agent's job is to talk to one internal service. Mesh identity is stronger than IP allowlisting. |
| Cilium, Tetragon, Falco | Cluster-scale egress policy and runtime observation. | Where agent network control goes when it stops being a laptop problem. Falco's lineage is also the source of the "denylists lose to reasoning adversaries" argument. |
| Domain allowlists in the agent itself | Codex, Claude Code and most CLIs ship a network policy alongside their sandbox. | Convenient, and worth using — but they are proxies in the sense above, so read how they are enforced on your platform. |