dangerous-cmd-warn.sh 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. #!/bin/bash
  2. # hooks/dangerous-cmd-warn.sh
  3. # PreToolUse hook - warns before destructive or irreversible commands
  4. # Matcher: Bash
  5. #
  6. # Configuration in .claude/settings.json:
  7. # {
  8. # "hooks": {
  9. # "PreToolUse": [{
  10. # "matcher": "Bash",
  11. # "hooks": ["bash hooks/dangerous-cmd-warn.sh $TOOL_INPUT"]
  12. # }]
  13. # }
  14. # }
  15. #
  16. # Exit codes:
  17. # 0 = allow (safe or not matched)
  18. # 2 = block with message (dangerous command detected)
  19. INPUT="$1"
  20. # Modern Claude Code delivers the tool call as JSON on stdin
  21. # ({"tool_input":{"command":"..."}}); older configs pass it as $TOOL_INPUT/$1.
  22. # Support both so the hook works regardless of harness version.
  23. if [[ -z "$INPUT" && ! -t 0 ]]; then
  24. RAW="$(cat 2>/dev/null)"
  25. if [[ -n "$RAW" ]] && command -v jq >/dev/null 2>&1; then
  26. INPUT="$(printf '%s' "$RAW" | jq -r '.tool_input.command // .tool_input // empty' 2>/dev/null)"
  27. fi
  28. [[ -z "$INPUT" ]] && INPUT="$RAW"
  29. fi
  30. [[ -z "$INPUT" ]] && exit 0
  31. # -------------------------------------------------------------------
  32. # Dangerous patterns and their risk descriptions
  33. # -------------------------------------------------------------------
  34. declare -A PATTERNS
  35. # Git destructive operations
  36. PATTERNS["git\s+push\s+.*--force"]="Force push can overwrite remote history and lose others' commits"
  37. PATTERNS["git\s+push\s+-f\b"]="Force push can overwrite remote history and lose others' commits"
  38. PATTERNS["git\s+reset\s+--hard"]="Hard reset discards all uncommitted changes permanently"
  39. PATTERNS["git\s+clean\s+-f"]="git clean -f permanently deletes untracked files"
  40. PATTERNS["git\s+checkout\s+--\s+\."]="Discards all unstaged changes in working directory"
  41. PATTERNS["git\s+branch\s+-D"]="Force-deletes a branch even if not fully merged"
  42. PATTERNS["git\s+stash\s+drop"]="Permanently removes a stash entry"
  43. PATTERNS["git\s+rebase\s+.*--force"]="Forced rebase can rewrite shared history"
  44. # File system destructive operations
  45. PATTERNS["rm\s+-rf\s+/"]="Recursive force delete from root - catastrophic data loss"
  46. PATTERNS["rm\s+-rf\s+~"]="Recursive force delete of home directory"
  47. PATTERNS["rm\s+-rf\s+\\."]="Recursive force delete of current directory"
  48. PATTERNS["rm\s+-rf\s+\*"]="Recursive force delete with glob - likely unintended"
  49. PATTERNS["rmdir\s+/"]="Attempting to remove root directory"
  50. PATTERNS["> /dev/sda"]="Direct write to block device - destroys filesystem"
  51. PATTERNS["mkfs\\."]="Formatting a filesystem destroys all data"
  52. PATTERNS["dd\s+.*of=/dev/"]="Direct disk write - can destroy data"
  53. # Database destructive operations
  54. PATTERNS["DROP\s+DATABASE"]="Drops entire database - all data lost"
  55. PATTERNS["DROP\s+TABLE"]="Drops table and all its data permanently"
  56. PATTERNS["DROP\s+SCHEMA"]="Drops schema and all contained objects"
  57. PATTERNS["TRUNCATE\s+TABLE"]="Removes all rows without logging - cannot rollback"
  58. PATTERNS["DELETE\s+FROM\s+\w+\s*;"]="DELETE without WHERE clause removes all rows"
  59. PATTERNS["UPDATE\s+\w+\s+SET\s+.*(?!WHERE)"]="UPDATE without WHERE clause modifies all rows"
  60. # Process/system operations
  61. PATTERNS["kill\s+-9\s+1\b"]="Killing PID 1 (init/systemd) crashes the system"
  62. PATTERNS["killall\s+-9"]="Force-kills all matching processes without cleanup"
  63. PATTERNS["chmod\s+-R\s+777"]="World-writable recursive permissions - security risk"
  64. PATTERNS["chown\s+-R\s+.*\s+/"]="Recursive ownership change from root"
  65. # Container operations
  66. PATTERNS["docker\s+system\s+prune\s+-a"]="Removes ALL unused Docker data (images, containers, volumes)"
  67. PATTERNS["docker\s+volume\s+prune"]="Removes all unused Docker volumes (data loss)"
  68. PATTERNS["kubectl\s+delete\s+namespace"]="Deletes entire Kubernetes namespace and all resources"
  69. PATTERNS["kubectl\s+delete\s+.*--all"]="Deletes all resources of a type"
  70. # Package/dependency operations
  71. PATTERNS["npm\s+cache\s+clean\s+--force"]="Clears entire npm cache"
  72. PATTERNS["pip\s+install\s+--force-reinstall"]="Force reinstalls all packages"
  73. # Environment/secrets
  74. PATTERNS["printenv"]="Prints all environment variables (may contain secrets)"
  75. PATTERNS["env\s*$"]="Prints all environment variables (may contain secrets)"
  76. PATTERNS["cat\s+.*\\.env"]="Displaying .env file may expose secrets"
  77. # -------------------------------------------------------------------
  78. # Check each pattern
  79. # -------------------------------------------------------------------
  80. for pattern in "${!PATTERNS[@]}"; do
  81. if echo "$INPUT" | grep -qEi "$pattern"; then
  82. echo "WARNING: Potentially dangerous command detected"
  83. echo "Pattern: $pattern"
  84. echo "Risk: ${PATTERNS[$pattern]}"
  85. echo ""
  86. echo "The command has been blocked. If you're certain this is safe,"
  87. echo "ask the user to confirm before proceeding."
  88. exit 2
  89. fi
  90. done
  91. exit 0