PreToolUse hook permissionDecision 'ask' silently overrides permissions.deny rules
Summary
A PreToolUse hook returning permissionDecision: "ask" silently disables permissions.deny rules in settings.json, allowing denied commands to execute without any prompt. This is a security bypass — deny rules should always take precedence over hook "ask" decisions.
Severity
Critical — a hook can silently override security deny rules. Any PreToolUse hook that returns "ask" for a command that matches a deny rule will cause the deny rule to be ignored entirely. The command executes without even prompting the user.
Reproduction
Prerequisites
- Claude Code v2.1.84
- A
settings.jsonwith a deny rule and a PreToolUse hook
Step 1: Baseline — deny rule only (no hook)
settings.json:
{
"permissions": {
"deny": ["Bash(printf REPRO42*)"]
}
}
Start a new session, ask Claude to run printf REPRO42.
Result: DENIED ✅ — deny rule works correctly.
Step 2: Bug — deny rule + hook returning "ask"
Add a PreToolUse hook that returns permissionDecision: "ask" for the same command:
Hook script (/tmp/repro-hook.sh):
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
[ "$TOOL_NAME" != "Bash" ] && exit 0
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
case "$CMD" in
printf\ REPRO42*)
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"ask","permissionDecisionReason":"repro test"}}'
exit 0
;;
esac
exit 0
settings.json:
{
"permissions": {
"deny": ["Bash(printf REPRO42*)"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash /tmp/repro-hook.sh"
}
]
}
]
}
}
Start a new session, ask Claude to run printf REPRO42.
Result: EXECUTED ❌ — prints REPRO42. The deny rule is completely bypassed.
Step 3: Control — hook returning "deny" works
Change the hook to return "deny" instead of "ask".
Result: DENIED ✅ — confirms the hook is running and "deny" works.
Expected behavior
When a command matches a permissions.deny rule, it should always be denied regardless of what any hook returns. The evaluation order should be:
- Check
permissions.deny— if matched, deny (non-overridable) - Run hooks — apply hook decisions
- Check
permissions.allow/permissions.ask
A hook returning "ask" should only be able to escalate an otherwise-allowed command to prompt — it should never be able to downgrade a denied command.
Actual behavior
Hook permissionDecision: "ask" overrides the deny rule entirely. The command executes without any prompt or denial.
Environment
- Claude Code v2.1.84
- macOS Darwin 25.2.0
- Model: claude-opus-4-6 (1M context)
Related
- #18312, #22018, #36059, #37420
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