How Do I Sandbox Codex on Linux?

Necco Ceresani
Necco Ceresani··17 min read

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: September 2026

Quick answer. Codex already ships a sandbox and on Linux it is bubblewrap plus seccomp, so the work is choosing a mode rather than building confinement. Set sandbox_mode = "workspace-write" with approval_policy = "on-request" for interactive work and read-only for anything unattended, and know two things the docs state plainly: the network proxy "does not filter web search, app or connector tool calls, MCP server connections, browser or Computer Use activity", and in a hardened container the sandbox can fail so quietly that codex exec exits 0 having run nothing. Verify it is enforcing before you rely on it.

I build kernel-side tooling and I read a lot of sandbox implementations, which makes me useful on what Codex's enforcement actually is and useless on whether it fits your workflow. I have not run Codex against a production estate. What I keep seeing is teams treating an agent's built-in sandbox as either complete or worthless, when the useful question is narrower: which mode, what does it not cover, and how would you know if it stopped working.

Does the Codex sandbox use bubblewrap, seccomp or Landlock on Linux?

Bubblewrap and seccomp, not Landlock, and the distinction matters because Codex moved off Landlock deliberately rather than never having used it. That history is why half the guidance online names the wrong mechanism, so it is worth reading the implementation rather than a summary. From the sandbox crate's own README:

"Filesystem-restricted execution requires bubblewrap. The legacy Landlock option is rejected for these policies because it cannot isolate app-server Unix sockets."

That is a specific, checkable reason rather than a preference. Landlock is unprivileged and enforces on the inode, which makes it attractive, and it does not mediate Unix domain sockets, which is what Codex needed. features.use_legacy_landlock still exists as an explicit fallback and the README says to disable it when upgrading.

The bubblewrap configuration is worth knowing because it tells you the shape of the boundary. The filesystem is read-only by default via --ro-bind / /, writable roots are layered back with --bind <root> <root>, and protected subpaths under those roots, including .git, a resolved gitdir: and .codex, are re-applied as read-only. The helper isolates the user namespace with --unshare-user and the PID namespace with --unshare-pid, applies PR_SET_NO_NEW_PRIVS, and adds a seccomp network filter in-process. When network is restricted without proxy routing it also adds --unshare-net.

Because bubblewrap is the mechanism, everything true of bubblewrap is true here, including its own scoping statement that it "is not a complete, ready-made sandbox with a specific security policy." Codex is one of the programs that supplies that policy, which is the useful way to read the modes below: they are not three levels of a dial, they are three sets of bubblewrap arguments with three different defaults, and the properties you get come from those arguments rather than from the name of the mode.

Should I use workspace-write or read-only for the Codex sandbox?

workspace-write for interactive work, read-only for anything unattended, and danger-full-access only inside a boundary you built yourself. The choice is less about strictness than about which direction each one fails when something unexpected happens, which is a different question from how much it gets in your way. The three modes and what each permits, from OpenAI's sandboxing documentation:

sandbox_modeWhat the agent may do
read-only"The agent can inspect files, but it can't edit files or run commands without approval."
workspace-write"The agent can read files, edit within the workspace, and run routine local commands inside that boundary." The documented default
danger-full-access"The agent runs without sandbox restrictions. This removes the filesystem and network boundaries"

Two approval policies pair with those, and the pairing matters more than either half alone. on-request means "the agent works inside the sandbox by default and asks when it needs to go beyond that boundary", and never means "the agent doesn't stop for approval prompts." The documented default pairing is workspace-write with on-request, which is a sensible interactive default because the prompt is doing real work: it is the thing that turns the mode's boundary from advisory into enforced for everything outside the workspace.

The combination worth being careful about is workspace-write with never, because it reads as a small change from the default and is not. It removes the prompt that was the only thing standing between the agent and everything the mode requires approval for, and nothing replaces it. If you want unattended work, read-only with never fails closed; workspace-write with never fails open. That asymmetry is the whole reason to pick the mode from the failure direction rather than from how much friction it removes, because the friction is what was doing the work.

