Blog

Codex CLI sandbox modes, and the part the sandbox does not cover

Codex ships a sandbox, which is more than most agents do. Two things about it are worth knowing before you tune it, and one of them is a macOS quirk that will bite anyone trying to add a boundary of their own.

Codex CLI controls three separate things. sandbox_mode decides what the agent may touch: read-only, which is the default, workspace-write, or danger-full-access. network_access is a separate switch that is off unless you turn it on, in read-only and workspace-write alike. approval_policy decides when Codex stops to ask you, with untrusted, on-request and never. They are independent, so a permissive mode with approvals off behaves nothing like the same mode with approvals on. On macOS the sandbox is enforced by the kernel through Seatbelt, which is why wrapping Codex in your own outer sandbox does not narrow it further and instead breaks the one Codex was applying.

Two settings, two different jobs

Most confusion about Codex permissions comes from treating these as one dial. They are not.

sandbox_mode is about reach on the filesystem. read-only is the default and writes nothing. workspace-write makes the working directory writable, plus /tmp and $TMPDIR when they are enabled and any writable_roots you configure. danger-full-access is what its name says.

network_access is a separate switch, and this is the part people miss: it is off unless you turn it on, and it is off in workspace-write too. A mode that lets an agent write your repository still lets it reach nothing until you say so. That default is a good one and it is worth knowing you are turning it off when you turn network on for a task that needs an API.

approval_policy is about who is watching. untrusted, on-request and never describe how often Codex pauses for a human. Turning approvals off does not widen the sandbox, and tightening the sandbox does not add approvals.

The combination that catches people out is a wide mode with network on and approvals off, because each of the three looked reasonable on its own. Decide filesystem reach, then network, then how often you want to be interrupted inside both. (Verified against the SandboxMode enum in OpenAI's own source, codex-rs/protocol/src/config_types.rs, rather than a docs page, since the modes are defined there.)

The macOS quirk: sandboxes do not nest

This one is measured rather than documented, and it is the most useful thing on this page if you were about to add your own boundary.

On macOS the enforcement is Seatbelt, the kernel sandbox applied through sandbox-exec. A profile is applied when a process starts and inherited by every descendant. What is not obvious is that profiles do not compose: once a process is running under a profile that contains any deny rule at all, applying a second sandbox inside it fails outright with sandbox_apply: Operation not permitted. There is no rule you can write in the outer profile to re-permit it. Measured on macOS 26.5.2.

So the intuitive move, wrapping your whole terminal in your own Seatbelt profile so everything inside is contained, does the opposite of what you wanted. Codex starts, tries to apply its own sandbox, and cannot. You have replaced a boundary the vendor maintains with one you wrote, and you will not get an error that says so in those words.

The pattern that works is to confine something narrower than the shell the agent starts in: bound the folders at the level above, or bound a single process that received something sensitive, and let the agent keep applying its own profile.

What no sandbox mode covers

Every mode above decides what a process may touch. None of them decides what a credential may do.

If Codex is allowed to reach the network at all, then any token in its environment can go anywhere that reach allows, inside an ordinary request, and nothing in a sandbox reads a payload. read-only mode does not help either: a token in the environment is readable without writing anything, and whatever is read becomes context sent upstream.

That is the gap worth naming, because sandbox settings feel like they answered it. They answered the blast radius question. The credential question is separate and has a separate answer, which is that the value stops being in the room.

How this works in Forkbench

Forkbench is a Mac app for running coding agents, and it runs the codex binary you installed, with your config, your sandbox_mode and your approval_policy untouched. It does not wrap the pane, for exactly the reason in the section above.

What it adds sits on either side of the sandbox, and the more important half is the credential. A key in Vault is used by the command that needs it without the value reaching the agent, so it is not in the environment for any sandbox mode to have to contain, which is the gap the section above describes. The other half is a boundary above the agent rather than around it: a Thread can be locked to a set of folders, applied as each shell starts and inherited by everything it spawns, so Codex still applies its own sandbox inside.

The honest limit on that second one: a command you authorise can still print what it was handed, and a key bound to a destination can still be used against that destination. It is containment and evidence, not isolation.

Related: Seatbelt, containers and VMs compared

Frequently asked

Keep reading