Skip to content

Sandboxing a coding agent

You ask a coding agent why the app can’t reach its database. It opens .env to check the connection settings, and the database password is now in the tool result. Claude Code reads files in the working folder without asking you first [1], so it didn’t ask you. The tool result is part of what the agent sends to the model provider with its next request. A sandbox changes nothing about that traffic [2]. The password has left your machine, and nobody decided to send it.

Keeping API keys out of the agent’s reach was about secrets going in to the agent: which key it holds and where that key is kept. This lesson is about what can go out. We compare the ways to contain a coding agent and look at why the controls that matter most still hold when the agent has been convinced to do something else. Then we set up Claude Code’s built-in sandbox with a network allowlist, and you predict which commands that allowlist lets through.

A sandbox is the environment the agent runs in, set up so that a wrong or hostile command reaches only what you put inside it. Each kind trades setup work for control:

ApproachForAgainst
The agent’s built-in sandboxIt comes with the agent, and on macOS there is nothing to installOnly the shell commands the agent runs are inside it. Hooks, MCP (Model Context Protocol) servers and the agent’s own file tools stay on your computer
The vendor’s sandbox runtimeHooks and MCP servers are inside the boundary too, because the entire agent runs in the operating system isolation of the built-in sandbox. Docker isn’t neededIt is an early beta, and the way you configure it may still change
A hardened container (Docker, Podman)You choose the mounts, the secrets that go in and the hosts it may reachMedium to high setup effort
A hardened dev containerThe editor starts and manages it, and the vendor’s example comes with a firewall that blocks traffic to hosts it doesn’t listThe team keeps the image and the firewall’s list current. Using it is a team convention, and a developer can still start the agent on the host
A hardened virtual machineA kernel of its own between the agent and your computer, which keeps the two further apart than any other option hereHigh setup effort
A vendor-hosted agent in the cloudNothing to install. The vendor runs the isolated machine, and your forge token stays in a proxy outside it, which gives the agent only scoped access to the repositoryYour code is copied to the vendor’s machines, and you work with the vendor’s controls instead of your own. It needs a subscription
No isolation, permission prompts onlyNothing to set upThe agent has all of your access, and you are the only check

The vendor’s comparison of these options is the source for every row but the last [2]. A container can also keep a credential in a second container next to it, a sidecar, as the hardened container in the last section does [3].

Whichever you choose, the agent can still change the code you mount for writing, and it can still send what it reads to any host the network lets it reach [2]. A sandbox shrinks the blast radius of a session. What the agent does inside it still needs your review.

The trust boundary is the line between what the agent decides on its own and what the harness, the operating system or the network enforces on it. An instruction is on the agent’s side of that line. Claude Code loads CLAUDE.md at the start of every session [4], but a line in it is a request, and the model can ignore it or be tricked out of it.

The controls on the other side work in two ways. A permission rule, a hook and your answer at the prompt check the request: Claude Code judges the text of the command before anything runs. The sandbox limits what the running process can reach, and the operating system holds that limit no matter what the command does once it starts [5].

Prompt injection works on the request. Text in a file can convince the agent to ask for a command that your rules allow and that you approve because it looks routine, such as a small script with a harmless name. The check on the request then passes. A limit on the running process never reads that text, so the text can’t change it. That is the rule of this lesson: for the controls that matter most, put limits on what the running process can reach, enforced where nothing the agent reads can change them. A network allowlist and a credential held by a sidecar both work this way:

  • A network allowlist enforced by the sandbox or a proxy. The agent can run any command it likes, and a connection to a host that isn’t on the list fails.
  • A credential held by a sidecar. The agent’s requests go out through the sidecar, which adds the real token on the way. The agent only ever sees a placeholder, so it has no token to leak [3].
Checkpoint · sort

Checkpoint · scenario

The agent has finished a change and needs a token for the code forge to open the pull request. Where do you keep the token?

Claude Code’s sandbox covers the shell commands the agent runs and every program those commands start. The operating system enforces it: Seatbelt on macOS, and bubblewrap on Linux and WSL2 (the Windows Subsystem for Linux), where you install bubblewrap and socat first. It doesn’t run on native Windows [5].

Which files. By default a sandboxed command writes only in the working folder, a per-user temporary folder and any folder you added. It can read almost the whole computer, including ~/.ssh and ~/.aws/credentials, until you list those under sandbox.credentials or sandbox.filesystem.denyRead. Sandboxed commands also inherit the environment Claude Code was started with, credentials included, unless sandbox.credentials removes them. Claude Code’s own file tools don’t run in the sandbox, and permission rules govern them instead. For the .env case, the Read(./.env) deny rule from the keys lesson blocks the file tools. Once the sandbox is on, Claude Code adds Read deny rules to the sandbox’s read blocks too, so a script the agent writes can’t open the file either [5]. Use denyRead for a path you don’t want as a permission rule.

