
Fifteen years building engineering platforms, currently focused on advanced AI infrastructure at yeet. I love turning the deeply complex topics into something everyone can understand. I relate deeply with the core yeet philosophy that you can just build things.
Last updated: October 2026
Quick answer. Partly, and the part it does not block is the part most people are worried about. In
workspace-writethe sandbox ships withnetwork_access = false, so shell commands Codex runs cannot reach the network until you set it totruein[sandbox_workspace_write]. What that boundary never covered is Codex's own traffic: its model requests, MCP server connections, web search, connector and browser activity all originate outside the command sandbox and are unaffected by the setting. So "the sandbox blocks network" is true of the agent's subprocesses and false of the agent. The only way to know which of the two you are looking at is to watch the connections rather than read the config.
I build eBPF tooling at yeet, and the question I get asked about agent sandboxes is almost always a network question wearing a filesystem question's clothes. People ask whether the sandbox is on. What they want to know is what left the machine.
For the commands it runs, yes. For itself, no. Those two halves are governed by different mechanisms and only one of them is what the config setting touches, which is why reading the config is not enough to answer the question.
The workspace-write mode disables network for sandboxed commands, and you turn it on explicitly:
[sandbox_workspace_write]
network_access = true
Underneath, the Linux sandbox helper implements that with a network namespace. From the sandbox crate's own README: "When bubblewrap is active and network is restricted without proxy routing, the helper also isolates the network namespace via --unshare-net." A command running in that namespace has no route out, which is a kernel-enforced boundary rather than a policy the command could ignore. That mechanism is bubblewrap, the same tool Codex uses for its filesystem boundary.
What that namespace contains is the subprocess tree Codex spawns when it runs a shell command. Codex itself is the parent, running outside the sandbox, and it has to be: it needs to reach the model API to function at all. Every request it makes on its own behalf, and every MCP server it talks to, happens in the parent's network context.
This is the distinction that makes the question hard to answer from configuration. network_access = false is a real control with a real mechanism, and it governs a narrower set of traffic than the phrase "the sandbox blocks network" suggests to most readers.
Because the traffic you are seeing is almost certainly the agent's own rather than its subprocesses'. The sandbox is working exactly as documented, and the connections are coming from a process it was never scoped to cover.
When someone watches an agent host and finds outbound connections with the sandbox enabled, the usual cause is one of four things, none of which the sandbox was ever covering:
The practical consequence is that an agent host with a correctly configured sandbox still has legitimate outbound traffic, and there is no setting that reduces it to zero while leaving the agent functional. The question therefore shifts from "is network blocked" to "which of these connections do I expect", which is a measurement question rather than a configuration one.
Watch the connections at the kernel, where the sandbox boundary stops mattering because every socket is visible regardless of which namespace opened it. That is the one vantage point that sees both sides of the split at once, which is what makes the answer legible.
A TCP connection leaves a process whether that process is the agent, an MCP server or a sandboxed subprocess. Reading them at the point they are established gives you the remote host, the port and the PID that opened it, with no dependence on the agent's own reporting:
curl -fsSL https://yeet.cx | sh # install yeet, once
yeet run gh:yeet-src/dialout # every outbound connection, with the process
dialout is the script that does it, and the same approach applied to any process covers the general case rather than the agent one.
Two things make this the right instrument for the question. It sees the parent and the sandboxed children with the same mechanism, so you can tell which side of the boundary a connection came from rather than inferring it. And it does not rely on the agent's transcript, which records what the agent believes it did rather than what the process actually opened.
For the names rather than the addresses, SNI is the field that identifies a destination even when the connection is TLS:
yeet run gh:yeet-src/agentwire # SNI names per process
One measured caveat worth knowing before you plan around DNS: on a glibc host, name resolution usually goes through NSS over a Unix socket rather than the wire, so a DNS-based tap sees far less than you would expect. agentwire reads SNI instead, which on a glibc host is the only one of the two that reliably names a destination. SNI names close to everything; DNS fires close to never. They are not co-equal sources.
At the proxy layer for sandboxed commands, and at the host layer for everything else. That split decides where your control has to live, and writing policy at the wrong layer produces a configuration that looks thorough and governs almost nothing.
Codex supports constraining command traffic to a configured policy once network access is enabled, with a domains allow list or deny list. That covers the subprocess tree. It does not cover the agent's own calls, so an allowlist expressed there is not an answer to "what can this agent reach".
For the agent's own traffic, the control is an outer boundary: a network namespace with a default-deny firewall, or egress rules on the host, wrapping the Codex process itself rather than the commands it runs. Denying link-local, loopback and private ranges first is the tractable version, because those are where a successful request is immediately expensive. That is a different piece of infrastructure and it is the one that holds when the concern is an MCP server or a connector.
Before writing either policy, it is worth knowing what the agent currently reaches, because an allowlist written from guesswork either breaks the agent or permits more than intended. A capture over a normal working session gives you the actual set, and the difference between that set and your assumptions is usually the interesting part.
Not on its own, and the reason is structural rather than a gap in the implementation: exfiltration never needs the sandboxed path in the first place.
A prompt injection that convinces the agent to send data somewhere uses the agent's own tools, which means an MCP server, a connector or a web request made from the parent process. All of those are outside the command sandbox, so network_access = false does not stand between an injected instruction and the network.
What the filesystem side of the sandbox does usefully is bound what a command can read, which the layer-by-layer comparison of confinement options covers in detail. What it does not do is bound what the model decides to do with what it has already read into its context. Those are different boundaries, and conflating them produces the common and wrong conclusion that a sandboxed agent cannot leak.
The control that matches this threat is egress policy around the agent process plus a record of what it connected to, so that an unexpected destination is visible after the fact. Confinement reduces what can be reached; observation is what tells you whether something was.
Test it rather than trust the config, because this sandbox has a documented way of failing quietly enough to look exactly like success. A config file saying the right thing is not evidence that the mechanism engaged.
The documented failure worth knowing is that in a hardened container, where unprivileged user namespaces are unavailable, the sandbox can fail in a way that leaves codex exec exiting 0 having run nothing. An exit code of 0 and no output reads as success at a glance.
A direct check is to have a sandboxed command attempt a connection and confirm it fails:
codex exec "run: curl -sS -m 5 https://example.com -o /dev/null && echo REACHED || echo BLOCKED"
BLOCKED tells you the namespace is in place for commands. It tells you nothing about the agent's own traffic, which is why the kernel-side capture above is the complement rather than a duplicate: one proves the boundary exists, the other shows what crosses it elsewhere.
The Codex sandbox does block network for the commands it runs, by default, with a real kernel mechanism. It never covered the agent's own model requests, MCP connections, web search or connector calls, and no mode setting extends it to them. When you find outbound traffic on an agent host, the useful first question is which process opened the socket, because that single fact tells you whether you are looking at a sandbox failure or at traffic the sandbox was never scoped to.
Not by default. workspace-write ships with network disabled for the commands Codex runs, and you enable it explicitly by setting network_access = true under [sandbox_workspace_write] in config.toml. That setting governs the subprocess tree only, so turning it off does not stop Codex itself from reaching the model API, an MCP server or a connector.
With a network namespace rather than a firewall rule. The sandbox helper passes --unshare-net to bubblewrap when network is restricted without proxy routing, so a sandboxed command is placed in a namespace with no route out at all. It is kernel-enforced, which means a command cannot opt out of it the way it could ignore an environment variable or a proxy setting.
No. MCP connections are opened by the Codex process itself rather than by a sandboxed command, so they sit outside the command sandbox entirely and network_access has no effect on them. If you need to bound where an MCP server can reach, the control has to wrap the agent process, through host egress rules or a namespace around Codex itself.
Most likely the agent's own traffic rather than a sandbox failure. Model API calls, MCP server connections, web search and connector requests all originate in the parent Codex process, which runs outside the command sandbox by design because it needs network access to function. Checking which PID opened the socket separates this case from an actual escape in a few seconds.
Yes, by reading connections at the kernel rather than asking the agent. The SNI field in the TLS ClientHello is cleartext, so it names the destination per process without any private key. Be careful with DNS as a source: on glibc hosts, resolution goes through NSS over a Unix socket, so a DNS tap sees very little while SNI names nearly everything.
No, and the reason is structural rather than a gap in the implementation. An injected instruction acts through the agent's own tools, which means an MCP server, a connector or a web request from the parent process, and none of those pass through the command sandbox. The matching control is egress policy wrapped around the agent process, plus a record of what it connected to so an unexpected destination is visible afterwards.
Have a sandboxed command attempt an outbound request and confirm that it fails rather than trusting the configuration. This matters more than it sounds because the sandbox has a documented quiet failure: in a hardened container without unprivileged user namespaces, codex exec can exit 0 having run nothing at all, which reads as success at a glance.
For sandboxed commands, yes, through the network policy's domains allow or deny list once access is enabled. For the agent's own traffic you need an outer boundary instead, such as a network namespace with default-deny egress or host firewall rules scoped to the agent's user. Measure what the agent actually reaches first, because an allowlist written from assumptions either breaks it or permits more than intended.
Only if the container's own network is restricted, which by default it is not. A container with ordinary outbound access gives the agent and every subprocess it spawns the same route out that the host has, so containerisation bounds the filesystem and the process tree without bounding egress at all.
The sandbox bounds what a command can touch, including placing it in a network namespace with no route out. The proxy is a narrower thing: once command network access is enabled, it constrains where that traffic may go, through an allow or deny list. Neither covers the agent's own model requests, MCP connections, connector calls or browser activity, which is the gap that surprises people.
--unshare-net quote and the proxy-routing behaviour, read from the implementation rather than a summary of it.network_access defaulting to false under [sandbox_workspace_write], and for the domains allow and deny list.codex exec exiting 0 having run nothing in a hardened container, which is the quiet-failure case behind the verification step.dialout. The outbound-connection monitor used here, which attaches the sending process to each connection.agentwire. The SNI reader, and the source of the measured finding that SNI names nearly every connection while DNS fires almost never on glibc hosts.