diff --git a/scripts/claude-hooks/check-dangerous-commands.py b/scripts/claude-hooks/check-dangerous-commands.py index fb9f7127..f602a2e3 100644 --- a/scripts/claude-hooks/check-dangerous-commands.py +++ b/scripts/claude-hooks/check-dangerous-commands.py @@ -7,8 +7,10 @@ Deny-префиксы в settings.json позиционны и обходятс внутри powershell/pwsh/bash -c. Ограничение (как у check-secret-read.py): видит только текст top-level команды — -опасная операция внутри вызываемого скрипта не детектится. Fail-open по дизайну: -это tripwire-слой поверх deny-правил и review, а не единственная защита. +опасная операция внутри вызываемого скрипта не детектится. Known-bypass (осознанно): +long-form `rm --recursive --force`, PowerShell-алиасы ri/rd/del, сокращения -Rec. +Fail-open по дизайну: это tripwire-слой поверх deny-правил и review, а не +единственная защита. Протокол: exit 0 = allow, exit 2 + stderr = block (сообщение уходит Claude). """ @@ -19,23 +21,23 @@ import sys # (pattern, объяснение). Все паттерны применяются к полной строке команды. PATTERNS: list[tuple[str, str]] = [ - (r"git\s+push\b[^\n]*?\s(-f|--force(?:-with-lease|-if-includes)?)\b", + (r"git\s+(?:-C\s+\S+\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", + (r"git\s+(?:-C\s+\S+\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", + (r"git\s+(?:-C\s+\S+\s+)?push\b[^\n]*\s\S+\s+(\S+:)?main(?![\w/-])", "прямой push в main запрещён — только ветка + PR (git-pr.md)"), - (r"git\s+commit\b[^\n]*?--amend\b", + (r"git\s+(?:-C\s+\S+\s+)?commit\b[^\n]*?--amend\b", "git commit --amend запрещён без явного approval юзера"), - (r"git\s+commit\b[^\n]*?(--no-verify\b|\s-n\b)", + (r"git\s+(?:-C\s+\S+\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)", + (r"git\s+(?:-C\s+\S+\s+)?rebase\b[^\n]*?(\s-i\b|--interactive\b)", "git rebase -i/--interactive запрещён"), - (r"git\s+reset\b[^\n]*?--hard\b", + (r"git\s+(?:-C\s+\S+\s+)?reset\b[^\n]*?--hard\b", "git reset --hard запрещён (уничтожает локальные изменения)"), - (r"git\s+clean\b[^\n]*?\s-[a-zA-Z]*f", + (r"git\s+(?:-C\s+\S+\s+)?clean\b[^\n]*?\s-[a-zA-Z]*f", "git clean -f* запрещён (удаляет untracked-файлы)"), - (r"git\s+checkout\b[^\n]*?\s-f\b", + (r"git\s+(?:-C\s+\S+\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) запрещён"),