Which hosts. Network traffic from sandboxed commands goes through a proxy that runs outside the sandbox. No host is allowed at the start, and the first time a command reaches a new host, Claude Code asks you [5]. allowedDomains lists the hosts that pass without a question. An entry such as *.example.com covers subdomains, a :443 suffix limits an entry to one port, and deniedDomains blocks a host even when an allowed entry covers it [6]. With strictAllowlist set to true, a host that isn’t on the list is blocked instead of asked about. Claude Code reads that setting only from your user settings, managed settings or a file you pass with --settings, and in a repository’s .claude/settings.json it has no effect. It needs Claude Code v2.1.219 or later [5].

What happens when the sandbox can’t run a command. If the sandbox can’t start at all, Claude Code shows a warning and runs commands without it, unless failIfUnavailable is true, which makes it refuse to start. When one command fails inside the sandbox, the agent may retry it outside, where the normal permission prompt applies, and allowUnsandboxedCommands: false takes that retry away [5]. By default a command that runs in the sandbox runs without asking you, and autoAllowBashIfSandboxed: false brings the prompt back [6].

The course repository has a fixture for this lesson in site/examples/coding-with-agents/sandboxing/. Its sandbox-settings.json uses the same setting names as the next lesson’s settings file and allows one host:

{
"permissions": {
"deny": ["WebFetch", "WebSearch"]
},
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"autoAllowBashIfSandboxed": false,
"allowUnsandboxedCommands": false,
"network": {
"allowedDomains": ["pypi.org"],
"strictAllowlist": true
}
}
}

The allowlist covers the sandboxed commands only. Claude Code’s web fetch tool runs inside Claude Code itself and follows its permission rules [5], so the deny rules turn off the web tools as well. Next to the settings, commands.txt holds three commands that each try to reach a different host:

curl -sI https://pypi.org/simple/
curl -sI https://files.pythonhosted.org/
curl -s https://collect.example.invalid/upload

The last address ends in .invalid, a name reserved so that it never belongs to a real server [7]. The fixture’s check_allowlist.py makes no network call. It reads the settings, finds the host and port of each command and applies the matching rules above.

Checkpoint · predict

Read the settings and the three commands above. For each command, decide whether the sandbox lets it through, asks you, or blocks it. Then run the script from the fixture’s folder and compare.

Terminal window
python3 check_allowlist.py

Output verified in CI from site/examples/coding-with-agents/sandboxing/check_allowlist.py.

Only the first command gets out. The list names hosts, so a host that isn’t written there is blocked, however closely it is related to one that is. When a command you need is blocked, add the one host it needs, and prefer a named host to a wildcard.

Exercise

From the top folder of your clone of the course repository, copy the fixture folder and start Claude Code in the copy with the fixture’s settings, in Manual mode:

Terminal window
cp -R site/examples/coding-with-agents/sandboxing ~/sandbox-try
cd ~/sandbox-try
claude --permission-mode default --settings sandbox-settings.json

If ~/sandbox-try is left over from an earlier try, remove it first or use a new folder name, because cp -R would put the copy inside the old folder. On the first start in a new folder, Claude Code asks whether you trust it [8]. Say Yes, since the folder is the copy you just made. Run /sandbox and check that the sandbox is on. Then ask the agent to run the commands in commands.txt one at a time and report what each printed. Approve each command at the prompt, since they only read a web address, and answer No if the agent asks for anything else. Write down which commands got a response and which failed with a network error from the sandbox, and compare that with your prediction. This part runs a real agent, so the course can’t check its output. The results depend on your Claude Code version and on your own settings, which Claude Code merges with the file you pass [9]. A host you allowed there, or in a WebFetch(domain:...) allow rule, is on the list in this session too [5]. It takes about five minutes.

A good result is a table with one row per command: the host, what you predicted, what happened, and the setting that decided it. If a row disagrees with your prediction, find the setting from your own files that explains it. Then answer one question: which host would you add for a real project, and what could the agent send there?

Stretch: Run the hardened container from the next section on a copy of the fixture, with no credential flags. Its threat model lists open outbound network, so add an allowlist in front of it, either the built-in sandbox inside the container or a proxy of your own, which then decides which hosts pass, and run the three commands again.

The summaries below each link to their source, for when the built-in sandbox isn’t enough.