Does the Codex network proxy filter MCP servers and connector calls?

Four categories, and OpenAI lists them rather than leaving you to discover them, which is more than most vendors do with the edges of their own controls. The list is worth reading as a map of which processes the sandbox considers in scope, because everything on it is a path out of the boundary that exists by design rather than by oversight. From the approvals and security documentation:

"The network proxy filters scripts, programs, and child processes that run inside the local command sandbox. It does not filter web search, app or connector tool calls, MCP server connections, browser or Computer Use activity, Codex cloud tasks, or the client's model and authentication requests."

Read that as a boundary drawing rather than a gap. The proxy covers what the sandboxed shell does, which is the large majority of what a coding agent does on a box. It does not cover the agent's own tool calls, and the MCP entry is the one worth acting on: an MCP server is a separate local process with its own filesystem and network access, so it needs auditing on its own terms rather than inheriting the sandbox.

The same documentation adds a line that belongs on a wall: "Monitoring doesn't replace sandboxing, permissions, or review of the result." That is a vendor declining to oversell its own observability, and it is the correct framing for every tool in this space including ours. A record tells you what happened; it does not decide whether it should have.

Why does codex exec exit 0 without running anything?

Because the sandbox failed to initialise and the failure is not surfaced, which is an open issue and the most operationally dangerous behaviour in this post. In a hardened container, non-root with --cap-drop ALL and no-new-privileges, neither the system nor the bundled bubblewrap can create the user namespace it needs.

What you observe is nothing. The issue title is precise about it: "Linux sandbox setup fails silently in hardened containers: codex exec exits 0, no command runs, no operator-visible error." No stderr, exit code 0, no filesystem changes. The sandbox-setup failure reaches the model as internal feedback and never reaches the operator.

The startup warning that does appear makes it worse rather than better, because it says Codex will use the bundled bubblewrap, and the bundled fallback needs the same privileges the container just denied. So the one piece of feedback the operator receives points at a remedy that cannot work in that environment. A CI job in this state reports success and accomplishes nothing, which is the worst available outcome: a green pipeline is a stronger signal than a red one, and it is wrong.

This is the same failure shape that matters for every confinement tool: a boundary that silently is not there looks exactly like a boundary that is holding. Both produce no output, no error and no refusals to read, and the absence of complaints is what a working sandbox and a missing one have in common. It is why the next section exists, and why a verification step belongs in the setup rather than in the incident review.

How do I verify the Codex sandbox refuses a write to /etc?

Test the boundary rather than reading the config, because a config file records what you asked for and not what the kernel agreed to, and the gap between those two is exactly where the hardened-container failure lives. Two checks, cheapest first, and both are worth running once per environment rather than once per project.

Prove a write outside the workspace is refused. Run a command under the sandbox that tries to touch a path you did not make writable, and read both the exit code and the filesystem afterwards, because either one alone can mislead you:

cd /path/to/project
codex exec 'touch /etc/codex-sandbox-probe' ; echo "exit=$?"

An exit code of 0 with no file created is the silent-failure signature from the previous section, not a pass. A refusal with a non-zero exit is the sandbox working. A file that appears at /etc/codex-sandbox-probe means you are not sandboxed at all, and the first thing to check is whether the mode is danger-full-access.

Then confirm what actually ran, from outside the agent. Codex's own output tells you what it believed it did, which is the one account you cannot check the others against, and in the silent-failure case it is the account most likely to be confidently wrong:

curl -fsSL https://yeet.cx | sh
yeet login
yeet run gh:yeet-src/exectop -- --pid $(pgrep -n codex)

exectop folds every command in that process tree into one row per kind, following it through fork in the kernel, so a sandbox that ran nothing produces an empty table and a sandbox that ran a hundred commands shows them folded by kind. Its README is explicit that it "does not stop, delay, or modify anything", which is the right tool for a verification step: you want the reading, not an intervention.

