-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathsafety-guard.sh
More file actions
executable file
·93 lines (82 loc) · 3.96 KB
/
Copy pathsafety-guard.sh
File metadata and controls
executable file
·93 lines (82 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/env bash
set -euo pipefail
# tested with: claude code v2.1.122
# =============================================================================
# Safety Guard: PreToolUse command blocker
# =============================================================================
# Intercepts Bash tool calls before execution and blocks dangerous commands.
# Returns exit code 2 to tell Claude Code to REJECT the tool call.
#
# Hook type: PreToolUse (matcher: "Bash")
#
# Exit codes:
# 0 = allow the command to proceed
# 2 = block the command (Claude Code will not execute it)
#
# Blocked patterns:
# - git push --force to main/master
# - rm -rf / or rm -rf ~ (catastrophic deletes)
# - git reset --hard on main/master
# - DROP TABLE / DROP DATABASE (SQL destruction)
# - chmod 777 on sensitive paths
# - curl piped to sh/bash (remote code execution)
# =============================================================================
# Read the hook payload from stdin
INPUT=$(cat)
# Extract the command string from the tool input (printf avoids echo interpreting flags)
COMMAND=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // ""')
# If no command found, allow (not a Bash call we care about)
if [ -z "$COMMAND" ]; then
exit 0
fi
# Helper: block with a reason
block() {
echo "BLOCKED by safety-guard: $1" >&2
exit 2
}
# ---------------------------------------------------------------------------
# git push --force to main/master
# Catches: --force, -f, --force-with-lease targeting main or master
# ---------------------------------------------------------------------------
if printf '%s' "$COMMAND" | grep -qiE 'git\s+push\s+.*(-f|--force)' ; then
if printf '%s' "$COMMAND" | grep -qiE '\b(main|master)\b'; then
block "Force push to main/master is not allowed. Use a feature branch."
fi
fi
# ---------------------------------------------------------------------------
# rm -rf / or rm -rf ~ (catastrophic filesystem deletion)
# No trailing $ anchor: catches `rm -rf / && ...` and `rm -rf / foo` too
# ---------------------------------------------------------------------------
if printf '%s' "$COMMAND" | grep -qiE 'rm\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)\s+(/|~|\$HOME)(\s|;|&|\||$)'; then
block "Recursive force delete on root or home directory is not allowed."
fi
# ---------------------------------------------------------------------------
# git reset --hard on main/master
# ---------------------------------------------------------------------------
if printf '%s' "$COMMAND" | grep -qiE 'git\s+reset\s+--hard'; then
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
if [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "master" ]]; then
block "git reset --hard on $CURRENT_BRANCH is not allowed. Checkout a feature branch first."
fi
fi
# ---------------------------------------------------------------------------
# DROP TABLE / DROP DATABASE (SQL destruction)
# Catches these in any context: inline sqlite3, psql, mysql, etc.
# ---------------------------------------------------------------------------
if printf '%s' "$COMMAND" | grep -qiE 'DROP\s+(TABLE|DATABASE)'; then
block "DROP TABLE / DROP DATABASE detected. Remove this from the command if intentional."
fi
# ---------------------------------------------------------------------------
# chmod 777 on sensitive paths
# ---------------------------------------------------------------------------
if printf '%s' "$COMMAND" | grep -qiE 'chmod\s+777\s+(/|~|\$HOME|/etc|/usr)'; then
block "chmod 777 on sensitive paths is not allowed."
fi
# ---------------------------------------------------------------------------
# curl/wget piped to sh/bash (remote code execution)
# ---------------------------------------------------------------------------
if printf '%s' "$COMMAND" | grep -qiE '(curl|wget)\s+.*\|\s*(sudo\s+)?(bash|sh|zsh)'; then
block "Piping remote content to a shell is not allowed. Download first, review, then execute."
fi
# All checks passed. allow the command
exit 0