#!/usr/bin/env python3 """PreToolUse hook: блокирует запрещённые git/fs/docker команды, включая shell-обёртки. Deny-префиксы в settings.json позиционны и обходятся обёртками (`powershell -c "git push --force"` не матчится ни одним deny-паттерном). Этот hook regex-сканирует ВСЮ строку команды Bash-тула, поэтому ловит и пейлоады внутри powershell/pwsh/bash -c. Ограничение (как у check-secret-read.py): видит только текст top-level команды — опасная операция внутри вызываемого скрипта не детектится. Fail-open по дизайну: это tripwire-слой поверх deny-правил и review, а не единственная защита. Протокол: exit 0 = allow, exit 2 + stderr = block (сообщение уходит Claude). """ import json import re import sys # (pattern, объяснение). Все паттерны применяются к полной строке команды. PATTERNS: list[tuple[str, str]] = [ (r"git\s+push\b[^\n]*?\s(-f|--force(?:-with-lease|-if-includes)?)\b", "git push --force/-f/--force-with-lease запрещён без явного approval юзера"), (r"git\s+push\b[^\n]*?--no-verify", "git push --no-verify запрещён (pre-push hooks обязательны)"), (r"git\s+push\b[^\n]*\s\S+\s+(\S+:)?main\b", "прямой push в main запрещён — только ветка + PR (git-pr.md)"), (r"git\s+commit\b[^\n]*?--amend\b", "git commit --amend запрещён без явного approval юзера"), (r"git\s+commit\b[^\n]*?(--no-verify\b|\s-n\b)", "git commit --no-verify/-n запрещён (pre-commit hooks обязательны)"), (r"git\s+rebase\b[^\n]*?(\s-i\b|--interactive\b)", "git rebase -i/--interactive запрещён"), (r"git\s+reset\b[^\n]*?--hard\b", "git reset --hard запрещён (уничтожает локальные изменения)"), (r"git\s+clean\b[^\n]*?\s-[a-zA-Z]*f", "git clean -f* запрещён (удаляет untracked-файлы)"), (r"git\s+checkout\b[^\n]*?\s-f\b", "git checkout -f запрещён (теряет незакоммиченные изменения)"), (r"\brm\b(?=[^\n|;&]*\s-[a-zA-Z]*r)(?=[^\n|;&]*\s-[a-zA-Z]*f)", "rm -rf (и вариации -fr / -r -f) запрещён"), (r"(?i)remove-item\b(?=[^\n]*-recurse)(?=[^\n]*-force)", "Remove-Item -Recurse -Force запрещён"), (r"(?i)docker(?:-|\s+)compose\s+down\b[^\n]*(\s-v\b|--volumes\b)", "docker compose down -v удаляет volumes"), (r"docker\s+volume\s+rm\b", "docker volume rm запрещён"), ] def main() -> int: try: data = json.load(sys.stdin) if data.get("tool_name") != "Bash": return 0 cmd = (data.get("tool_input") or {}).get("command") or "" for pattern, reason in PATTERNS: if re.search(pattern, cmd): print( f"BLOCKED (check-dangerous-commands): {reason}. " "Правило CLAUDE.md #5 / git-pr.md § Запреты. Если операция реально нужна — " "попроси юзера выполнить её самому или дать явное разрешение; " "не пытайся обойти через обёртки/скрипты.", file=sys.stderr, ) return 2 return 0 except Exception: return 0 # fail-open: tripwire-слой, не единственная защита if __name__ == "__main__": sys.exit(main())