Claude Code Sandboxing for Client Work: Fence Each Build So One Client Can Never Read Another

- The Claude Code sandbox runs Bash commands inside an operating-system boundary. By default a sandboxed command can write only to the working directory, any directories you added, and a per-user temp directory, and it can reach no network domain until you allow one. On macOS it uses the built-in Seatbelt framework with nothing to install; on Linux and WSL2 it uses bubblewrap and socat. Native Windows is not supported.
- For client work, use the Profit Room's One-Folder Fence: a .claude/settings.json in each client repo that enables the sandbox, blocks reads of your home directory except the project itself, denies your credential files and secret environment variables, and pre-allows only the domains that client's stack needs. Sandboxed commands then run without prompts, and a command in Client A's project cannot read Client B's folder or your keys.
- It is a fence, not a vault. The sandbox covers Bash, PowerShell, and Monitor commands and their child processes, not the Read, Edit, and Write tools, and the docs say plainly that it reduces risk but is not a complete isolation boundary. Pair it with permission rules and a pre-handoff security review, and describe it to clients as exactly what it is.
What the Claude Code Sandbox Actually Enforces
The sandbox is built into Claude Code and has two independent layers. Filesystem isolation controls which paths a sandboxed command can read and write: by default it can write to the current working directory and its subdirectories, any directory you added with --add-dir or permissions.additionalDirectories, and a per-user temp directory, and it can read the rest of the computer except a few denied directories. Network isolation runs every outbound connection through a proxy outside the sandbox, and Claude Code pre-allows no domains at all. The first time a command needs a new host, you get a prompt; answer 'Yes, and don't ask again' and it saves a WebFetch(domain:...) allow rule so that host stays allowed in future sessions.
The reason this matters more than a permission rule is where it is enforced. Claude Code evaluates permission rules before a command runs, based on the command string. The sandbox is enforced by the operating system on the running process, so it holds regardless of what the model chose to run and even if an allowed command does more than its name suggests. On macOS the enforcement is Seatbelt, which is already on the machine. On Linux and WSL2 it is bubblewrap for the filesystem and socat for the network relay, and the /sandbox panel in the terminal shows a Dependencies tab if either is missing. Native Windows is not supported; the docs say to run Claude Code inside a WSL2 distribution.
There is a second protection inside the fence that clients never see but should. Even within the directories a sandboxed command may write to, the sandbox denies writes to the files Claude Code loads its own configuration and code from: the .claude settings files, the .claude/skills, .claude/agents, .claude/commands, and .claude/hooks directories, .mcp.json, shell startup files like .zshrc, and the hooks and config inside .git. A command that could edit those could grant itself permissions or add a hook that runs outside the sandbox on the next turn. No allowWrite entry lifts that protection. It is the same principle as the [blast radius rule for permissions](/blog/claude-code-permissions-for-client-work), applied to the process after it starts rather than before.
The One-Folder Fence
The Profit Room's One-Folder Fence is one rule: every client project carries a committed .claude/settings.json that fences the sandbox to that folder, and nothing else on the machine is readable from inside it. The rule exists because of the default read policy. Out of the box, a sandboxed command can read the entire computer apart from a few denied directories, and the docs note that this default still allows reading credential files such as ~/.aws/credentials and ~/.ssh. There is no built-in credential deny list; only the files and variables you list are restricted. If you [run several client projects on one laptop](/blog/run-multiple-claude-code-client-projects-at-once), the defaults are not a fence between them. The settings file is.
- Turn it on. Set sandbox.enabled to true. In your user settings at ~/.claude/settings.json that enables it for every project; in the project's own .claude/settings.json it ships with the repo, which is what I want on client work.
- Fence the reads. In the project's .claude/settings.json, set filesystem.denyRead to ['~/'] and filesystem.allowRead to ['.']. The docs give this exact example and say to put it in project settings, because the relative path resolves to the project root only there; in user settings it would resolve to ~/.claude and block the project itself. When read rules overlap, the narrower path wins, so the project stays readable and the rest of your home directory, including every other client's folder, does not.
- Deny your credentials. Under sandbox.credentials, list the files and variables a sandboxed command must never see: ~/.aws/credentials and ~/.ssh with mode deny, and any token variables such as GITHUB_TOKEN or NPM_TOKEN with mode deny. A deny entry only ever narrows access, so any settings scope can add one and no scope can remove one another scope added.
- Pre-allow the client's domains. Put the hosts their stack actually needs, such as their package registry and their git host, in network.allowedDomains so the build runs without a prompt for each. Keep it short. The docs warn that broad domains can create paths for data exfiltration, so allow the host, not the wildcard, wherever you can.
Desktop-app note. The sandbox itself runs in the app, but the interactive terminal panels are a different story: the desktop docs say built-in commands that open a terminal dialog behave differently in the Code tab, and to edit settings files directly to manage permission rules and configuration. That suits client work better anyway. Choosing a mode in the terminal panel writes to .claude/settings.local.json, which Claude Code adds to your global gitignore. The One-Folder Fence lives in .claude/settings.json so it is committed, travels with the code, and applies to whoever opens the repo next. Edits to the filesystem lists apply to the running session, so you can tighten a path mid-build and the next command runs under it.
Auto-Allow: Fewer Prompts Without Skipping Permissions
The sandbox has two modes and they enforce the same boundary; the only difference is whether sandboxed commands are auto-approved. In auto-allow mode, a command that can run sandboxed runs without asking. A command that cannot, because it needs a host you have not allowed or a tool that is incompatible, falls back to the regular permission flow, and the prompt is titled 'Bash command (unsandboxed)' so you can tell the difference at a glance. Three guards survive auto-allow: explicit deny rules are always respected, rm or rmdir on a critical path still goes through the regular flow, and content-scoped ask rules like Bash(git push *) still force a prompt. So the git push guard from the permissions post keeps working with the sandbox on.
Then there is the escape hatch. When the sandbox blocks a command, Claude sees which path or host was denied and may retry the command with the dangerouslyDisableSandbox parameter. The retry runs outside the sandbox, so it goes through the regular permission flow and in Manual mode you get a confirmation prompt. On a sensitive client project I close the hatch by setting allowUnsandboxedCommands to false, which the Overrides tab labels Strict sandbox mode; then every command Claude runs must run sandboxed unless it is listed in excludedCommands. That list is where the known incompatibilities go: the docs name docker, which needs a 'docker *' entry, jest, which wants --no-watchman, and on macOS some Go-based CLIs such as gh, gcloud, and terraform that can fail TLS verification under Seatbelt.
Auto-allow is not [auto mode](/blog/claude-code-auto-mode-for-client-work), and /sandbox is not a permission mode. Auto-allow approves a Bash command because the boundary contains it; auto mode uses a classifier to review each action. They work independently and combine. With both on, Claude names the hosts a command needs on the command itself instead of prompting for each connection, the classifier reviews the hosts with the command, and an approved list opens those hosts for that one command only. Nothing gets added to your settings. For a client build that is the pairing I run most: classifier on the decision, operating system on the process.
What the Sandbox Does Not Protect
Scope first. The sandbox isolates Bash, PowerShell, and Monitor commands and their child processes. The built-in Read, Edit, and Write tools use the permission system directly and never pass through it, so the Read deny rules from the [two-bucket rule for client data](/blog/is-it-safe-to-use-claude-code-with-client-data) are still required; the fence does not replace them. Sandboxed commands inherit the parent process environment by default, including any credentials exported there, which is why the credentials block exists and why the docs offer CLAUDE_CODE_SUBPROCESS_ENV_SCRUB to strip them from every subprocess. Subagents run in the same process and use the same sandbox configuration, so a subagent's commands are fenced when the parent's are. Computer use runs on your actual desktop and is outside all of this.
Then the limits the docs state themselves. By default the proxy makes its allow decision from the requested hostname and does not terminate or inspect TLS, so it cannot see what an allowed connection carries, and allowing broad domains can create exfiltration paths. Commands you type yourself at the ! shell prompt run outside the sandbox in a normal session. And turning filesystem isolation off while commands are auto-allowed lets a command write shell startup files or executables on your PATH and widen its own access on the next run, which is why I never disable that layer on a client project. The docs' own summary is the one to repeat: sandboxing reduces risk but is not a complete isolation boundary.
So position it correctly. The One-Folder Fence protects the client from your other clients, protects your keys from a careless command, and protects Claude Code's own configuration from being rewritten mid-session. It does not replace the [security review before handoff](/blog/claude-code-security-review-for-client-work), the hooks that gate a deploy, or the language in the client agreement. It is one layer, and it is the layer that holds when the others are wrong.
How to Explain It to a Client
Sooner or later a client asks where their code goes while you work. Answer in three plain sentences and then show the file. Claude Code runs on my machine, in the desktop app, in your project folder. Every command it runs is fenced by the operating system to that folder and to a short list of internet hosts I will show you, and my other clients' projects and my own credentials are blocked from it by a configuration file I commit into your repository, so the fence travels with the code. Then open .claude/settings.json and read the four pieces out loud. A client who understands the read block and the domain list stops worrying, because the answer is specific and they can see it.
Be equally plain about what the fence does not change. The code Claude reads to do the work is sent to Anthropic's API; that is how the model works, and the sandbox governs commands on your machine, not that traffic. That belongs in the [expectations conversation before the build](/blog/set-client-expectations-before-a-claude-code-build) and in the agreement, in writing. Clients do not leave over a clear boundary. They leave over a boundary they discover later.
Then make it part of what you deliver. The committed settings file, the domain allowlist, the excluded-commands list with a one-line reason per entry, and the credential deny list go in the handoff doc next to the security review, and when the client's own developer opens the repo with Claude Code, the fence is already up. It costs nothing, it takes ten minutes per project, and it is the most concrete answer I have to the question every careful client eventually asks.
Short, practical drops on offers, outreach, pricing, and closing clients with Claude Code. No spam, unsubscribe anytime.
Frequently asked
What is Claude Code sandboxing?
It is the built-in Bash sandbox in Claude Code. Instead of approving each shell command, you define which files and network domains commands can touch, and the operating system enforces that boundary on every Bash, PowerShell, or Monitor command and its child processes. By default a sandboxed command can write only to the working directory, added directories, and a per-user temp directory, and can reach no network domain until you allow it. On macOS it uses Seatbelt with nothing to install; on Linux and WSL2 it uses bubblewrap and socat. Native Windows is not supported.
Does the sandbox work in the Claude Code desktop app?
Yes. The sandbox is part of Claude Code and applies to local sessions on macOS, Linux, and WSL2. The difference in the desktop app is how you configure it: the docs say the built-in commands that open a terminal dialog behave differently in the Code tab and to edit settings files directly for permission rules and configuration. Put the sandbox keys in the project's .claude/settings.json so the fence is committed with the client's repo, or in ~/.claude/settings.json to enable it for every project.
Does sandboxing replace permission rules and permission modes?
No. They are complementary layers. Permission rules decide which tools may run and are evaluated before a command runs, based on the command string. The sandbox restricts what a shell command can access once it is running, enforced by the operating system. The sandbox's auto-allow mode is also separate from auto mode: auto-allow approves a command because the boundary contains it, while auto mode uses a classifier to review actions. They can be combined, and explicit deny rules and content-scoped ask rules such as Bash(git push *) still apply to sandboxed commands.
Can a client's repository settings weaken my sandbox?
Only in narrow ways. A repo's .claude/settings.json can narrow the sandbox but cannot turn filesystem isolation off, add credential mask entries, or enable Apple Events; those keys are honored only from user settings, managed settings, or the --settings flag. Array keys merge across scopes, so a project file can add excludedCommands entries that run specific commands outside the sandbox. Read that list before you trust a repo you did not write, and keep your own credential deny entries in your user settings, where no project can remove them.
What does the Claude Code sandbox not protect against?
The docs are direct: it reduces risk but is not a complete isolation boundary. It covers Bash, PowerShell, and Monitor commands only, not the Read, Edit, and Write tools. Sandboxed commands inherit your environment variables, including credentials, unless you deny or mask them. The proxy allows by hostname and by default does not inspect TLS, so broad domain allowlists can create exfiltration paths. Commands you type at the ! shell prompt run outside the sandbox in a normal session. And it does not change the fact that the code Claude reads is sent to Anthropic's API to do the work.
Last reviewed September 27, 2026.

Co-founder of the Claude Code Profit Room. Went from shipping software to closing paying clients, and now teaches builders the selling half of the equation.
More from David Iya →