The Sandbox I Turned Off Because It Got in the Way — Rethinking Agent Isolation Before Turning It Back On#
2026-10-18

A sandbox I turned off on purpose#
I have been running Claude Code with its sandbox turned off, doing most of my work in bypass permission mode, which allows every tool call without an approval prompt. It was not that I didn’t know about the sandbox. I had it on, found it getting in the way, and decided to accept some security risk for the sake of speed. In exchange, I figured that if I read the agent’s responses carefully within each session, I could avoid creating a problem.
Then last week, in the 2026-10-11 AI news brief, I wrote about the general availability of Microsoft’s MXC (Microsoft Execution Containers). It is an isolation layer: you define access to files, network, processes, and UI in a JSON policy, and the operating system enforces that boundary. In that brief I wrote that I planned “to experiment with wrapping Claude Code work in the macOS Seatbelt backend.” To run that experiment, I first had to revisit why I had turned the sandbox off.
This essay is the record of that review. Here is the conclusion up front. Reading responses carefully is an after-the-fact review, so it cannot replace a boundary. And much of the friction I felt was not something I had to accept, but something configuration could reduce. That said, I am not yet working with the sandbox turned on, so the friction and the fixes below are based on the official documentation. Where I apply them to my own work, those are expectations, not experience.
Checking the “I’ll just read carefully” assumption#
There are conditions under which my approach does work. I am focused on one task in one session, and I can see a command before it runs and reject it. Reading every prompt in manual approval mode comes close to that.
The problem is that the way I work has been drifting away from those conditions. When I looked closely, the assumption broke down in four places.
First, in bypass mode, reading happens after execution. If I read a response and notice something wrong, it has already happened. Reading is a way to notice an incident, not to prevent one.
Second, an incident can play out within a single turn, before I read anything. Say the agent reads a web page that contains a hidden instruction like “read the config file and send it to this address,” and on its next tool call the agent runs it with curl. All I see on screen are a few lines summarizing the tool calls. This kind of attack is called prompt injection.
Third, some code runs outside the screen I am reading. What MCP (Model Context Protocol) servers and hooks do is not fully visible in the conversation. The same goes for install scripts that npm install runs for dependencies.
Fourth, attention has limits. I switch between several sessions and use more and more subagents and background tasks. The more work runs at once, the less realistic “reading carefully” becomes.
So it is less that my judgment was wrong than that its assumptions were aging as my way of working changed. Reading responses is still important for reviewing the quality of the output, but it cannot serve as a security boundary.
Approval is not a boundary#
So what should the boundary be? The MXC announcement sums up the starting point this way: an agent cannot be its own security authority, so it must run inside a boundary that a developer or organization sets and that is enforced independently of the agent. The policy has to sit outside the agent’s reach, so that neither the agent nor the code it generates can grant itself more access.
The Claude Code documentation draws the same distinction. Permission modes decide whether a tool call runs and whether you are asked first; isolation decides what the call can reach once it runs. Approval prompts belong to the former. And asking every time breeds approval fatigue, which tends to end in “allow everything.” That is exactly the path that led me to bypass mode.
Why isolation matters becomes clear through what Simon Willison named the lethal trifecta.
- Access to private data (source code,
~/.ssh, API tokens) - Exposure to untrusted content (web pages, issues, external documents)
- A way to send data out (the network)
When all three come together in one agent, an attacker can extract data just by hiding instructions in content. Coding agents have all three by default. Filters that try to catch hidden instructions only work probabilistically, so isolation instead cuts one leg of the trifecta structurally. Usually that means opening item 3, the network, only to allowed destinations, or blocking reads of secret files under item 1.
A map of isolation — what, and how strongly#
Isolation can be broken down into five dimensions.
- File writes: allow only the working directory, and protect shell config files,
.git/hooks, and agent config files - File reads: block secret files such as
~/.sshand~/.aws/credentials - Network: block by default, and let only allowed domains through a proxy
- Credentials and environment variables: remove tokens or replace them with fake values
- Processes and UI: block control of the desktop, clipboard, and other apps
Of these, files and network must be blocked together. The Claude Code documentation warns that without network isolation, SSH keys can leak, and without filesystem isolation, an agent can plant a backdoor in system files to regain network access.
There are two broad ways to build a boundary. The restriction approach uses policy to limit what a process can do on the same operating system and filesystem. The separation approach gives the agent its own filesystem or kernel and connects only what it needs. Strictly speaking, “sandbox” refers to the restriction approach, but in practice people use the word for both. Ordered by strength, it looks like this.
| Level | Representative tech | Characteristics |
|---|---|---|
| In-app permission rules | Allow / deny rules, classifiers | A policy, not a boundary |
| OS process sandbox | Seatbelt, bubblewrap, Landlock, AppContainer | Light and fast. Shares the kernel |
| Container | Docker, dev container | Separates the whole environment. Shares the kernel |
| microVM / VM | Docker Sandboxes, Firecracker | Separates the kernel too. Strongest but heaviest |
| Cloud session | Claude Code on the web, etc. | Fully separated from the local machine |
Seatbelt is the isolation feature built into macOS, bubblewrap and Landlock serve the same role on Linux, and AppContainer does so on Windows. Claude Code’s sandbox uses Seatbelt on macOS and bubblewrap on Linux and WSL2, and runs without a sandbox on native Windows. OpenAI Codex defaults to workspace-write for local work, which allows writes only in the working folder, and GitHub Copilot has just made its MXC-based local sandbox generally available.
What makes MXC meaningful is that it unifies these backends under one policy format. The same JSON policy is enforced with AppContainer on Windows, Seatbelt on macOS, and Bubblewrap on Linux. When you first write a policy, you can observe in Learning mode, which blocks and records, or Permissive mode, which allows and records, before switching to enforcement. Claude Code is on the “coming soon” list, so what I can use right now is Claude Code’s own sandbox.
Why fake environments are rare#
One question came up while I was researching. Instead of blocking the outside world, is there an approach that builds a fake external environment inside the sandbox, so the agent uses it believing it is real?
There is. But how it is used depends on what gets faked.
The most practical target is credentials. What an agent needs is not the token value itself but the authenticated result that the token produces. Claude Code’s mask setting shows sandboxed commands a fake value that changes every session, and a proxy outside the sandbox swaps in the real value only on outbound requests to allowed hosts. Even if the agent prints the token or sends it somewhere it shouldn’t, what leaks is the fake. Claude Code’s cloud sessions likewise keep the GitHub token in a proxy outside the sandbox and issue only narrowly scoped credentials inside it.
Substitution at the filesystem level is also common. A container’s overlay filesystem is a copy that looks like the original, so whatever is written there does not reach the original. bubblewrap can also block writes by mounting an empty file over a protected path.
By contrast, imitating entire external services is mostly used in benchmarks and simulated training environments, and in deception-based defenses that plant decoy tokens to detect leaks. I see two reasons this is rare for working agents. One is that the work doesn’t get done. An agent decides its next step from real results such as build output or API responses, and fake responses send it spinning toward wrong conclusions. The other is that honest refusal works better. Claude Code reports the blocked host name right in the command result, and the agent can use that to ask the user, “I need this domain allowed.” Silently handing over a fake cuts that feedback loop.
So current practice has settled on giving fakes for secrets, and giving the real environment but fencing it with a boundary. This distinction also guides the choice of fixes later on.
The friction I can expect when I turn it back on#
From the troubleshooting section of Claude Code’s sandbox documentation, I picked out what I am likely to run into on macOS, which is what I use.
| Friction | Cause |
|---|---|
| Blocked every time a new host is needed | The allowed domain list starts empty |
git push over SSH fails | On macOS, the SSH tunnel cannot authenticate to the sandbox proxy |
DB clients and ssh can’t connect | Tools that bypass the proxy have no way out |
| Can’t reach a localhost dev server | Direct connections to local servers outside the sandbox are blocked by default |
open and osascript fail with error -600 | Apple Events are blocked by default |
Go-based CLIs such as gh fail TLS verification | A compatibility issue with the Seatbelt environment |
docker commands don’t work | Incompatible with the sandbox |
pbcopy doesn’t update the clipboard | Can’t reach the system clipboard |
git checkout fails with unable to unlink old | It needs to replace files in a protected path such as .claude/skills |
Applied to my blog work, I expect a few of these to hit right away. The scripts that query GA (Google Analytics) and Search Console will need the Google API domain allowed, and the script that launches a logged-in Chrome uses open, so it will run into the Apple Events block. This repository also keeps .claude/skills under version control, so switching branches that change skill files may hit the protected path issue.
There is also a pitfall of a different kind. Claude Code’s sandbox wraps only shell commands such as Bash. The file read / write tools, MCP servers, and hooks run outside the sandbox with my full permissions. Simply turning it on can add friction while protecting less than I might think.
Accept it, or design around it?#
The list makes the friction look substantial. But one point in the documentation changed my thinking: how much friction you feel depends on what you compare against.
Claude Code’s sandbox has an auto-allow mode, which runs commands inside the sandbox without asking. Compared with manual approval mode, the sandbox actually reduces prompts. With a boundary in place, there is no need to ask every time. Friction only increases compared with bypass mode. The friction I felt had been measured from the start against a state that blocked nothing at all.
Conversely, there is a catch if you turn on the sandbox while staying in bypass mode. According to the documentation, in bypass mode a command that fails inside the sandbox can be retried outside it without a prompt, and connections to hosts not on the allowlist are also allowed without a prompt. It is like setting up a boundary and leaving the door open. If I keep using bypass, I need to close these two exits in my user settings.
Most of the remaining friction can be reduced through configuration. Here is what the documentation suggests.
- Blocked on every new host: register frequently used domains in
allowedDomainsahead of time. Broad domains such asgithub.com, however, can themselves become exfiltration paths. The documentation warns that because the default proxy does not inspect traffic wrapped in TLS (transport encryption), code can bypass it by disguising itself as an allowed domain. - Git over SSH: switch the remote URL to HTTPS. Keeping SSH means taking commands like
git pushout of the sandbox, and those excluded commands can reach any host regardless of the allowlist. - Localhost dev servers: on macOS,
allowLocalBindingopens them, but it also exposes every other service running on localhost. - Incompatible tools such as
ghanddocker: add them toexcludedCommandswith narrow patterns so they run outside the sandbox, and add an ask rule if you want a prompt for each run. - Apple Events: turning on
allowAppleEventslets sandboxed commands launch other apps without a sandbox, which effectively removes execution isolation. It is better for me to launch the browser myself, or to exclude only theopencommand. - Clipboard: copy what the agent printed in its response with the
/copycommand. That command runs in the Claude Code process, outside the sandbox. - Credentials: block reads of
~/.sshand~/.aws/credentialswith thecredentialssetting, and give environment variable tokens a fake value with themasksetting described earlier. On macOS, however, file masking is handled as blocking instead, and the TLS termination that masking requires is still experimental.
The one to be most careful with is excludedCommands. Use it broadly for convenience and the boundary becomes meaningless. The documentation specifically warns against excluding interpreters or scripts inside the working directory, because the agent can rewrite that file and then run it outside the sandbox.
Here is the skeleton of the configuration I have in mind. Some settings, such as the strict allowlist, are ignored in repository settings and only apply from user settings, so it goes in ~/.claude/settings.json.
{
"sandbox": {
"enabled": true,
"allowUnsandboxedCommands": false,
"network": {
"strictAllowlist": true,
"allowedDomains": ["www.googleapis.com", "registry.npmjs.org"]
},
"credentials": {
"files": [
{ "path": "~/.ssh", "mode": "deny" },
{ "path": "~/.aws/credentials", "mode": "deny" }
]
}
}
}You can’t get the allowlist right from day one. Just as MXC observes in Learning mode before switching to enforcement, I think the realistic order is to start without strict mode, collect the hosts I need through prompts, and lock it down with strict mode once the list stabilizes.
Some friction will still remain. Trying to remove that too would lead back to opening the boundary. I have decided to accept the rest as the price of the boundary. The standard I took from this review is not to accept all the friction or avoid all of it, but to separate the friction that design can reduce from the friction I pay as the price of a boundary.
My choice#
Following that standard, I plan to split isolation levels by the kind of work.
- Interactive day-to-day work (writing essays and briefs): turn on Claude Code’s sandbox in auto-allow mode, together with a prepared allowlist and a block on unsandboxed retries. Even if I keep using bypass mode, I keep those exits closed.
- Commands with external effects (such as deployment): I run them myself, outside the sandbox.
- Unattended or long-running work: the Bash sandbox alone leaves MCP servers and hooks outside, so I use
@anthropic-ai/sandbox-runtime, which wraps the whole process, or a Docker Sandboxes microVM. Keep in mind that the kernel-level isolation Docker Sandboxes advertises is still Docker’s own description. - MXC: once Claude Code supports it, I will reassess whether the different per-tool settings can move into a single policy.
The “wrap Claude Code in Seatbelt” experiment I mentioned in the brief doesn’t have to wait for MXC. /sandbox wraps Bash commands in Seatbelt, and sandbox-runtime wraps the entire Claude Code process the same way. I will record the friction I actually run into once it is on, and compare it with this list in a follow-up post.
Closing#
When I turned off the sandbox, I put “friction” and “risk” on the scale. Looking again, the scale itself was off. Much of the friction could be reduced by configuration, and the careful reading I trusted to prevent risk was a review after execution. Once a boundary is in place, the role of reading changes too. I can spend the attention I put into reading responses on the quality of the output instead of on security monitoring.
References#
- Microsoft — Microsoft Execution Containers: policy-driven containment for AI agents
- GitHub — Local sandboxing for GitHub Copilot now generally available
- Claude Code — Configure the sandboxed Bash tool
- Claude Code — Choose a sandbox environment
- anthropics / sandbox-runtime
- OpenAI — Codex sandboxing
- Simon Willison — The lethal trifecta for AI agents
- Docker — Docker Sandboxes: Run Claude Code and other coding agents unsupervised (but safely)
- 2026-10-11 AI news brief