The Approval Prompt Is Not the Sandbox

The Approval Prompt Is Not the Sandbox

Codex ships 43,591 lines of sandboxing across four platform backends, and treats enforcement and approval as two separate axes. Pi ships none and says so in its README. Both are defensible. What is not defensible is the assumption in between, where a dialog box gets mistaken for a boundary. A source-level look at what your coding agent can actually do to your machine.

September 4, 2026
Harrison Guo
8 min read
Security AI Engineering

Codex ships 43,591 lines of sandboxing code across four platform backends.

Pi ships zero, and puts it in the README:

Pi does not include a built-in permission system for restricting filesystem, process, network, or credential access. By default, it runs with the permissions of the user and process that launched it.

Both of those are reasonable engineering positions. I am not going to argue that one team is careless. What I want to look at is the space between them, because that is where most people are actually operating, and it is the one position that is not reasonable: running an agent that shows you a prompt, and concluding from the prompt that something is enforcing a boundary.

Two axes, not one

The single most useful thing in the Codex source is that it refuses to collapse permission into one setting. There are two enums, and they are independent.

Enforcement, from codex-rs/protocol/src/config_types.rs:

pub enum SandboxMode {
    ReadOnly,          // default
    WorkspaceWrite,
    DangerFullAccess,
}

Escalation, from codex-rs/protocol/src/protocol.rs:

pub enum AskForApproval {
    UnlessTrusted,
    OnRequest,         // default
    Granular(GranularApprovalConfig),
    Never,
}

The default pairing is ReadOnly plus OnRequest. The agent cannot write anything until something changes, and the model may ask when it wants more.

The Granular variant is worth reading closely, because it is a permission system that has been through contact with reality. It is five separate booleans: sandbox_approval, rules, skill_approval, request_permissions, mcp_elicitations. Five different things can ask a human for permission, and you can switch each of them off independently. When a field is false the request is auto-rejected rather than shown, which is the correct default for an unattended run and the wrong one for an interactive session.

Now hold those two axes apart. Four combinations, and they are four different systems:

approvals onapprovals off (Never)
sandbox onthe normal case. The OS refuses; a human can grant an exception.CI. Nothing is asked, anything outside the box fails, and the failure goes back to the model instead of to a person. Codex’s comment on Never says exactly this.
sandbox offwhere most people actually are. Every boundary in the system is a human reading a command string.DangerFullAccess plus Never. Honest, at least. This is what a container is for.
sandbox on
approvals on the normal case. The OS refuses; a human can grant an exception.
approvals off (Never) CI. Nothing is asked, anything outside the box fails, and the failure goes back to the model instead of to a person. Codex’s comment on Never says exactly this.
sandbox off
approvals on where most people actually are. Every boundary in the system is a human reading a command string.
approvals off (Never) DangerFullAccess plus Never. Honest, at least. This is what a container is for.

The bottom-left cell is the one I care about, and it is not a bug in anyone’s code. It is a reading error.

What the approval prompt actually measures

An approval dialog shows you a command. It does not show you what the command reaches.

Approving npm test approves the project’s test script, plus whatever that script shells out to, plus any lifecycle hook in the dependency tree. Approving make approves a Makefile you did not write. Approving a Python script approves its imports. The string in the dialog is a handle on an arbitrarily large graph, and the graph is what executes.

This is the wrong ruler with a UI. The prompt is not lying. It truthfully reports the command the model proposed. The problem is what people read off it, which is “this operation has been reviewed and bounded,” a claim the prompt never made and cannot support.

An approval is a statement of intent. A sandbox is a statement about capability. Intent without capability limits gives you a system whose entire safety property is that a tired human parses shell correctly at the end of a long session.

Codex’s design makes the distinction structural. Two enums, and you have to set both. That alone is worth stealing even if you never run Codex.

What 43,591 lines of enforcement looks like

Here is what enforcement costs when a team actually means it. The sandbox surface of the Codex repository, tests included, measured on 2026-09-01:

cratelines
windows-sandbox-rs21,399
linux-sandbox9,846
sandboxing9,220
execpolicy2,975
bwrap151
total43,591
crate windows-sandbox-rs
lines 21,399
crate linux-sandbox
lines 9,846
crate sandboxing
lines 9,220
crate execpolicy
lines 2,975
crate bwrap
lines 151
crate total
lines 43,591