Do I still need Landlock or a container if Codex sandboxes itself?

For a developer box, usually not, and the exception is worth naming. Codex's sandbox covers the sandboxed shell well, and layering another filesystem jail underneath it mostly buys duplication plus two policies to debug, which is a real cost the first time a build fails and you have to work out which boundary refused it.

Where an outer boundary earns its place is the part Codex's proxy does not filter. If the concern is the agent's own tool calls, an MCP server, a connector or browser activity, that traffic is outside the sandbox by design, and a network namespace with a default-deny firewall is the control rather than a mode setting. The distinction is which process you are trying to bound: Codex's sandbox bounds the shell it spawns, and an outer boundary bounds Codex itself along with everything it talks to. The layer-by-layer comparison of confinement options covers where each boundary holds, and what to grant an agent inspecting a server covers the access question rather than the confinement one.

The other case is the hardened-container one above, inverted. If your environment cannot give bubblewrap a user namespace, Codex's sandbox does not work there, and the boundary has to come from the environment rather than from the agent. That is not a worse outcome, just a different owner: a container that denies namespaces is already confining the agent, and the mistake is expecting a second boundary inside it.

How should I configure the Codex sandbox in config.toml?

Start from the documented defaults and change one thing at a time, because the modes interact with the approval policy in ways a single setting does not show, and a config that reads stricter can be functionally looser. The keys are sandbox_mode, approval_policy, approvals_reviewer and sandbox_workspace_write.writable_roots, and the last one is the one most often left wider than intended.

For interactive local work, the documented default pairing is already reasonable: workspace-write with on-request. The change most teams should make is narrowing writable_roots to the project rather than leaving it broad, because the mode's boundary is only as tight as the roots you declared, and a root that covers a home directory has turned the workspace boundary into a formality while still reading as sandboxed in the config.

For unattended or CI work, use read-only with never. That combination fails closed: the agent inspects and reports and cannot edit or run commands without an approval that will never come. It will also stall on tasks that need to write, which is the correct behaviour for a job nobody is watching and the reason to choose it deliberately rather than discovering it.

Whatever you set, add the verification step. A config file is a request, the hardened-container issue is the proof that the kernel does not always grant it, and the cost of checking is one command against the cost of finding out during an incident that nothing was ever enforced.

The bottom line: pick the mode, then prove the boundary exists

Treat Codex's sandbox as a real control with a stated scope rather than as either complete or decorative. On Linux it is bubblewrap plus seccomp, with the filesystem read-only by default and writable roots layered back, and OpenAI rejected the legacy Landlock path for a specific reason: it "cannot isolate app-server Unix sockets." Set workspace-write with on-request for interactive work and narrow writable_roots to the project; use read-only with never for anything unattended, because workspace-write with never fails open. Know the four things the proxy does not filter, and treat an MCP server as a separate process to audit rather than something the sandbox covers. Then verify, with a write probe outside the workspace and exectop on yeet to see what the tree actually launched.

The failure worth building a habit around is the quiet one. A sandbox that could not initialise and a sandbox that is holding perfectly produce the same output, which is nothing, and in a hardened container Codex currently reports exit code 0 either way. The only difference visible to you is whether a command you expected to be refused was refused.

Frequently asked questions

Does Codex sandbox commands by default on Linux?

Yes. The documented default is workspace-write paired with an on-request approval policy, which lets the agent read files, edit within the workspace and run routine local commands inside that boundary while asking before it goes beyond. The enforcement on Linux is bubblewrap plus a seccomp network filter.

What is the difference between read-only and workspace-write in Codex?

In read-only the agent can inspect files but cannot edit them or run commands without approval. In workspace-write it can read, edit within the workspace and run routine local commands inside that boundary, and needs approval to edit outside the workspace or reach the network. The second is the low-friction default for local work.

Is danger-full-access ever the right choice?

