Initial commit
This commit is contained in:
113
hooks/post-tool-use/03-block-pre-commit-bash.py
Executable file
113
hooks/post-tool-use/03-block-pre-commit-bash.py
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user