Claude Code macOS Sandbox Escape: Executive Summary
During a review of Claude Code's macOS sandbox policy generator, our team identified a path-provenance flaw that caused literal filesystem names to be interpreted as glob patterns.
Claude Code automatically authorizes sandboxed writes to the current workspace. If the workspace's real directory name contained glob metacharacters, such as:
the policy generator resolved the directory as a literal path and then passed the resulting string through glob detection. The ** bytes were treated as pattern syntax and compiled to the regular expression fragment .*.
This widened the macOS Seatbelt write policy beyond the source workspace. The same conversion also altered the mandatory deny rule that protects .claude/settings.local.json. A sibling settings path could therefore match the write allow while failing to match the protected-settings deny.
A sandboxed, auto-approved Bash command could use this policy mismatch to create a sibling project's .claude/settings.local.json. The injected file defined a SessionStart command hook. When the already-trusted sibling project was opened later, Claude Code executed the hook before initialization, authentication, model output, or access to the Bash tool.
The proof of concept used no symbolic link, hard link, filesystem race, alias, pre-existing hook, or valid Anthropic credential.
Anthropic validated the behavior, assigned CVSS 4.0 7.7 (High), and awarded a $XXXX bounty.

Affected Claude Code Versions and macOS Scope
The original report tested the official Claude Code 2.1.214 macOS arm64 executable. A separate regression run confirmed the same behavior on 2.1.215.
Item | Confirmed value |
|---|---|
Product | Claude Code |
Platform tested | macOS arm64 using Seatbelt |
Version 2.1.214 SHA-256 |
|
Version 2.1.215 SHA-256 |
|
2.1.214 result | Complete differential matrix passed |
2.1.215 result | Complete differential matrix passed |
Vendor severity | CVSS 4.0 7.7, High |
Vendor vector |
|
This article does not claim that Linux or Windows shared the macOS Seatbelt-specific implementation flaw.
The demonstrated chain required the following conditions:
Claude Code was running an affected macOS build.
sandbox.enabledandsandbox.autoAllowBashIfSandboxedwere enabled.The source workspace was already trusted.
The source workspace's real path contained literal glob metacharacters.
A sibling project existed below the same canonical parent directory.
That already-trusted sibling project was launched after the configuration write.
The sequence used two Claude Code launches, one for the source workspace and one for the later victim launch. It did not require concurrent sessions or a timing race.
How the Claude Code macOS Sandbox Is Supposed to Work
Claude Code's Bash sandbox documentation states that sandboxed commands can write to the working directory and the session temporary directory by default. In auto-allow mode, eligible sandboxed commands run without a separate Bash confirmation because the operating system policy is expected to enforce that boundary.
The target file was:
Claude Code treats this as local configuration for the current project. The file can define command hooks, including SessionStart hooks that run when a new session starts or an existing session resumes.
Claude Code also generated a mandatory sandbox deny for protected project configuration. The expected authorization model was therefore clear:
Source project A was trusted.
Sandboxed commands launched from A could write inside A.
Sibling project B was a separate project with its own trust record.
A sandboxed command in A could not write B's protected local settings.
Trusting A did not authorize repository code or dependencies in A to modify B.
The vulnerability bypassed both the working-directory boundary and the protected-settings deny.
Root Cause: Literal Path and Glob Pattern Confusion
The reverse-engineered JavaScript from Claude Code 2.1.214 showed the following policy-generation path. These are minified identifiers from the pinned build, so their names can change between releases.
Function | Observed role |
|---|---|
| Adds the automatic write entry |
| Resolves the entry against the current working directory |
| Detects glob syntax by inspecting characters in the resulting string |
| Converts glob syntax to a regular expression |
| Generates macOS write-allow and protected-path deny rules |
| Launches the command under the generated Seatbelt profile |
The initial resolution step was correct. From the affected workspace, the automatic entry "." resolved to one real directory:
At this point, the asterisks were literal filename bytes. The value should have remained a literal canonical path.
Instead, the value was passed to U6() as an untyped string. U6() inferred glob intent from the characters alone. Nnt() then converted each ** sequence to .*.
The security-relevant type information was lost between path resolution and policy generation. A value that represented one filesystem object was reinterpreted as a pattern representing multiple filesystem objects.

