Claude Code refused to return a file through its Read tool, then returned the same contents through an allowed Python command. We saw both results in one session running version 2.1.311, with a matching Read deny rule and the shell sandbox switched off. The file held a random dummy value.
That difference matters when you want Claude Code to run a project’s tests while keeping a local .env private. Permission rules govern whether tool actions proceed. The shell sandbox restricts what the processes it wraps can access. Their coverage overlaps, but an approval prompt or a rejected file read does not establish that credentials are protected. Security architecture
Where Read rules stop and the Bash sandbox begins
A file can reach a session through the built-in Read tool or through a program running in Bash. Review both routes.
Read rules cover the Read tool and certain recognized Bash reads. Anthropic documents checks for commands such as cat and for input redirections, the latter requiring v2.1.257 or later. They do not cover arbitrary unsandboxed subprocess reads. File-rule coverage · Redirections
The built-in sandbox is off by default, and its default read policy is broad: enabling it alone does not protect .env. The resolved sandbox incorporates Read and Edit deny rules, but sandbox.filesystem.denyRead alone does not block the built-in Read tool. Sandbox configuration
Permission mode determines how approvals are handled; it does not by itself establish process isolation. The built-in shell sandbox supports macOS, Linux, and WSL2, but not native Windows. A Windows host needs WSL2 for that sandbox or a separate container or VM boundary. Permission modes · Supported environments
What Claude Code 2.1.311 did in our tests
We ran the official CLI on Ubuntu 24.04.3 LTS as UID 0 on 28 October 2026 (JST). Its Read and Bash tools were real; a deterministic localhost API stub supplied predefined tool requests and recorded their results. No language model performed inference. The network namespace had no external route, and the files and API credential contained dummy values.
Sessions used --bare -p, a fresh CLAUDE_CONFIG_DIR, an explicit file supplied through --settings, and --setting-sources "". We allowed Read and the exact Bash invocations. Most runs used --permission-mode dontAsk; the retry comparison used default. Permission checks stayed enabled. These runs tested tool enforcement, without testing model judgment, Auto mode, or interactive approvals. Programmatic and bare mode
The ordinary Linux sandbox failed to mount a fresh /proc. Both permitted and protected Bash reads failed before the reader ran, so that result could not demonstrate file protection.
The working sandbox cases below used sandbox.enableWeakerNestedSandbox: true, which reuses the outer container’s /proc. Anthropic describes this as weaker isolation. These direct-path checks do not validate the ordinary sandbox, alternate paths through /proc, or complete confidentiality. Treat this setting as a limitation of the experiment, not a production recommendation. Container limitation
Each case had two files containing distinct, freshly generated dummy values. Before applying restrictions, we confirmed that the same Python reader could retrieve both. It could still read the permitted file in every row below.
| Tested configuration | Read tool: protected file | Python through Bash: protected file |
|---|---|---|
| Sandbox off; no Read deny | Returned contents | Returned contents |
| Sandbox off; matching Read deny | Rejected by permission policy | Returned contents |
Weaker nested sandbox; filesystem.denyRead; no Read deny | Returned contents | PermissionError (errno 13) |
Weaker nested sandbox; matching Read deny; no explicit filesystem.denyRead | Rejected by permission policy | PermissionError (errno 13) |
Weaker nested sandbox; filesystem.disabled: true; no Read deny | Returned contents | Returned contents despite the configured sandbox read-deny path |
Command spelling changed the result
With the sandbox off, a matching Read deny, and exact Bash allow rules, cat PATH, cat < PATH, and /bin/cat < PATH were rejected for the protected file. /bin/cat PATH and the Python reader returned its contents.
Those results apply to the invocations, rules, and CLI version we tested. A rejected cat command is therefore insufficient evidence that an arbitrary approved program cannot read the file.
Retries, exclusions, and missing dependencies
We supplied Bash’s dangerouslyDisableSandbox: true input directly to an already-allowed command. With allowUnsandboxedCommands: false, the protected read still failed under the filesystem restriction. With it true, the command retrieved the dummy contents. This comparison did not exercise an automatic retry sequence or interactive approval. An exclusion supplied through --settings also allowed the read while retries were disabled.
A dummy environment variable remained readable inside the tested sandbox. A one-test Python suite using only the permitted fixture passed. This was a minimal workload; it says nothing about compatibility with a production application’s tests.
When bwrap was absent from the test PATH, failIfUnavailable: true stopped startup with exit code 1 and no tool execution. With the setting false, the CLI warned that sandboxing was disabled, and the approved reader retrieved both files. This establishes the behavior for a missing dependency, not every failure that might occur later.
We retained settings, invocations, raw CLI output, and tool results. Documentation was checked on 28 October 2026 (JST). The runs did not test macOS, WSL2, hooks, MCP servers, provider data handling, prompt-injection resistance, or the CLI sandbox’s network filtering. The outer network namespace’s lack of an external route cannot establish how the inner sandbox filters traffic.
Check the settings Claude Code actually loaded
An apparently restrictive project file may be only one contribution to the effective policy. Claude Code loads user, project, and managed settings; lists such as permission allowlists can merge. An empty list in one file does not prove the final list is empty. Settings precedence
Record the CLI version, operating system, working directory, and permission mode. Use /status to identify loaded settings sources, recognizing that it does not report every setting’s provenance. Inspect the resolved policy through /sandbox when available; otherwise obtain it from your administrator. Permission patterns and sandbox paths use different syntax, so check the reference before copying a path between them. Loaded sources · Permission paths · Sandbox paths
Review these settings together. The keys below are under sandbox:
| Setting | What to check |
|---|---|
enabled | Whether the command sandbox is enabled |
filesystem.disabled | Whether filesystem isolation has been removed |
failIfUnavailable | Whether unavailable prerequisites must prevent startup |
allowUnsandboxedCommands | Whether an unsandboxed retry can be requested |
excludedCommands | Which effective exclusions allow commands to run outside the sandbox |
Disabling retries does not eliminate effective exclusions. Settings sources matter: our exclusion and filesystem-disable tests used CLI --settings, not project files. failIfUnavailable covers startup checks and specified later failure conditions; a later command failure does not necessarily end the session. Anthropic documents that filesystem.disabled retains network isolation, but our tests checked its file-access effect only. Settings and exceptions
Verify the intended policy with two dummy files
Use a disposable project with scripts you have reviewed. Keep production credentials out of the environment: a new project directory does not isolate credentials elsewhere on the machine. File restrictions also do not remove secrets inherited through environment variables. Credential controls
Create a permitted file and a protected file, each with a distinct random dummy value. Before applying restrictions, confirm that your reader retrieves both. Keep the protected value out of the prompt and leave permission checks enabled.
Write down the expected outcomes, then check them:
- Use the built-in Read tool on the permitted file. It should return the dummy value.
- Use Read on the protected file. The intended permission rule should reject it.
- Run the reviewed subprocess against the permitted file. It should return that value.
- Run the same subprocess against the protected file. The intended restriction should prevent access to its original contents.
- Run the reviewed test suite. It should pass using its permitted data.
Inspect the tool that actually ran, its output, and its exit status. A model declining to act does not exercise the control. Neither does a missing interpreter, a wrong path, or a broken script. Empty output alone is inconclusive; establish which restriction caused it and confirm the permitted read still works.
Our restricted Python reads produced PermissionError. Do not assume every environment returns the same error. Also inspect individual tool results: a CLI session can finish with exit code 0 after a tool action fails.
Keep the configuration fixed. If a check asks to run outside the intended boundary, decline that change and record it. Save the version, sanitized settings, exact commands, expected outcomes, and actual results. Remove credentials, sensitive paths, and unrelated session content before sharing the record. Passing these checks establishes behavior for the operations you exercised, not universal containment.
The shell sandbox does not cover the whole session
The built-in shell sandbox wraps shell commands and their children. Built-in file tools, local MCP processes, and hooks sit outside that per-command boundary. An outer environment can include those local components, provided its mounts, credentials, and network access are configured for the work. Environment comparison
A remote MCP server remains outside a local container. Review the service account, permissions, and data handling separately. MCP connections
Claude Code also sends model requests to its configured provider. Local isolation does not make that processing local or determine retention and training policies; consult the terms for your account and provider before exposing sensitive material. Data usage
For the test-running task, keep the acceptance criteria concrete: the reviewed tests pass, and the protected contents stay unavailable through the routes checked. Recheck those routes when the CLI, configuration, or extensions change.
Sources & scope
Official documentation and controlled Claude Code 2.1.311 executions on Ubuntu. A localhost stub supplied tool requests without model inference. Working sandbox comparisons used weaker nested mode; the ordinary sandbox was blocked by the container environment. Methods and limits are described above.
- Anthropic: Security
- Anthropic: Choose a permission mode
- Anthropic: Configure permissions
- Anthropic: Configure the sandboxed Bash tool
- Anthropic: Choose a sandbox environment
- Anthropic: Settings files and precedence
- Anthropic: Connect Claude Code to tools via MCP
- Anthropic: Data usage
- Anthropic: Run Claude Code programmatically
- Anthropic: Claude Code v2.1.311 release
Publishing details & corrections
Prepared with AI assistance. Sources, any tests performed, and their limitations are described in the article.
No material corrections recorded for this version.