A hardened container with a written threat model. The public schubergphilis/claude-docker repository runs Claude Code in a container, and its README says what that protects and what it doesn’t [3]. Apart from the agent’s own sign-in, a credential goes in only when you pass its flag. A forge token stays in a sidecar for the session, and the agent can still use it through the sidecar while the session runs. The threat model also warns that tools such as npx and uvx download and run code the first time you use them. After a compromised session, it says to assume the data has already left and to rotate every credential that entered the container.

A layered containment pattern. Some teams stack controls from different projects. No single source documents the stack as a whole:

LayerWhat it doesPublic building blocks
IsolationOne container per agent on its own network, with only the workspace mountedDocker, Podman, dev containers
Enforcement outside the agentA wrapper proxy allows listed hosts and commands, and removes secrets from tool results before they go to the modelmitmproxy, a small proxy of your own
Review gateThe agent pushes to a local git server, and a person reviews there before anything reaches the real remoteForgejo, Gitea, a bare repository
Supply-chain hygieneA secret scan before each commit, and no package version younger than a set ageGitleaks, pnpm’s minimumReleaseAge
ObservationRules that record network, file and process events around the container without blocking themTetragon, Falco

Gitleaks scans a repository for secrets [10], and pnpm’s minimumReleaseAge holds back a new package version for a set time, one day by default [11].

Running with every prompt switched off. In Claude Code that is --dangerously-skip-permissions, and nothing replaces the prompts it removes [5]. Anthropic asks that such a session run where its file tools, MCP servers and hooks are isolated as well, which means a container, a virtual machine or the sandbox runtime [2]. Check such a run more closely the less of it you watched, as the Academy course Claude Code in action advises [12]. Codex keeps where commands may write apart from when the agent asks you, and turns the network off by default [13]. Those are three separate decisions in any agent, and a setup can be strict on one and loose on another.

The model brings no guardrails. Pi, an open-source coding agent, has no approval step before each tool call. Its documentation leaves safety to whatever limits what Pi can reach, usually a container, a virtual machine or a sandbox around it [14]. The limits come from the harness and the environment, whatever the model.

Recap

  1. A file the agent reads goes to the model provider in the next request, whether or not a sandbox is on. A sandbox limits which files it can read and which hosts it can send them to [2].
  2. The ways to contain an agent run from the built-in sandbox, quick to start and limited to shell commands, through the sandbox runtime, containers, dev containers and virtual machines, to a vendor-hosted agent that needs no setup and gives you only the vendor’s controls.
  3. A permission rule, a hook or a prompt checks what the agent asks for, and injected text can steer that request. For the controls that matter most, limit what the running process can reach: a network allowlist enforced by a proxy, and credentials held by a sidecar.
  4. In Claude Code, allowedDomains lists the hosts, strictAllowlist blocks the rest without asking when it comes from your own or a passed settings file, and allowUnsandboxedCommands: false with failIfUnavailable: true stops the agent’s commands from running outside the sandbox [5].
  5. Keep a token the agent needs in a proxy or sidecar that adds it on the way out, and assume the agent can use it for the whole session.

You can now

  • Runs a session from setup to a reviewed diff

  1. Anthropic. Configure permissions. Claude Code documentation. Reference. Claude Code permissions
  2. Anthropic. Choose a sandbox environment. Claude Code documentation. Reference. Claude Code isolation
  3. claude-docker contributors. claude-docker: hardened Docker container for running Claude Code. schubergphilis/claude-docker on GitHub. Reference. claude-docker
  4. Anthropic. How Claude remembers your project. Claude Code documentation. Reference. Claude Code memory
  5. Anthropic. Configure the sandboxed Bash tool. Claude Code documentation. Reference. Claude Code sandboxing
  6. Anthropic. All settings. Claude Code documentation. Reference. Claude Code all settings
  7. Eastlake and Panitz. Reserved Top Level DNS Names. IETF RFC 2606 (BCP 32). Reference. RFC 2606
  8. Anthropic. Security. Claude Code documentation. Reference. Claude Code security
  9. Anthropic. Settings files and precedence. Claude Code documentation. Reference. Claude Code settings
  10. Zachary Rice and contributors. Gitleaks. GitHub, gitleaks/gitleaks README. Reference. Gitleaks
  11. Zoltan Kochan and contributors. Mitigating supply chain attacks. pnpm documentation. Reference. pnpm supply chain
  12. Anthropic. Claude Code in action. Claude Academy. Course. Academy claude-code-in-action
  13. OpenAI. Agent approvals & security. Codex documentation. Reference. Codex approvals
  14. Mario Zechner and contributors. Run Pi safely. earendil-works/pi on GitHub. Reference. Pi security