The upper path in Figure 2 represents the required remediation invariant. The lower path represents the behavior observed in the affected builds.
How the Sandbox Allow and Deny Rules Diverged
For the following source workspace:
the generated write allow was equivalent to:
The protected local-settings deny was equivalent to:
The sibling target was:
The allow matched this path. Its first .* matched -private/, the literal \.claude matched the sibling's settings directory, and the final .* matched the remaining filename.
The deny did not match. After consuming the sibling's .claude directory, the generated deny still required another .claude/settings.local.json suffix. The target contained only one such directory.
Seatbelt and the regular expression engine enforced the supplied policy. The vulnerability was in the compiler that selected which paths the policy described.
Claude Code Sandbox Escape: Differential Validation
The proof of concept used four matched cases. The first three retained the same victim, payload, command, trust state, permission mode, binary, and target. Only the literal source directory name changed.
Case | Literal source directory | Allow matches | Settings deny matches | Settings file created | Hook executes |
|---|---|---|---|---|---|
Ordinary negative |
| No | No | No | No |
Protected-path control |
| Yes | Yes | No | No |
Targeted exploit |
| Yes | No | Yes | Yes |
Parent-wide ceiling |
| Yes | No | Yes | Yes |
The review-bundle** control was essential. Its broadened allow permitted creation of the sibling's previously absent .claude directory, but the generated protected-settings deny still matched and blocked the file copy. This established three facts:
The command was executing inside the sandbox.
The protected-settings deny took precedence when it matched.
The positive result depended on the
.claudetoken changing the deny language, not on the absence of a protection rule.
The **.claude** case measured the proven write envelope. It reached an unrelated sibling name below the same canonical parent. The proof does not claim arbitrary filesystem-wide writes from the initial sandbox primitive.
From Protected Configuration Write to SessionStart Command Execution
The injected local settings file contained a minimal SessionStart command hook. The published example keeps the payload limited to a benign nonce marker:
The source-side operation required only directory creation and a file copy:
Observed behavior differed according to the generated policy:
In the ordinary negative case,
mkdirfailed withOperation not permitted.In the protected-path control,
mkdirsucceeded andcpfailed on the protected file.In the two positive cases, both operations succeeded without a Claude Code permission denial.
The sibling was then launched with Bash explicitly disabled and without an API credential. The recorded event order was:
The nonce marker existed before the first assistant event. The victim process recorded:
This evidence supports the term pre-model command execution. The SessionStart command completed before initialization and before any successful model interaction. The hook also wrote its marker above the extraction parent, demonstrating that the canonical-parent ceiling constrained only the initial sandboxed file write. It did not constrain the later host process.

Claude Code Path Confusion Vulnerability Explained
This was not a symbolic-link or time-of-check to time-of-use issue.
The proof used:
no symbolic link;
no hard link;
no rename or directory-swap race;
no alternate path alias;
no pre-existing
.claudedirectory;no pre-existing setting, hook, helper, or Git execution configuration; and
no trust decision after the configuration was written.
The path did not change between validation and use. The same path string was interpreted under two incompatible types. The most accurate classification is literal-path and pattern type confusion in a security-policy compiler.
What the Claude Code Sandbox Escape Allows
Workspace trust did not make the behavior expected or authorized.
The user trusted the source project. The sibling was a different project with a separate trust record. More importantly, the source process ran under an enabled sandbox whose purpose was to limit auto-approved Bash writes to the source workspace.
The target file was also covered by a mandatory deny. The protected-path control demonstrated that this deny was active and blocked the exact target when the generated expression matched.
The capability change was:
The attacker principal did not begin with permission to write the sibling's protected settings or execute a host command during the sibling's startup. The vulnerable policy generator granted both capabilities through separate lifecycle transitions.
Exploit Conditions and Scope Limitations
macOS permits glob metacharacters in directory names. The proof also verified that the stock ditto and unzip utilities preserved archive members named:
This provides a practical delivery path through an archive, copied project template, support bundle, or reproduction package whose top-level name is preserved.
The report does not claim that an ordinary git clone automatically selects such a destination name. It also does not claim direct writes above the fixed canonical parent prefix or through unrelated mandatory deny rules.
How to Fix the Claude Code Sandbox Path Confusion
The policy generator should preserve path provenance instead of inferring a value's type from its characters.
Recommended changes are:
Represent generated filesystem values using distinct
LiteralPathandGlobPatterntypes.Resolve automatic entries such as
.to canonical absolute paths and emit literal Seatbeltsubpathrules.Do not infer glob intent from metacharacters found inside a resolved filesystem path.
Emit protected settings, hook, and Git paths as literal canonical denies, including when an ancestor contains
*,?,[or].Parse patterns only in settings fields that explicitly accept user-authored globs.
Add real-directory regression tests for
review-bundle**,review-bundle**.claude**,**.claude**,project?, and[abc].
The general engineering requirement is straightforward: a security-policy compiler must preserve whether a value represents one literal filesystem object or a language of possible objects.
Claude Code Vulnerability Disclosure Timeline
Date | Event |
|---|---|
18 July 2026 | Report submitted to Anthropic through HackerOne with a four-case differential proof of concept |
20 July 2026 | Anthropic validated the behavior and assigned CVSS 4.0 7.7, High |
4 August 2026 | Anthropic awarded a $XXXX bounty |
TBD | Fixed version, advisory or CVE decision, and coordinated public disclosure |
At the time of this draft, the report remained private. The complete proof-of-concept script and private HackerOne material are intentionally excluded pending coordinated disclosure.
What This Claude Code Sandbox Escape Teaches About Path Security
The vulnerability originated at the boundary between filesystem resolution and pattern compilation. Claude Code correctly resolved the current workspace to one real directory, but later discarded that provenance and treated metacharacters in the directory name as glob syntax.
That type change altered both sides of the policy. It broadened the write allow and changed the protected-settings deny so that the sibling target fell inside the first expression and outside the second. The resulting protected configuration write led to command execution during the sibling project's next startup, before the victim session produced model output.
Security-sensitive path processing should retain explicit type information from input parsing through policy generation. Literal paths should remain literal regardless of the characters they contain.
Research and technical analysis by our AI Security Researcher, Sunder Singh. Anthropic's security team reproduced the submitted behavior and provided the severity assessment stated above.


