[BUG] PreToolUse hook `permissionDecision: "ask"` is overridden by `auto` mode for benign `dangerouslyDisableSandbox` commands (contradicts documented hook precedence)
Summary
In auto permission mode, a PreToolUse hook that returns permissionDecision: "ask" for a Bash call with dangerouslyDisableSandbox: true is silently overridden to auto-allow when a background safety classifier judges the command benign. No permission prompt appears and the command runs outside the sandbox with no user review, even though the hook explicitly returned ask. Some commands (a destructive verb like rm, or reading a system file such as /etc/hosts) do surface the hook's ask, so the classifier — not the hook — is deciding whether the escape is reviewed. The classifier's criteria are opaque and not simply about filesystem scope: a bare echo runs silently, but so does gh issue view … --json — a command that reads a GitHub token from the macOS Keychain and makes a network call, both outside the project. This contradicts the documented guarantee that a hook's permissionDecision takes precedence over the permission mode "regardless of the mode", and it means a user-authored safety hook cannot reliably force review of a sandbox escape — including escapes that read credentials and reach the network.
Environment
- Claude Code version:
2.1.201 - Platform: macOS (Darwin 24.6.0), Apple Silicon
- Model: Claude Opus 4.8
- Permission mode:
auto(permissions.defaultMode: "auto") - Sandbox: enabled, with
allowUnsandboxedCommands: true
Reproduction
1. Settings (.claude/settings.json) — auto mode plus a PreToolUse hook on Bash:
{
"permissions": { "defaultMode": "auto" },
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [
{ "type": "command", "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/ask-on-unsandboxed.sh\"" }
] }
]
},
"sandbox": { "enabled": true, "allowUnsandboxedCommands": true }
}
2. Hook (.claude/hooks/ask-on-unsandboxed.sh) — return ask for any unsandboxed command:
#!/usr/bin/env bash
val="$(jq -r '.tool_input.dangerouslyDisableSandbox // false')"
if [ "$val" = "true" ]; then
jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"ask",permissionDecisionReason:"Runs OUTSIDE the sandbox — review before allowing."}}'
fi
exit 0
3. Have the agent run three Bash calls, each with dangerouslyDisableSandbox: true:
| Probe | Command (with dangerouslyDisableSandbox: true) | Prompt shown in auto? |
| --- | --- | --- |
| A | echo "hi" — no filesystem or other side effects | No — runs silently |
| B | rm -f "$PROJECT/nonexistent" — destructive verb, target inside the project | Yes |
| C | cat /etc/hosts — read a path outside the project | Yes |
| D | gh issue view N --repo … --json … — reads a Keychain token + network call, outside the project | No — runs silently |
The hook returns ask for all four (verified: it fires and emits the JSON in every case). Only probes B and C actually prompt; probes A and D are auto-approved and execute unsandboxed with no prompt. Probe D is the most concerning: a command that reads credentials and makes a network request is classified benign and escapes the sandbox unreviewed, despite the hook's ask. The gate is clearly a semantic classifier rather than a filesystem-scope rule — C (a local read of a public system file) prompts, while D (credentials + network) does not.
Observed vs. expected
- Observed: Probe A runs outside the sandbox with no permission prompt; the hook's
askis discarded. - Expected: Per the documented precedence, the hook's
askshould surface a prompt for every unsandboxed command in every mode, includingauto.
Documentation this contradicts
Hook decisions take precedence over the mode — hooks.md:
Your hook's permissionDecision takes precedence over the user's permission mode setting, allowing you to enforce stricter policies regardless of the mode.
The documented precedence chain (permissions.md) is: deny/ask rules > hook decision > permission mode. There are no ask/deny rules matching these probes, so the hook's ask should beat auto mode. It doesn't for probe A.
Unsandboxed escapes require approval — sandboxing.md:
The retried command runs outside the sandbox, so it goes through the regular permission flow and requires your approval.
Probe A ran outside the sandbox with no approval. (auto is documented as "auto-approves tool calls with background safety checks", so one might argue the classifier satisfies this clause — but that cannot excuse the hook-precedence violation above.)
Undocumented gap: none of hooks.md, permissions.md, or sandboxing.md describes how a PreToolUse hook interacts with the dangerouslyDisableSandbox retry path. The observed behavior (classifier gating the escape approval and overriding the hook) is unspecified.
Additional notes
- A hook
denyis honored on the escape path inautomode (the command is blocked), and hookaskis honored for a normal sandboxed command inautomode (it prompts). Onlyaskon thedangerouslyDisableSandboxpath is subject to the classifier. So the failure is specific to the intersection ofask+ escape path +automode. - Impact is safety-reducing: a user-authored hook intended to force review of every sandbox escape cannot do so; inert-looking commands escape the sandbox unreviewed. The only reliable workaround is to return
deny(blocking the escape hatch entirely) or setsandbox.allowUnsandboxedCommands: false.
Related issues
- #42797 — Auto-mode ignores
permissions.ask(settings rules, not hooks; no sandbox angle) — same spirit, different mechanism. - #51798 — hook
allowno longer suppresses the unsandboxed prompt (the inverse direction on the same path; closed as not planned). - #43713 —
autoAllowBashIfSandboxedbypassed for shell expansions (confirms a classifier gates sandbox-related auto-approval). - #39344 — hook
asksilently overridespermissions.deny(hookaskinteracting wrongly with the permission system).
3 Comments
Follow-up: everyday
git/ghcommands also escape unprompted — including a write to a sandbox-denied pathMore data on which unsandboxed commands
automode's classifier waves through without honoring a PreToolUse hook'sask. Beyond theechoandgh issue viewprobes in the original report, two ordinary dev commands also run outside the sandbox with no prompt:git push— reads a credential from the macOS Keychain and makes a network call; runs unprompted.git config lfs.locksverify false— writes to.git/config, a path the sandbox is explicitly configured to deny writes to (that denial is precisely why the command needsdangerouslyDisableSandboxat all). The escape runs unprompted, silently defeating the sandbox's own protected-path guarantee.Consolidated with the earlier probes (all with
dangerouslyDisableSandbox: true, hook returningaskin every case):| Unsandboxed command | What it touches | Prompt in
auto? || --- | --- | --- |
|
echo …| nothing | No — silent ||
gh issue view …| Keychain token + network | No — silent ||
git push| Keychain + network | No — silent ||
git config lfs.locksverify false| writes.git/config(a sandbox-denied path) | No — silent ||
cat /etc/hosts| reads a public system file | Yes — prompts ||
rm -f <path>| deletion | Yes — prompts |The gate looks like an intent classifier: normal dev-workflow commands (
git,gh) are treated as benign even when they read credentials, hit the network, or write protected git internals, while deletion or system-file reads are flagged. So it keys on the command's apparent intent, not what it actually accesses — which is why a user-authored hook returningaskcannot be relied on to surface a sandbox escape for review. Thegit configcase is the sharpest: an escape writing a path the sandbox is actively deny-listing runs with no prompt at all.Environment unchanged from the report: Claude Code 2.1.201, macOS,
permissions.defaultMode: auto.The key invariant is simpler than command classification: dangerouslyDisableSandbox=true is itself the privileged action. If a hook asks for review of that capability transition, a later benign-command classifier should not erase the request.
I would model this as two independent decisions: may this command run, and may it cross the sandbox boundary? The second decision should be bound to the exact tool call and remain visible in the audit record even when the first is auto-allowed. Credential-backed network tools such as git push and gh show why verb-level benignness is not a sound proxy for sandbox-escape risk.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.