48 lines
1.7 KiB
Bash
Executable File
48 lines
1.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# PreToolUse hook for the Bash tool. Does NOT silently block anything —
|
|
# per design, it only ever forces the normal confirmation prompt
|
|
# (permissionDecision: "ask") when a Bash command contains shell chaining
|
|
# operators. It never returns "deny" and never returns "allow" itself; it
|
|
# either stays out of the way (no JSON output) or asks. This exists as a
|
|
# safety net against Claude Code's Bash allow-list not always splitting
|
|
# compound commands the way the docs describe (see e.g.
|
|
# https://github.com/anthropics/claude-code/issues/20085), so a chained
|
|
# command can't slip through on a whitelisted prefix without the user
|
|
# seeing it.
|
|
#
|
|
# Register in settings.json under hooks.PreToolUse with a matcher of "Bash".
|
|
|
|
set -euo pipefail
|
|
|
|
INPUT="$(cat)"
|
|
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
# No jq available — fail open (no output = no opinion), rather than
|
|
# breaking every Bash call because a dependency is missing.
|
|
exit 0
|
|
fi
|
|
|
|
TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name // empty')"
|
|
if [ "$TOOL_NAME" != "Bash" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
CMD="$(echo "$INPUT" | jq -r '.tool_input.command // empty')"
|
|
|
|
# Look for shell chaining/substitution operators anywhere in the command.
|
|
# Deliberately broad (better a false positive prompt than a missed chain):
|
|
# && || ; | backticks $( )
|
|
if echo "$CMD" | grep -qE '[;&|`]|\$\('; then
|
|
jq -n \
|
|
--arg reason "Command appears to chain multiple shell commands (&&, ;, |, or command substitution). Forcing a normal confirmation prompt instead of relying on the allow-list, since compound commands can bypass per-command whitelisting." \
|
|
'{
|
|
hookSpecificOutput: {
|
|
hookEventName: "PreToolUse",
|
|
permissionDecision: "ask",
|
|
permissionDecisionReason: $reason
|
|
}
|
|
}'
|
|
fi
|
|
|
|
exit 0
|