|
#!/bin/bash |
|
# PreToolUse hook to block dangerous git operations |
|
# Returns non-zero exit code to block, zero to allow |
|
# Can output JSON with "decision" and "reason" fields |
|
|
|
# Read the tool invocation from stdin |
|
INPUT=$(cat) |
|
|
|
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty') |
|
TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input // empty') |
|
|
|
# Only check Bash tool |
|
if [[ "$TOOL_NAME" != "Bash" ]]; then |
|
exit 0 |
|
fi |
|
|
|
# Extract the command from tool input |
|
COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command // empty') |
|
|
|
# Define blocked patterns with reasons |
|
declare -A BLOCKED_PATTERNS=( |
|
["git push --force"]="Force push can destroy remote history" |
|
["git push -f "]="Force push can destroy remote history" |
|
["git reset --hard"]="Hard reset discards uncommitted changes permanently" |
|
["git clean -fd"]="Clean -fd deletes untracked files permanently" |
|
["git checkout ."]="Discards all unstaged changes" |
|
["git checkout -- ."]="Discards all unstaged changes" |
|
["git stash drop"]="Permanently deletes stashed changes" |
|
["git stash clear"]="Permanently deletes all stashes" |
|
["git branch -D"]="Force deletes branch without merge check" |
|
["git rebase -i"]="Interactive rebase requires user input" |
|
["git add -A"]="Stages all files including untracked - be explicit" |
|
["git add --all"]="Stages all files including untracked - be explicit" |
|
["git add ."]="Stages everything in current directory - be explicit about files" |
|
["git merge --no-ff"]="Use explicit merge strategies" |
|
["git cherry-pick --abort"]="Aborting cherry-pick loses progress" |
|
["git rebase --abort"]="Aborting rebase loses progress" |
|
) |
|
|
|
# Check each blocked pattern |
|
for pattern in "${!BLOCKED_PATTERNS[@]}"; do |
|
if [[ "$COMMAND" == *"$pattern"* ]]; then |
|
REASON="${BLOCKED_PATTERNS[$pattern]}" |
|
|
|
# Output JSON response with block decision |
|
cat << EOF |
|
{ |
|
"decision": "block", |
|
"reason": "🚫 Blocked: $pattern - $REASON" |
|
} |
|
EOF |
|
exit 2 |
|
fi |
|
done |
|
|
|
# Additional check: block any git command with --force or -f flag on push/reset |
|
if [[ "$COMMAND" =~ git\ (push|reset).*(-f|--force) ]]; then |
|
cat << EOF |
|
{ |
|
"decision": "block", |
|
"reason": "🚫 Blocked: Force flags on git push/reset are dangerous" |
|
} |
|
EOF |
|
exit 2 |
|
fi |
|
|
|
# Allow the operation |
|
exit 0 |