Files
gh-withzombies-hyperpowers/hooks/post-tool-use/03-block-pre-commit-bash.py
2025-11-30 09:06:38 +08:00

114 lines
3.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""
PostToolUse hook to block Bash commands that modify .git/hooks/pre-commit
Catches sneaky modifications through sed, redirection, chmod, mv, cp, etc.
"""
import json
import sys
import re
# Patterns that indicate pre-commit hook modification
PRECOMMIT_MODIFICATION_PATTERNS = [
# File paths
r'\.git/hooks/pre-commit',
r'\.git\\hooks\\pre-commit',
# Redirection to pre-commit
r'>.*pre-commit',
r'>>.*pre-commit',
# sed/awk/perl modifying pre-commit
r'(sed|awk|perl).*-i.*pre-commit',
r'(sed|awk|perl).*pre-commit.*>',
# Moving/copying to pre-commit
r'(mv|cp).*\s+.*\.git/hooks/pre-commit',
r'(mv|cp).*\s+.*pre-commit',
# chmod on pre-commit (might be preparing to modify)
r'chmod.*\.git/hooks/pre-commit',
# echo/cat piped to pre-commit
r'(echo|cat).*>.*\.git/hooks/pre-commit',
r'(echo|cat).*>>.*\.git/hooks/pre-commit',
# tee to pre-commit
r'tee.*\.git/hooks/pre-commit',
# Creating pre-commit hook
r'cat\s*>\s*\.git/hooks/pre-commit',
r'cat\s*<<.*\.git/hooks/pre-commit',
]
def check_precommit_modification(command):
"""Check if command modifies pre-commit hook."""
if not command:
return None
for pattern in PRECOMMIT_MODIFICATION_PATTERNS:
match = re.search(pattern, command, re.IGNORECASE)
if match:
return match.group(0)
return None
def main():
# Read tool use event from stdin
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError:
# If we can't parse JSON, allow the operation
sys.exit(0)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
# Only check Bash tool calls
if tool_name != "Bash":
sys.exit(0)
command = tool_input.get("command", "")
# Check for pre-commit modification
modification_pattern = check_precommit_modification(command)
if modification_pattern:
# Block the command and provide helpful feedback
output = {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": (
f"🚫 PRE-COMMIT HOOK MODIFICATION BLOCKED\n\n"
f"Detected modification attempt via: {modification_pattern}\n"
f"Command: {command[:200]}{'...' if len(command) > 200 else ''}\n\n"
"Git hooks should not be modified directly by Claude.\n\n"
"Why this is blocked:\n"
"- Pre-commit hooks enforce critical quality standards\n"
"- Direct modifications bypass code review\n"
"- Changes can break CI/CD pipelines\n"
"- Hook modifications should be version controlled\n\n"
"If you need to modify hooks:\n"
"1. Edit the source hook template in version control\n"
"2. Use proper tooling (husky, pre-commit framework, etc.)\n"
"3. Document changes and get them reviewed\n"
"4. Never bypass hooks with --no-verify\n\n"
"If the hook is causing issues:\n"
"- Fix the underlying problem the hook detected\n"
"- Ask the user for permission to modify hooks\n"
"- Use the test-runner agent to handle verbose hook output\n\n"
"Common mistake: Trying to disable hooks instead of fixing issues."
)
}
}
print(json.dumps(output))
sys.exit(0)
# Allow command if no pre-commit modification detected
sys.exit(0)
if __name__ == "__main__":
main()