Only inside a boundary you built yourself, such as a disposable VM or a container you are willing to discard. It removes the filesystem and network boundaries entirely, so the isolation has to come from the environment instead. Using it on a machine you care about means there is no sandbox.

Does Codex use Landlock on Linux?

Not for filesystem-restricted execution. The sandbox crate's README states that those policies require bubblewrap and that the legacy Landlock option is rejected because it cannot isolate app-server Unix sockets. A features.use_legacy_landlock fallback exists, and the README advises disabling it when upgrading.

Does the Codex sandbox block MCP servers?

No. The documentation lists MCP server connections among the things the network proxy does not filter, alongside web search, app and connector tool calls, browser or Computer Use activity, Codex cloud tasks, and the client's own model and authentication requests. An MCP server is a separate local process and needs auditing on its own terms.

Why does codex exec succeed but change nothing?

Most likely the sandbox failed to initialise and the failure was not surfaced. An open issue documents codex exec exiting 0 in hardened containers with no command run and no operator-visible error, because neither system nor bundled bubblewrap can create the user namespace it needs when capabilities are dropped.

Can I run Codex sandboxed inside a Docker container?

Sometimes, and it depends on whether the container permits user namespaces. Bubblewrap needs them, and a hardened container that drops all capabilities and sets no-new-privileges denies them, at which point Codex's sandbox does not function. Test with a write probe rather than assuming, because the failure is silent.

How do I test whether the Codex sandbox is working?

Ask it to write somewhere it should not be able to, and check both the exit code and whether the file appeared. A refusal with a non-zero exit is the sandbox working; a created file means no sandbox; exit 0 with nothing created is the silent-initialisation failure rather than a pass.

Does Codex's sandbox protect against prompt injection?

It reduces what an injected instruction can accomplish rather than preventing injection. A mode that requires approval to leave the workspace bounds the damage of a bad instruction, and the categories the proxy does not filter remain reachable. Sandboxing and injection resistance are different properties.

Should I use WSL for Codex sandboxing on Windows?

WSL2 follows the normal Linux bubblewrap path, so the sandbox behaves as it does on Linux. WSL1 is not supported for bubblewrap sandboxing because it cannot create the required user namespaces, and Codex rejects sandboxed shell commands that would enter that path.

Sources

  • Codex sandboxing documentation. Source for the three mode names and the verbatim description of what each permits, the on-request and never approval policies, the documented default pairing, and the config.toml keys sandbox_mode, approval_policy, approvals_reviewer and sandbox_workspace_write.writable_roots.
  • Codex agent approvals and security. Source for the list of what the network proxy does not filter, quoted in full above, and for the statement that "Monitoring doesn't replace sandboxing, permissions, or review of the result."
  • codex-rs/linux-sandbox README. The implementation's own description: bubblewrap as the default filesystem sandbox, the rejection of legacy Landlock because it "cannot isolate app-server Unix sockets", --ro-bind / / as the default, writable-root layering, the .git and .codex read-only re-application, --unshare-user, --unshare-pid, PR_SET_NO_NEW_PRIVS, the in-process seccomp network filter, and the WSL1 exclusion.
  • openai/codex issue 46246. The open issue documenting silent sandbox-initialisation failure in hardened containers, where codex exec exits 0 with no command run and no operator-visible error. Read this before running Codex sandboxed in CI.
  • bubblewrap on GitHub. The mechanism underneath, and the source for its own scoping statement that it "is not a complete, ready-made sandbox with a specific security policy", which is why the policy Codex supplies is the thing to read.
  • exectop. The process-launch monitor used for the verification step. Follows one process tree through fork, folds repeated commands into one row per kind, and states plainly that it "does not stop, delay, or modify anything".
  • yeet documentation. The runtime the kernel-side probe here is written in, for when the verification question needs a shape no ready-made tool has.

Related resources

Built with yeet, a JS runtime for writing eBPF programs on Linux machines. Join us on discord.