[BUG] Workspace sandbox config silently dropped for sessions/subagents rooted in nested project directories (sandbox escape)
Description
A workspace-level sandbox configuration in .claude/settings.local.json is silently not applied to sessions (including subagents spawned via the Agent tool) whose project root is a nested project directory that has its own .claude/settings.local.json without a sandbox key. The nested file completely replaces the parent's sandbox config instead of inheriting or merging it, so the strictest possible parent sandbox ("enabled": true, "allowUnsandboxedCommands": false, tight network allowlist) is a no-op for any work delegated into the child project.
This is the sandbox-enforcement variant of #20218 (closed/locked, filed about permission allowlists shadowing). Related sandbox-escape class: #40831, #70711.
Steps to Reproduce
- Workspace layout: a container directory with a nested repo, each with its own settings:
````
workspace/.claude/settings.local.json ← sandbox: enabled=true, allowUnsandboxedCommands=false,
network.allowedDomains WITHOUT host X
workspace/repo/.claude/settings.local.json ← permissions only, NO "sandbox" key
- Start a session rooted at
workspace/. Runssh <host-X> hostname→ correctly blocked:ssh: connect to host <host-X> port 22: Operation not permitted(seatbelt). This holds even when the shell hascd'd intoworkspace/repo/— the session's project root decides which settings load, not the shell cwd. ✓ expected. - From that same session, spawn a subagent (Agent tool) whose working directory is
workspace/repo/, or simply start a new session inworkspace/repo/. Run the identicalssh <host-X> hostname.
Expected Behavior
The parent workspace's sandbox restrictions apply (inherit/merge), or at minimum the user is warned that entering the nested project drops the ancestor sandbox entirely — matching the resolution direction requested in #20218 for permissions.
Actual Behavior
The command runs completely unsandboxed and succeeds. The nested project's settings file (no sandbox key) silently replaces the ancestor's sandbox config. Verified via the subagent's transcript (filed under the nested project in ~/.claude/projects/): plain ssh <host-X> … Bash calls executed with full network access that the parent workspace sandbox explicitly denies.
Security Impact
- A workspace sandbox is not a boundary as soon as any nested directory is a Claude project without its own sandbox block — which is the default state of every nested repo, since
"Yes, and don't ask again"creates permissions-onlysettings.local.jsonfiles (#20218's mechanism). - Subagents are the common case, not the edge case: delegated agents naturally root themselves in the nested repo they're asked to work on, so exactly the least-supervised work runs with the weakest restrictions.
- The failure is silent. The operator sees the main session correctly blocked and reasonably concludes the sandbox binds; nothing indicates delegated work escaped it. We discovered it only by noticing a subagent succeeding at a command the main session could not run, then diffing the two projects' settings.
Suggested Direction
Inherit-and-merge sandbox config from ancestor project settings (nested files may tighten, not drop, restrictions), or treat a missing sandbox key in a nested project as "inherit nearest ancestor's" rather than "none". Failing that, a loud warning at session/subagent start when an ancestor sandbox config is being discarded.
Environment
- Claude Code v2.1.220
- macOS 26.5.2 (Darwin 25.5.0)
- Sandbox: macOS seatbelt, workspace config with
enabled: true,autoAllowBashIfSandboxed: true,allowUnsandboxedCommands: false,network.allowedDomainsallowlist
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