Sandbox denyWrite/allowWrite silently ignores misconfigured path prefixes — no validation warning
Bug Description
When configuring sandbox.filesystem.denyWrite (or allowWrite), paths that use the wrong prefix resolve to non-existent locations and are silently ignored. No warning is emitted that the deny/allow rule is effectively a no-op.
This is a security concern: users configure deny rules believing they are protected, but the rules match nothing because the resolved path doesn't exist.
The Problem
Claude Code supports 4 path prefixes:
| Prefix | Meaning | Example |
|--------|---------|---------|
| // | Absolute from filesystem root | //Users/me/.ssh → /Users/me/.ssh |
| ~/ | Relative to home directory | ~/.ssh → /Users/me/.ssh |
| / | Relative to settings file directory | /.ssh → <project>/.claude/.ssh |
| ./ | Relative to CWD | ./.ssh → <cwd>/.ssh |
The natural instinct is to write absolute paths with a single /:
{
"sandbox": {
"filesystem": {
"denyWrite": [
"/Users/me/.ssh",
"/Users/me/projects/my-repo/.claude/settings.json"
]
}
}
}
This resolves to:
<project>/.claude/Users/me/.ssh— doesn't exist, rule is a no-op<project>/.claude/Users/me/projects/my-repo/.claude/settings.json— doesn't exist, rule is a no-op
The correct syntax requires //:
"denyWrite": ["//Users/me/.ssh"]
There is no warning, error, or log entry when a deny/allow rule resolves to a non-existent path. The sandbox loads the configuration, resolves the paths, and silently discards rules that don't match anything.
Security Impact
High. Users who configure denyWrite rules expect those paths to be protected. When the wrong prefix is used:
- The rule resolves to a non-existent path
- No warning is shown
- The sandbox reports as "enabled" and "active"
- The user has a false sense of security — they believe writes to sensitive paths are blocked
- Any subprocess can freely write to those paths
This is especially dangerous for deny rules protecting security-critical files like:
.claude/settings.json(could disable sandbox)- Hook scripts (could remove security checks)
- SSH keys, credentials, etc.
Proposed Fix
At sandbox initialization, validate every path in denyWrite, denyRead, and allowWrite:
- Resolve the path using the prefix rules
- Check if the resolved path exists on the filesystem
- If it does NOT exist, emit a visible warning at startup:
````
⚠️ Sandbox config warning: denyWrite path "/Users/me/.ssh" resolves to
"<project>/.claude/Users/me/.ssh" which does not exist.
Did you mean "//Users/me/.ssh" (absolute path)?
- Optionally: if a
/-prefixed path looks like an absolute path (starts with/Users/,/home/,/etc/,/var/, etc.), suggest the//prefix automatically
This would catch misconfiguration immediately rather than letting users run for weeks with broken deny rules.
How We Found This
We spent significant time debugging why denyWrite wasn't blocking writes to protected paths. The configuration looked correct at first glance — paths started with / which intuitively looks like an absolute path. After reverse-engineering the binary, we found resolvePathPatternForSandbox() which treats single / as relative to the settings file directory, not as absolute. The fix was changing all paths to use // prefix. But there was zero indication that the original paths were wrong.
Environment
- Claude Code: v2.1.71
- OS: macOS 26.1 Tahoe (arm64)
🤖 Generated with Claude Code
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