#!/bin/bash # pre-tool-confirm.sh # PreToolUse Hook: 拦截危险操作,强制原生确认对话框 # # 输入格式(stdin JSON): # { "tool_name": "Bash", "tool_input": { "command": "..." } } # # 输出格式(stdout JSON): # { "hookSpecificOutput": { "hookEventName": "PreToolUse", # "permissionDecision": "ask", # "permissionDecisionReason": "..." } } # # 安装方式:在 ~/.claude/settings.json 配置 # hooks.PreToolUse: # - matcher: "Bash" # hooks: # - type: command # command: "/hooks/pre-tool-confirm.sh" # timeout: 30 # # 参考:devflow-claude confirm-before-commit.sh + ai-proj memory 规则 # REQ-20260416-0017 P0-2 set -e INPUT=$(cat) if ! command -v jq >/dev/null 2>&1; then # 没有 jq 就不处理 exit 0 fi COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty') if [ -z "$COMMAND" ]; then exit 0 fi REASON="" # ============ 1. 生产分支推送 ============ if echo "$COMMAND" | grep -qE '\bgit\s+push\b.*\b(origin\s+)?(main|master)\b'; then REASON="⚠️ 即将推送到 main/master 生产分支。确认已过 PR 评审?" # ============ 2. 强制推送 ============ elif echo "$COMMAND" | grep -qE '\bgit\s+push\b.*(--force|--force-with-lease|-f\b)'; then REASON="⛔ 危险:force push 会覆盖远程历史,可能丢失他人提交。确认继续?" # ============ 3. tea pr merge --base main ============ elif echo "$COMMAND" | grep -qE '\btea\s+pr\s+merge\b.*--base\s+main\b'; then REASON="⚠️ 即将合并 PR 到 main 分支,合并后将触发生产部署。确认 PR 已测试?" # ============ 4. docker rm/stop 生产容器 ============ elif echo "$COMMAND" | grep -qE '\bdocker\s+(rm|stop|kill)\b.*\b(ai_postgres_prod|ai_backend_prod|ai_frontend_prod|ai_redis_prod)\b'; then REASON="⛔ 危险:即将停止/删除生产容器!2026-04-05 曾因此宕机 15 分钟。确认是灾难恢复?" # ============ 5. docker rm/stop 其他 prod 相关 ============ elif echo "$COMMAND" | grep -qE '\bdocker\s+(rm|stop|kill)\b.*_prod\b'; then REASON="⚠️ 即将停止/删除含 _prod 的容器。确认是生产环境?" # ============ 6. git reset --hard / clean -fd ============ elif echo "$COMMAND" | grep -qE '\bgit\s+reset\s+--hard\b'; then REASON="⚠️ git reset --hard 会丢弃所有未提交改动。确认继续?" elif echo "$COMMAND" | grep -qE '\bgit\s+clean\s+.*-f'; then REASON="⚠️ git clean -f 会删除未跟踪文件。确认继续?" # ============ 7. rm -rf / 系统路径 ============ elif echo "$COMMAND" | grep -qE '\brm\s+.*-[rf]+.*\s+(/|/\*|~|\$HOME)'; then REASON="⛔ 危险:rm -rf 指向系统根或家目录。确认继续?" # ============ 8. ssh 生产服务器 + 破坏性命令 ============ elif echo "$COMMAND" | grep -qE 'ssh\s+\S*prod\S*.*\b(rm|drop|truncate|delete)\b'; then REASON="⛔ 危险:在生产服务器执行破坏性命令。确认继续?" # ============ 9. psql/mysql 生产数据库 + DROP/TRUNCATE/DELETE ============ elif echo "$COMMAND" | grep -qiE '(psql|mysql).*prod.*\b(DROP|TRUNCATE|DELETE FROM)\b'; then REASON="⛔ 危险:生产数据库 DROP/TRUNCATE/DELETE。确认已备份?" # ============ 10. MCP advance_delivery_stage force=true ============ # 这种会走 MCP 而不是 Bash,本 hook 不好拦,留给另一个 matcher 处理 fi if [ -n "$REASON" ]; then jq -n --arg reason "$REASON" '{ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: $reason } }' else exit 0 fi