-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·188 lines (151 loc) · 6.66 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·188 lines (151 loc) · 6.66 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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/usr/bin/env bash
# install.sh — Install claude-guardrails (lite or full variant)
# Usage: ./install.sh [lite|full] (defaults to lite)
set -euo pipefail
VARIANT="${1:-lite}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
VARIANT_DIR="$SCRIPT_DIR/$VARIANT"
CLAUDE_DIR="$HOME/.claude"
SETTINGS="$CLAUDE_DIR/settings.json"
# --- Validate ---
if [[ "$VARIANT" != "lite" && "$VARIANT" != "full" ]]; then
echo "Error: Unknown variant '$VARIANT'. Use 'lite' or 'full'."
exit 1
fi
if ! command -v jq &>/dev/null; then
echo "Error: jq is required but not installed."
echo " macOS: brew install jq"
echo " Debian/Ubuntu: sudo apt install jq"
exit 1
fi
if [[ ! -d "$VARIANT_DIR" ]]; then
echo "Error: Variant directory not found: $VARIANT_DIR"
exit 1
fi
echo "Installing claude-guardrails ($VARIANT)..."
# --- Ensure ~/.claude exists ---
mkdir -p "$CLAUDE_DIR"
# --- Merge settings.json ---
BROKEN_SCHEMA="https://claude.ai/schemas/claude-code-settings.json"
if [[ -f "$SETTINGS" ]]; then
# Capture the old $schema BEFORE the merge overwrites it. If the user
# is on a pre-v0.3.5 install, their existing settings.json carries the
# broken value that made Claude Code discard the entire file. The merge
# below silently corrects it; the check after the merge surfaces the
# fact so the user learns their guardrails were previously inactive.
# See https://github.com/dwarvesf/claude-guardrails/issues/6.
OLD_SCHEMA="$(jq -r '."$schema" // ""' "$SETTINGS" 2>/dev/null || echo "")"
cp "$SETTINGS" "$SETTINGS.backup"
echo " Backed up existing settings → settings.json.backup"
# Merge: deduplicate deny rules and PreToolUse hooks
jq -s '
.[0] as $existing | .[1] as $new |
($existing * $new) |
.permissions.deny = (
[($existing.permissions.deny // [])[], ($new.permissions.deny // [])[]]
| unique
) |
.hooks.PreToolUse = (
[($existing.hooks.PreToolUse // [])[], ($new.hooks.PreToolUse // [])[]]
| unique
) |
.hooks.UserPromptSubmit = (
[($existing.hooks.UserPromptSubmit // [])[], ($new.hooks.UserPromptSubmit // [])[]]
| unique
)
' "$SETTINGS.backup" "$VARIANT_DIR/settings.json" > "$SETTINGS.tmp" \
&& mv "$SETTINGS.tmp" "$SETTINGS"
echo " Merged settings.json (deny rules + hooks deduplicated)"
if [[ "$OLD_SCHEMA" == "$BROKEN_SCHEMA" ]]; then
cat <<'NOTICE'
====================== NOTICE — schema URL auto-corrected ======================
Your existing ~/.claude/settings.json carried the pre-v0.3.5 $schema value,
which Claude Code silently rejects. Any claude-guardrails release from v0.3.0
through v0.3.4 shipped that URL, which means your deny rules, hooks, and
secret scanner were INACTIVE on every session until now.
We corrected the $schema during this merge. Start a new Claude Code session
for the fix to take effect.
Background: https://github.com/dwarvesf/claude-guardrails/issues/6
================================================================================
NOTICE
fi
else
cp "$VARIANT_DIR/settings.json" "$SETTINGS"
echo " Created settings.json"
fi
# --- Append CLAUDE.md security section ---
CLAUDE_MD="$CLAUDE_DIR/CLAUDE.md"
SECURITY_SECTION="$VARIANT_DIR/CLAUDE-security-section.md"
if [[ -f "$CLAUDE_MD" ]] && grep -q "# Security Rules" "$CLAUDE_MD" 2>/dev/null; then
echo " CLAUDE.md already contains Security Rules — skipped"
else
{ echo ""; cat "$SECURITY_SECTION"; } >> "$CLAUDE_MD"
echo " Appended security rules to CLAUDE.md"
fi
# --- Both variants: shared patterns file ---
# scan-secrets.sh and scan-commit.sh both load regexes from this file so
# there's a single source of truth for what counts as a credential.
PATTERNS_DIR="$CLAUDE_DIR/hooks/patterns"
mkdir -p "$PATTERNS_DIR"
cp "$SCRIPT_DIR/patterns/secrets.json" "$PATTERNS_DIR/secrets.json"
cp "$SCRIPT_DIR/patterns/bip39-english.txt" "$PATTERNS_DIR/bip39-english.txt"
echo " Installed patterns/secrets.json + bip39-english.txt → ~/.claude/hooks/patterns/"
# --- Both variants: scan-secrets UserPromptSubmit hook ---
SCAN_DIR="$CLAUDE_DIR/hooks/scan-secrets"
mkdir -p "$SCAN_DIR"
cp "$VARIANT_DIR/scan-secrets.sh" "$SCAN_DIR/scan-secrets.sh"
chmod +x "$SCAN_DIR/scan-secrets.sh"
echo " Installed scan-secrets.sh → ~/.claude/hooks/scan-secrets/"
# --- Both variants: scan-commit PreToolUse hook ---
# Fires on `git commit` Bash calls and scans the staged diff for the same
# secret patterns scan-secrets uses on prompts.
COMMIT_DIR="$CLAUDE_DIR/hooks/scan-commit"
mkdir -p "$COMMIT_DIR"
cp "$VARIANT_DIR/scan-commit.sh" "$COMMIT_DIR/scan-commit.sh"
chmod +x "$COMMIT_DIR/scan-commit.sh"
echo " Installed scan-commit.sh → ~/.claude/hooks/scan-commit/"
# Merge UserPromptSubmit hook entry into settings.json
PROMPT_HOOK='[{"hooks":[{"type":"command","command":"~/.claude/hooks/scan-secrets/scan-secrets.sh","timeout":5}]}]'
jq --argjson hook "$PROMPT_HOOK" '
.hooks.UserPromptSubmit = (
[(.hooks.UserPromptSubmit // [])[], $hook[]]
| unique
)
' "$SETTINGS" > "$SETTINGS.tmp" && mv "$SETTINGS.tmp" "$SETTINGS"
echo " Added UserPromptSubmit hook to settings.json"
# --- Full only: prompt injection defender ---
if [[ "$VARIANT" == "full" ]]; then
HOOK_DIR="$CLAUDE_DIR/hooks/prompt-injection-defender"
mkdir -p "$HOOK_DIR"
cp "$VARIANT_DIR/prompt-injection-defender.sh" "$HOOK_DIR/prompt-injection-defender.sh"
chmod +x "$HOOK_DIR/prompt-injection-defender.sh"
echo " Installed prompt-injection-defender.sh → ~/.claude/hooks/"
# Merge PostToolUse hook entry into settings.json
POST_HOOK='[{"matcher":"Read|WebFetch|Bash|mcp__.*","hooks":[{"type":"command","command":"~/.claude/hooks/prompt-injection-defender/prompt-injection-defender.sh"}]}]'
jq --argjson hook "$POST_HOOK" '
.hooks.PostToolUse = (
[(.hooks.PostToolUse // [])[], $hook[]]
| unique
)
' "$SETTINGS" > "$SETTINGS.tmp" && mv "$SETTINGS.tmp" "$SETTINGS"
echo " Added PostToolUse hook to settings.json"
fi
# --- Summary ---
echo ""
echo "Done! Summary:"
echo " Variant: $VARIANT"
DENY_COUNT=$(jq '.permissions.deny | length' "$SETTINGS")
PRE_COUNT=$(jq '.hooks.PreToolUse | length' "$SETTINGS")
PROMPT_COUNT=$(jq '.hooks.UserPromptSubmit | length' "$SETTINGS")
echo " Deny rules: $DENY_COUNT"
echo " PreToolUse hooks: $PRE_COUNT"
echo " UserPromptSubmit hooks: $PROMPT_COUNT (inbound secret scanner)"
if [[ "$VARIANT" == "full" ]]; then
POST_COUNT=$(jq '.hooks.PostToolUse | length' "$SETTINGS")
echo " PostToolUse hooks: $POST_COUNT (prompt injection scanner)"
fi
echo ""
echo "Start a new Claude Code session to activate. Test with:"
echo " cat ~/.ssh/id_rsa → should be denied"
echo " rm -rf / → should be blocked by hook"
echo " git push origin main → should be blocked by hook"