Four platform paths: Apple Seatbelt, Linux Landlock, bubblewrap, and a Windows implementation that is by itself the largest of the four. The Seatbelt policies are not generated from some abstraction, they are checked-in .sbpl files: a base policy, a network policy, a preferences policy, and a set of read-only platform defaults.

Set against the agent core, with tests excluded on both sides so the two numbers are the same kind of number, that is 32,774 lines of sandboxing against 125,574. About a quarter of the core, spent entirely on making the operating system say no.

I had that ratio wrong in a draft of this piece, and the way I got it wrong is the subject of the article. I compared the sandbox crates counted with tests against a core counted without them, and got “roughly a third.” Both figures were real. Neither was wrong on its own. They just were not measuring the same thing, and the sentence built on them read as authoritative anyway.

Which is exactly why Pi’s position is defensible rather than lazy. That is a large, permanently expensive, four-platform commitment. A small team can reasonably decline it and tell users to bring their own container. Pi’s README points at three concrete patterns for doing so: a micro-VM extension, plain Docker, and a policy-controlled sandbox.

The prompt that is not a permission

Pi does have a trust prompt, and it is the reason this piece exists. packages/coding-agent/src/core/project-trust.ts asks:

Trust project folder? This allows pi to load .pi settings and resources, install missing project packages, and execute project extensions.

If you have used any modern editor you have seen this dialog, and you know what it feels like it means. It feels like the thing that decides whether the agent is allowed to touch your machine.

It is not. Read the sentence again. It gates loading project configuration, installing project packages, and executing project extensions. It says nothing about the agent’s own tools, because those were never gated. bash is available either way. Answering “no” to that prompt does not put the agent in a box, because there is no box. The README already told us so.

So a user meets a trust dialog, answers it carefully, and walks away with a belief about their security posture that the dialog did not create and does not support. Pi documented the truth in two places. The dialog is still the thing people will remember, because a dialog interrupts you and a README does not.

This is evidence that is not evidence in its purest form. A true signal, about a real decision, borrowed for a claim it was never making.

Why this is worse than it was last year

Because the input got hostile.

I have been making the argument that a downloaded artifact is untrusted input, most recently about GGUF model files, where sixty-five bytes crash a loader. That is a parser problem with a parser fix.

Agent context is not a parser problem. Pi’s SECURITY.md is admirably blunt about it:

Pi relies on users installing trustworthy extensions and loading trustworthy skills and only to use pi within trusted repositories. This is because files like AGENTS.md or instructions in comments can be used to prompt inject the coding agent trivially and this cannot be protected against.

That is correct and it applies to every harness in the field, not just Pi. There is no parse step where you can validate an instruction, because instructions are the payload. A comment in a vendored dependency is in the context window with the same standing as your own words.

So the trust boundary sits somewhere unfamiliar. The agent will at some point act on text written by someone else. That is not a hypothetical, it is the design. The only question left is what the process is capable of when it does.

Which is precisely the question a sandbox answers and an approval prompt does not.

What to actually do

Decide which of the four quadrants you are in, and say it out loud. Most people have never made this an explicit choice, and defaults chose for them.

If you run an agent unattended, on a repository you did not write, with network access and your real credentials in the environment, then approvals are not protecting you and never were. Containerize. Pi’s own docs tell you how, and Codex will do it for you.

If you run Codex, set both enums deliberately. ReadOnly plus OnRequest is a genuinely good default and it will annoy you into weakening it. When you weaken it, weaken the axis you meant to.

If you run Pi, run it the way its authors say to run it: inside something. The four tools are read, bash, edit and write, and bash is not a smaller capability because there are only four of them.

And whatever you run, stop counting approval prompts as a control. A prompt records that you agreed. Only the operating system records what was possible.


Line counts measured 2026-09-01 against github.com/openai/codex and github.com/earendil-works/pi at that day’s HEAD, including test files. Enum definitions quoted from codex-rs/protocol/src/config_types.rs and codex-rs/protocol/src/protocol.rs. Pi quotations are verbatim from its README.md and SECURITY.md.

🎧 More Ways to Consume This Content

I occasionally advise small teams on backend reliability, Go performance, and production AI systems. Learn more: /services

Comments

This space is waiting for your voice.

Comments will be supported shortly. Stay connected for updates!

Preview of future curated comments

This section will display user comments from various platforms like X, Reddit, YouTube, and more. Comments will be curated for quality and relevance.