cc-safe-setup 29.6.40 → 29.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/.claude-plugin/marketplace.json +66 -0
  2. package/.claude-plugin/plugin.json +11 -0
  3. package/README.md +123 -12
  4. package/SETTINGS_REFERENCE.md +2 -0
  5. package/SKILL.md +47 -0
  6. package/examples/README.md +11 -1
  7. package/examples/auto-approve-compound-git.sh +3 -0
  8. package/examples/auto-compact-context-monitor.sh +35 -0
  9. package/examples/auto-mode-safety-enforcer.sh +57 -0
  10. package/examples/background-task-guard.sh +57 -0
  11. package/examples/broad-find-guard.sh +62 -0
  12. package/examples/cache-creation-spike-detector.sh +32 -0
  13. package/examples/case-insensitive-path-guard.sh +96 -0
  14. package/examples/cjk-punctuation-guard.sh +44 -0
  15. package/examples/clipboard-secret-guard.sh +29 -0
  16. package/examples/compact-circuit-breaker.sh +72 -0
  17. package/examples/context-size-alert.sh +38 -0
  18. package/examples/context-usage-drift-alert.sh +33 -0
  19. package/examples/dangerous-pip-flag-guard.sh +51 -0
  20. package/examples/deny-bypass-detector.sh +143 -0
  21. package/examples/dotenv-read-guard.sh +48 -0
  22. package/examples/dotfile-protection-guard.sh +60 -0
  23. package/examples/effort-tracking-logger.sh +30 -0
  24. package/examples/exploration-budget-guard.sh +77 -0
  25. package/examples/financial-operation-guard.sh +47 -0
  26. package/examples/full-rewrite-detector.sh +63 -0
  27. package/examples/home-critical-bash-guard.sh +56 -0
  28. package/examples/idle-session-cost-alert.sh +36 -0
  29. package/examples/model-version-alert.sh +18 -0
  30. package/examples/model-version-change-alert.sh +31 -0
  31. package/examples/move-delete-sequence-guard.sh +92 -0
  32. package/examples/pii-upload-guard.sh +72 -0
  33. package/examples/pr-duplicate-guard.sh +14 -0
  34. package/examples/production-port-kill-guard.sh +60 -0
  35. package/examples/quota-reset-cycle-monitor.sh +30 -0
  36. package/examples/repo-visibility-guard.sh +33 -0
  37. package/examples/sandbox-relative-path-audit.sh +51 -0
  38. package/examples/session-agent-cost-limiter.sh +43 -0
  39. package/examples/session-cost-alert.sh +62 -0
  40. package/examples/session-memory-watchdog.sh +9 -0
  41. package/examples/settings-integrity-monitor.sh +55 -0
  42. package/examples/settings-json-model-guard.sh +89 -0
  43. package/examples/shell-config-truncation-guard.sh +97 -0
  44. package/examples/shell-wrapper-guard.sh +4 -4
  45. package/examples/subagent-spawn-rate-monitor.sh +34 -0
  46. package/examples/subcommand-chain-guard.sh +44 -0
  47. package/examples/system-dir-protection-guard.sh +100 -0
  48. package/examples/thinking-display-enforcer.sh +25 -0
  49. package/examples/thinking-stall-detector.sh +61 -0
  50. package/examples/tool-retry-budget-guard.sh +59 -0
  51. package/examples/worktree-branch-pollution-detector.sh +35 -0
  52. package/examples/worktree-create-log.sh +6 -0
  53. package/examples/worktree-hook-linker.sh +72 -0
  54. package/examples/worktree-remove-uncommitted-guard.sh +20 -0
  55. package/hooks/hooks.json +60 -0
  56. package/index.mjs +92 -6
  57. package/memory/market-anthropic-japan-strategy-2026-04-13.md +4 -0
  58. package/package.json +2 -2
  59. package/plugins/credential-guard/.claude-plugin/plugin.json +58 -0
  60. package/plugins/git-protection/.claude-plugin/plugin.json +58 -0
  61. package/plugins/safety-essentials/.claude-plugin/plugin.json +58 -0
  62. package/plugins/token-guard/.claude-plugin/plugin.json +51 -0
  63. package/skills/safety-setup/SKILL.md +47 -0
  64. package/tests/dotenv-read-guard.test.sh +65 -0
  65. package/tests/test-auto-mode-safety-enforcer.sh +55 -0
  66. package/tests/test-case-insensitive-path-guard.sh +78 -0
  67. package/tests/test-compact-circuit-breaker.sh +134 -0
  68. package/tests/test-context-usage-drift-alert.sh +52 -0
  69. package/tests/test-dangerous-pip-flag-guard.sh +56 -0
  70. package/tests/test-dotfile-protection-guard.sh +68 -0
  71. package/tests/test-effort-tracking-logger.sh +55 -0
  72. package/tests/test-exploration-budget-guard.sh +164 -0
  73. package/tests/test-financial-operation-guard.sh +59 -0
  74. package/tests/test-home-critical-bash-guard.sh +59 -0
  75. package/tests/test-model-version-change-alert.sh +55 -0
  76. package/tests/test-move-delete-sequence-guard.sh +63 -0
  77. package/tests/test-pr-duplicate-guard.sh +29 -0
  78. package/tests/test-quota-reset-cycle-monitor.sh +52 -0
  79. package/tests/test-shell-config-truncation-guard.sh +104 -0
  80. package/tests/test-subagent-spawn-rate-monitor.sh +43 -0
  81. package/tests/test-system-dir-protection-guard.sh +81 -0
  82. package/tests/test-thinking-stall-detector.sh +151 -0
  83. package/tests/test-tool-retry-budget-guard.sh +75 -0
  84. package/tests/test-worktree-branch-pollution-detector.sh +50 -0
  85. package/tests/test-worktree-lifecycle-hooks.sh +29 -0
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+ # Tests for dangerous-pip-flag-guard.sh
3
+ # Run: bash tests/test-dangerous-pip-flag-guard.sh
4
+ set -euo pipefail
5
+
6
+ PASS=0
7
+ FAIL=0
8
+ HOOK="$(dirname "$0")/../examples/dangerous-pip-flag-guard.sh"
9
+
10
+ test_hook() {
11
+ local input="$1" expected_exit="$2" desc="$3"
12
+ local actual_exit=0
13
+ echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
14
+ if [ "$actual_exit" -eq "$expected_exit" ]; then
15
+ echo " PASS: $desc"
16
+ PASS=$((PASS + 1))
17
+ else
18
+ echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
19
+ FAIL=$((FAIL + 1))
20
+ fi
21
+ }
22
+
23
+ echo "dangerous-pip-flag-guard.sh tests"
24
+ echo ""
25
+
26
+ # --- Block: --break-system-packages ---
27
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"pip install --break-system-packages requests"}}' 2 "Block --break-system-packages"
28
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"pip3 install --break-system-packages numpy"}}' 2 "Block pip3 --break-system-packages"
29
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"pip install requests --break-system-packages"}}' 2 "Block flag after package name"
30
+
31
+ # --- Block: sudo pip install ---
32
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"sudo pip install flask"}}' 2 "Block sudo pip install"
33
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"sudo pip3 install django"}}' 2 "Block sudo pip3 install"
34
+
35
+ # --- Block: targeting system directories ---
36
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"pip install --target=/usr/lib/python3/dist-packages requests"}}' 2 "Block install to /usr/lib"
37
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"pip install --target /opt/python/lib requests"}}' 2 "Block install to /opt"
38
+
39
+ # --- Allow: normal pip install ---
40
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"pip install requests"}}' 0 "Allow normal pip install"
41
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"pip install --user requests"}}' 0 "Allow --user install"
42
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"pip install -r requirements.txt"}}' 0 "Allow requirements.txt"
43
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"pip3 install flask==2.0"}}' 0 "Allow versioned install"
44
+
45
+ # --- Allow: non-pip commands ---
46
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"npm install express"}}' 0 "Allow npm install"
47
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"git status"}}' 0 "Allow git status"
48
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"echo hello"}}' 0 "Allow echo"
49
+
50
+ # --- Allow: empty/missing ---
51
+ test_hook '{"tool_name":"Bash","tool_input":{"command":""}}' 0 "Allow empty command"
52
+ test_hook '{"tool_name":"Bash","tool_input":{}}' 0 "Allow no command"
53
+
54
+ echo ""
55
+ echo "Results: $PASS passed, $FAIL failed out of $((PASS + FAIL))"
56
+ [ "$FAIL" -eq 0 ] && echo "ALL TESTS PASSED" || exit 1
@@ -0,0 +1,68 @@
1
+ #!/bin/bash
2
+ # Tests for dotfile-protection-guard.sh
3
+ # Run: bash tests/test-dotfile-protection-guard.sh
4
+ set -euo pipefail
5
+
6
+ PASS=0
7
+ FAIL=0
8
+ HOOK="$(dirname "$0")/../examples/dotfile-protection-guard.sh"
9
+
10
+ test_hook() {
11
+ local input="$1" expected_exit="$2" desc="$3"
12
+ local actual_exit=0
13
+ echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
14
+ if [ "$actual_exit" -eq "$expected_exit" ]; then
15
+ echo " PASS: $desc"
16
+ PASS=$((PASS + 1))
17
+ else
18
+ echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
19
+ FAIL=$((FAIL + 1))
20
+ fi
21
+ }
22
+
23
+ echo "dotfile-protection-guard.sh tests"
24
+ echo ""
25
+
26
+ # --- Block: Shell config files ---
27
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.bashrc\"}}" 2 "Block Write to .bashrc"
28
+ test_hook "{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"$HOME/.zshrc\"}}" 2 "Block Edit to .zshrc"
29
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.bash_profile\"}}" 2 "Block Write to .bash_profile"
30
+ test_hook "{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"$HOME/.profile\"}}" 2 "Block Edit to .profile"
31
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.zshenv\"}}" 2 "Block Write to .zshenv"
32
+
33
+ # --- Block: SSH ---
34
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.ssh/id_rsa\"}}" 2 "Block Write to .ssh/id_rsa"
35
+ test_hook "{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"$HOME/.ssh/config\"}}" 2 "Block Edit to .ssh/config"
36
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.ssh/authorized_keys\"}}" 2 "Block Write to .ssh/authorized_keys"
37
+
38
+ # --- Block: Git credentials ---
39
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.git-credentials\"}}" 2 "Block Write to .git-credentials"
40
+ test_hook "{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"$HOME/.gitconfig\"}}" 2 "Block Edit to .gitconfig"
41
+
42
+ # --- Block: Other credentials ---
43
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.npmrc\"}}" 2 "Block Write to .npmrc"
44
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.aws/credentials\"}}" 2 "Block Write to .aws/credentials"
45
+ test_hook "{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"$HOME/.config/gh/hosts.yml\"}}" 2 "Block Edit to gh hosts.yml"
46
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.netrc\"}}" 2 "Block Write to .netrc"
47
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.docker/config.json\"}}" 2 "Block Write to docker config"
48
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.kube/config\"}}" 2 "Block Write to kube config"
49
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.gnupg/trustdb.gpg\"}}" 2 "Block Write to gnupg"
50
+
51
+ # --- Allow: Claude Code config ---
52
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"$HOME/.claude/settings.json\"}}" 0 "Allow Write to .claude/settings.json"
53
+ test_hook "{\"tool_name\":\"Edit\",\"tool_input\":{\"file_path\":\"$HOME/.claude/CLAUDE.md\"}}" 0 "Allow Edit to .claude/CLAUDE.md"
54
+
55
+ # --- Allow: Project files ---
56
+ test_hook '{"tool_name":"Write","tool_input":{"file_path":"/home/user/project/src/main.py"}}' 0 "Allow Write to project file"
57
+ test_hook '{"tool_name":"Edit","tool_input":{"file_path":"./README.md"}}' 0 "Allow Edit to relative path"
58
+
59
+ # --- Allow: Empty input ---
60
+ test_hook '{}' 0 "Allow empty input"
61
+ test_hook '{"tool_name":"Write","tool_input":{}}' 0 "Allow missing file_path"
62
+
63
+ # --- Block: Tilde expansion ---
64
+ test_hook "{\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"~/.bashrc\"}}" 2 "Block Write to ~/.bashrc (tilde)"
65
+
66
+ echo ""
67
+ echo "Results: $PASS passed, $FAIL failed out of $((PASS + FAIL)) tests"
68
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,55 @@
1
+ #!/bin/bash
2
+ # Tests for effort-tracking-logger.sh
3
+ HOOK="examples/effort-tracking-logger.sh"
4
+ PASS=0 FAIL=0
5
+
6
+ assert_contains() { if echo "$2" | grep -q "$3"; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $1 (expected '$3')"; fi; }
7
+ assert_exit() { if [ "$2" -eq "$3" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $1 (exit $2, expected $3)"; fi; }
8
+
9
+ LOG_DIR="${HOME}/.claude/effort-log"
10
+ LOG_FILE="$LOG_DIR/$(date +%Y-%m-%d).jsonl"
11
+ rm -rf "$LOG_DIR"
12
+
13
+ # Test 1: Creates log directory
14
+ echo '{"tool_name":"Bash","was_error":"false"}' | bash "$HOOK" 2>&1
15
+ RC=$?
16
+ assert_exit "exit 0" "$RC" 0
17
+ if [ -d "$LOG_DIR" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: log dir not created"; fi
18
+
19
+ # Test 2: Log file created with valid JSONL
20
+ if [ -f "$LOG_FILE" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: log file not created"; fi
21
+ ENTRY=$(cat "$LOG_FILE")
22
+ assert_contains "has timestamp" "$ENTRY" "timestamp"
23
+ assert_contains "has tool name" "$ENTRY" "Bash"
24
+ assert_contains "has error field" "$ENTRY" "error"
25
+
26
+ # Test 3: Valid JSON
27
+ python3 -c "import json; json.loads(open('$LOG_FILE').read().strip())" 2>/dev/null
28
+ if [ $? -eq 0 ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: invalid JSON"; fi
29
+
30
+ # Test 4: Multiple entries appended
31
+ echo '{"tool_name":"Read","was_error":"false"}' | bash "$HOOK" 2>&1
32
+ echo '{"tool_name":"Edit","was_error":"true"}' | bash "$HOOK" 2>&1
33
+ LINES=$(wc -l < "$LOG_FILE")
34
+ if [ "$LINES" -eq 3 ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: expected 3 lines, got $LINES"; fi
35
+
36
+ # Test 5: Error field correctly parsed
37
+ LAST=$(tail -1 "$LOG_FILE")
38
+ assert_contains "error=true parsed" "$LAST" '"error": true'
39
+
40
+ # Test 6: Tool name correctly parsed
41
+ SECOND=$(sed -n '2p' "$LOG_FILE")
42
+ assert_contains "Read tool name" "$SECOND" '"tool": "Read"'
43
+
44
+ # Test 7: Unknown tool handled
45
+ echo '{}' | bash "$HOOK" 2>&1
46
+ RC=$?
47
+ assert_exit "unknown tool exit 0" "$RC" 0
48
+ LAST=$(tail -1 "$LOG_FILE")
49
+ assert_contains "unknown tool logged" "$LAST" "unknown"
50
+
51
+ # Cleanup
52
+ rm -rf "$LOG_DIR"
53
+
54
+ echo "effort-tracking-logger: $PASS passed, $FAIL failed"
55
+ exit $FAIL
@@ -0,0 +1,164 @@
1
+ #!/bin/bash
2
+ # Tests for exploration-budget-guard.sh
3
+ set -euo pipefail
4
+
5
+ HOOK="$(dirname "$0")/../examples/exploration-budget-guard.sh"
6
+ PASS=0
7
+ FAIL=0
8
+ STATE_FILE="/tmp/.cc-exploration-budget/exploration-count"
9
+
10
+ setup() {
11
+ rm -f "$STATE_FILE"
12
+ mkdir -p /tmp/.cc-exploration-budget
13
+ }
14
+
15
+ run_hook() {
16
+ echo "$1" | bash "$HOOK" 2>&1 || true
17
+ }
18
+
19
+ assert_pass() {
20
+ local desc="$1"
21
+ local input="$2"
22
+ output=$(echo "$input" | bash "$HOOK" 2>&1) && rc=0 || rc=$?
23
+ if [ "$rc" -eq 0 ] && ! echo "$output" | grep -q "WARNING\|BLOCKED"; then
24
+ PASS=$((PASS + 1))
25
+ echo " PASS: $desc"
26
+ else
27
+ FAIL=$((FAIL + 1))
28
+ echo " FAIL: $desc (expected clean pass, rc=$rc)"
29
+ echo " output: $output"
30
+ fi
31
+ }
32
+
33
+ assert_warn() {
34
+ local desc="$1"
35
+ local input="$2"
36
+ output=$(echo "$input" | bash "$HOOK" 2>&1) && rc=0 || rc=$?
37
+ if [ $rc -eq 0 ] && echo "$output" | grep -q "WARNING"; then
38
+ PASS=$((PASS + 1))
39
+ echo " PASS: $desc"
40
+ else
41
+ FAIL=$((FAIL + 1))
42
+ echo " FAIL: $desc (expected warning, rc=$rc)"
43
+ echo " output: $output"
44
+ fi
45
+ }
46
+
47
+ assert_block() {
48
+ local desc="$1"
49
+ local input="$2"
50
+ output=$(echo "$input" | bash "$HOOK" 2>&1) && rc=0 || rc=$?
51
+ if [ $rc -eq 2 ] && echo "$output" | grep -q "BLOCKED"; then
52
+ PASS=$((PASS + 1))
53
+ echo " PASS: $desc"
54
+ else
55
+ FAIL=$((FAIL + 1))
56
+ echo " FAIL: $desc (expected block, rc=$rc)"
57
+ echo " output: $output"
58
+ fi
59
+ }
60
+
61
+ READ_INPUT='{"tool_name":"Read","tool_input":{"file_path":"/tmp/test.txt"}}'
62
+ GLOB_INPUT='{"tool_name":"Glob","tool_input":{"pattern":"*.ts"}}'
63
+ GREP_INPUT='{"tool_name":"Grep","tool_input":{"pattern":"foo"}}'
64
+ EDIT_INPUT='{"tool_name":"Edit","tool_input":{"file_path":"/tmp/test.txt","old_string":"a","new_string":"b"}}'
65
+ WRITE_INPUT='{"tool_name":"Write","tool_input":{"file_path":"/tmp/test.txt","content":"hello"}}'
66
+ OTHER_INPUT='{"tool_name":"Bash","tool_input":{"command":"ls"}}'
67
+
68
+ echo "=== exploration-budget-guard tests ==="
69
+
70
+ # Test 1: Single read passes
71
+ setup
72
+ assert_pass "single Read passes" "$READ_INPUT"
73
+
74
+ # Test 2: Non-tracked tool passes
75
+ setup
76
+ assert_pass "Bash is not tracked" "$OTHER_INPUT"
77
+
78
+ # Test 3: Edit resets counter
79
+ setup
80
+ for i in $(seq 1 20); do
81
+ echo "$READ_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
82
+ done
83
+ echo "$EDIT_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
84
+ assert_pass "Read after Edit reset passes" "$READ_INPUT"
85
+
86
+ # Test 4: Write resets counter
87
+ setup
88
+ for i in $(seq 1 20); do
89
+ echo "$READ_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
90
+ done
91
+ echo "$WRITE_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
92
+ assert_pass "Read after Write reset passes" "$READ_INPUT"
93
+
94
+ # Test 5: Warning at threshold
95
+ setup
96
+ for i in $(seq 1 24); do
97
+ echo "$READ_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
98
+ done
99
+ assert_warn "warns at 25 reads" "$READ_INPUT"
100
+
101
+ # Test 6: Different read tools count together
102
+ setup
103
+ for i in $(seq 1 8); do
104
+ echo "$READ_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
105
+ echo "$GLOB_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
106
+ echo "$GREP_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
107
+ done
108
+ assert_warn "mixed read tools warn at 25" "$READ_INPUT"
109
+
110
+ # Test 7: Block at 40
111
+ setup
112
+ for i in $(seq 1 39); do
113
+ echo "$READ_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
114
+ done
115
+ assert_block "blocks at 40 reads" "$READ_INPUT"
116
+
117
+ # Test 8: Block shows count
118
+ setup
119
+ for i in $(seq 1 41); do
120
+ echo "$READ_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
121
+ done
122
+ output=$(echo "$READ_INPUT" | bash "$HOOK" 2>&1 || true)
123
+ if echo "$output" | grep -q "EXCEEDED"; then
124
+ PASS=$((PASS + 1))
125
+ echo " PASS: block message shows EXCEEDED"
126
+ else
127
+ FAIL=$((FAIL + 1))
128
+ echo " FAIL: block message should show EXCEEDED"
129
+ fi
130
+
131
+ # Test 9: Timeout reset (simulate 11 min gap)
132
+ setup
133
+ for i in $(seq 1 30); do
134
+ echo "$READ_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
135
+ done
136
+ # Fake old timestamp
137
+ echo "30 $(($(date +%s) - 700))" > "$STATE_FILE"
138
+ assert_pass "resets after 10min gap" "$READ_INPUT"
139
+
140
+ # Test 10: Glob passes normally
141
+ setup
142
+ assert_pass "single Glob passes" "$GLOB_INPUT"
143
+
144
+ # Test 11: Grep passes normally
145
+ setup
146
+ assert_pass "single Grep passes" "$GREP_INPUT"
147
+
148
+ # Test 12: Counter persists across different read tools
149
+ setup
150
+ for i in $(seq 1 10); do
151
+ echo "$READ_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
152
+ done
153
+ for i in $(seq 1 10); do
154
+ echo "$GLOB_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
155
+ done
156
+ for i in $(seq 1 5); do
157
+ echo "$GREP_INPUT" | bash "$HOOK" >/dev/null 2>&1 || true
158
+ done
159
+ # Count should be 25 now
160
+ assert_warn "warns at 25 mixed reads" "$GREP_INPUT"
161
+
162
+ echo ""
163
+ echo "Results: $PASS passed, $FAIL failed out of $((PASS + FAIL)) tests"
164
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,59 @@
1
+ #!/bin/bash
2
+ # Tests for financial-operation-guard.sh
3
+ # Run: bash tests/test-financial-operation-guard.sh
4
+ set -euo pipefail
5
+
6
+ PASS=0
7
+ FAIL=0
8
+ HOOK="$(dirname "$0")/../examples/financial-operation-guard.sh"
9
+
10
+ test_hook() {
11
+ local input="$1" expected_exit="$2" desc="$3"
12
+ local actual_exit=0
13
+ echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
14
+ if [ "$actual_exit" -eq "$expected_exit" ]; then
15
+ echo " PASS: $desc"
16
+ PASS=$((PASS + 1))
17
+ else
18
+ echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
19
+ FAIL=$((FAIL + 1))
20
+ fi
21
+ }
22
+
23
+ echo "financial-operation-guard.sh tests"
24
+ echo ""
25
+
26
+ # --- Block: Exchange API calls (#46828 pattern) ---
27
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"python3 -c \"ex.transfer(USDT, 1446.65, spot, swap)\" bitget"}}' 2 "Block Bitget fund transfer (#46828 exact pattern)"
28
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"curl https://api.binance.com/api/v3/order -X POST"}}' 2 "Block Binance order API"
29
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"python3 trade.py --exchange bybit --withdraw 500"}}' 2 "Block Bybit withdrawal"
30
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"ccxt kraken transfer USDT from spot to futures"}}' 2 "Block Kraken transfer via ccxt"
31
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"python3 -c \"coinbase.create_order(symbol, side, amount)\""}}' 2 "Block Coinbase order"
32
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"curl -X POST https://api.okx.com/api/v5/trade/order"}}' 2 "Block OKX trade order"
33
+
34
+ # --- Block: Generic crypto transfers ---
35
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"python3 -c \"transfer(USDT, 1000, wallet_a, wallet_b)\""}}' 2 "Block USDT transfer"
36
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"send_eth --to 0xabc --amount 5 --wallet main"}}' 2 "Block ETH send"
37
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"python3 withdraw_btc.py --balance 0.5"}}' 2 "Block BTC withdrawal"
38
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"python3 -c \"swap(usdc, 500, from_wallet)\""}}' 2 "Block USDC swap"
39
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"bridge sol from mainnet to polygon --funds 100"}}' 2 "Block SOL bridge"
40
+
41
+ # --- Block: Payment processor operations ---
42
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"curl -X POST https://api.stripe.com/v1/charges -d amount=5000"}}' 2 "Block Stripe charge"
43
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"python3 -c \"stripe.Transfer.create(amount=1000)\""}}' 2 "Block Stripe transfer"
44
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"paypal-cli send payment --to user@email.com --amount 200"}}' 2 "Block PayPal payment"
45
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"python3 -c \"square.payments.create_payment(body)\""}}' 2 "Block Square payment"
46
+
47
+ # --- Allow: Safe operations ---
48
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"curl https://api.example.com/data"}}' 0 "Allow normal API call"
49
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"python3 analyze_trades.py --read-only"}}' 0 "Allow read-only trade analysis"
50
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"cat balance.txt"}}' 0 "Allow reading balance file"
51
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"echo transfer complete"}}' 0 "Allow echo containing transfer"
52
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"git push origin main"}}' 0 "Allow git push"
53
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"npm install stripe"}}' 0 "Allow npm install stripe (not an operation)"
54
+ test_hook '{}' 0 "Allow empty input"
55
+ test_hook '{"tool_name":"Bash","tool_input":{}}' 0 "Allow no command"
56
+
57
+ echo ""
58
+ echo "Results: $PASS passed, $FAIL failed out of $((PASS + FAIL)) tests"
59
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,59 @@
1
+ #!/bin/bash
2
+ # Tests for home-critical-bash-guard.sh
3
+ # Run: bash tests/test-home-critical-bash-guard.sh
4
+ set -euo pipefail
5
+
6
+ PASS=0
7
+ FAIL=0
8
+ HOOK="$(dirname "$0")/../examples/home-critical-bash-guard.sh"
9
+
10
+ test_hook() {
11
+ local input="$1" expected_exit="$2" desc="$3"
12
+ local actual_exit=0
13
+ echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
14
+ if [ "$actual_exit" -eq "$expected_exit" ]; then
15
+ echo " PASS: $desc"
16
+ PASS=$((PASS + 1))
17
+ else
18
+ echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
19
+ FAIL=$((FAIL + 1))
20
+ fi
21
+ }
22
+
23
+ echo "home-critical-bash-guard.sh tests"
24
+ echo ""
25
+
26
+ # --- Block: rm on critical paths ---
27
+ test_hook "{\"tool_input\":{\"command\":\"rm -rf $HOME/.ssh\"}}" 2 "Block rm -rf ~/.ssh"
28
+ test_hook "{\"tool_input\":{\"command\":\"rm $HOME/.git-credentials\"}}" 2 "Block rm ~/.git-credentials"
29
+ test_hook "{\"tool_input\":{\"command\":\"rm -f $HOME/.bashrc\"}}" 2 "Block rm ~/.bashrc"
30
+ test_hook "{\"tool_input\":{\"command\":\"sudo rm $HOME/.zshrc\"}}" 2 "Block sudo rm ~/.zshrc"
31
+ test_hook "{\"tool_input\":{\"command\":\"rm $HOME/.npmrc\"}}" 2 "Block rm ~/.npmrc"
32
+ test_hook "{\"tool_input\":{\"command\":\"rm -rf $HOME/.gnupg\"}}" 2 "Block rm -rf ~/.gnupg"
33
+ test_hook "{\"tool_input\":{\"command\":\"rm $HOME/.aws/credentials\"}}" 2 "Block rm ~/.aws/credentials"
34
+
35
+ # --- Block: mv on critical paths ---
36
+ test_hook "{\"tool_input\":{\"command\":\"mv $HOME/.bashrc /tmp/\"}}" 2 "Block mv ~/.bashrc"
37
+ test_hook "{\"tool_input\":{\"command\":\"mv $HOME/.ssh/config /tmp/bak\"}}" 2 "Block mv ~/.ssh/config"
38
+
39
+ # --- Block: Truncation via redirect ---
40
+ test_hook "{\"tool_input\":{\"command\":\"> $HOME/.bashrc\"}}" 2 "Block > ~/.bashrc truncation"
41
+ test_hook "{\"tool_input\":{\"command\":\"echo '' > $HOME/.zshrc\"}}" 2 "Block echo > ~/.zshrc"
42
+
43
+ # --- Block: chmod 777 on critical files ---
44
+ test_hook "{\"tool_input\":{\"command\":\"chmod 777 $HOME/.ssh/id_rsa\"}}" 2 "Block chmod 777 on .ssh/id_rsa"
45
+
46
+ # --- Allow: Safe commands ---
47
+ test_hook '{"tool_input":{"command":"rm -rf node_modules"}}' 0 "Allow rm node_modules"
48
+ test_hook '{"tool_input":{"command":"rm /tmp/test.txt"}}' 0 "Allow rm in /tmp"
49
+ test_hook '{"tool_input":{"command":"ls -la ~/.ssh"}}' 0 "Allow ls on .ssh (read-only)"
50
+ test_hook '{"tool_input":{"command":"cat ~/.bashrc"}}' 0 "Allow cat on .bashrc (read-only)"
51
+ test_hook '{"tool_input":{"command":"git status"}}' 0 "Allow git commands"
52
+
53
+ # --- Allow: Empty input ---
54
+ test_hook '{}' 0 "Allow empty input"
55
+ test_hook '{"tool_input":{}}' 0 "Allow missing command"
56
+
57
+ echo ""
58
+ echo "Results: $PASS passed, $FAIL failed out of $((PASS + FAIL)) tests"
59
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,55 @@
1
+ #!/bin/bash
2
+ # Tests for model-version-change-alert.sh
3
+ HOOK="examples/model-version-change-alert.sh"
4
+ PASS=0 FAIL=0
5
+
6
+ assert_contains() { if echo "$2" | grep -q "$3"; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $1 (expected '$3')"; fi; }
7
+ assert_not_contains() { if ! echo "$2" | grep -q "$3"; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $1 (unexpected '$3')"; fi; }
8
+ assert_exit() { if [ "$2" -eq "$3" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $1 (exit $2, expected $3)"; fi; }
9
+
10
+ HISTORY="/tmp/cc-model-version-history"
11
+ rm -f "$HISTORY"
12
+
13
+ # Test 1: First run (no history) — no alert
14
+ OUT=$(CLAUDE_MODEL="opus-4.7" bash "$HOOK" 2>&1)
15
+ RC=$?
16
+ assert_not_contains "first run should not alert" "$OUT" "MODEL CHANGED"
17
+ assert_exit "first run exit 0" "$RC" 0
18
+
19
+ # Test 2: Same model — no alert
20
+ OUT=$(CLAUDE_MODEL="opus-4.7" bash "$HOOK" 2>&1)
21
+ assert_not_contains "same model no alert" "$OUT" "MODEL CHANGED"
22
+
23
+ # Test 3: Model changed — should alert
24
+ OUT=$(CLAUDE_MODEL="opus-4.6" bash "$HOOK" 2>&1)
25
+ assert_contains "model change should alert" "$OUT" "MODEL CHANGED"
26
+ assert_contains "should show old model" "$OUT" "opus-4.7"
27
+ assert_contains "should show new model" "$OUT" "opus-4.6"
28
+ assert_contains "should reference issue" "$OUT" "#49689"
29
+
30
+ # Test 4: Exit code always 0
31
+ RC=$?
32
+ assert_exit "exit 0 on alert" "$RC" 0
33
+
34
+ # Test 5: Unknown model — no update, no alert
35
+ echo "opus-4.7" > "$HISTORY"
36
+ OUT=$(bash "$HOOK" 2>&1) # No CLAUDE_MODEL set
37
+ assert_not_contains "unknown model no alert" "$OUT" "MODEL CHANGED"
38
+
39
+ # Test 6: History file is updated correctly
40
+ echo "opus-4.6" > "$HISTORY"
41
+ CLAUDE_MODEL="opus-4.7" bash "$HOOK" > /dev/null 2>&1
42
+ STORED=$(cat "$HISTORY")
43
+ if [ "$STORED" = "opus-4.7" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: history should store new model (got $STORED)"; fi
44
+
45
+ # Test 7: Back-to-back changes detected
46
+ CLAUDE_MODEL="sonnet-4.5" bash "$HOOK" > /dev/null 2>&1
47
+ OUT=$(CLAUDE_MODEL="haiku-4.5" bash "$HOOK" 2>&1)
48
+ assert_contains "sequential change detected" "$OUT" "MODEL CHANGED"
49
+ assert_contains "shows sonnet" "$OUT" "sonnet-4.5"
50
+
51
+ # Cleanup
52
+ rm -f "$HISTORY"
53
+
54
+ echo "model-version-change-alert: $PASS passed, $FAIL failed"
55
+ exit $FAIL
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+ # Tests for move-delete-sequence-guard.sh
3
+ # Run: bash tests/test-move-delete-sequence-guard.sh
4
+ set -euo pipefail
5
+
6
+ PASS=0
7
+ FAIL=0
8
+ HOOK="$(dirname "$0")/../examples/move-delete-sequence-guard.sh"
9
+
10
+ test_hook() {
11
+ local input="$1" expected_exit="$2" desc="$3"
12
+ local actual_exit=0
13
+ echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
14
+ if [ "$actual_exit" -eq "$expected_exit" ]; then
15
+ echo " PASS: $desc"
16
+ PASS=$((PASS + 1))
17
+ else
18
+ echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
19
+ FAIL=$((FAIL + 1))
20
+ fi
21
+ }
22
+
23
+ echo "move-delete-sequence-guard.sh tests"
24
+ echo ""
25
+
26
+ # --- Block: mv + rm -rf on parent directory (#49129 pattern) ---
27
+ test_hook '{"tool_input":{"command":"mv /home/user/project/important.txt /tmp/ && rm -rf /home/user/project"}}' 2 "Block mv file then rm -rf parent (&&)"
28
+ test_hook '{"tool_input":{"command":"mv /home/user/project/src/main.py /tmp/backup/ ; rm -rf /home/user/project/src"}}' 2 "Block mv file then rm -rf parent (;)"
29
+ test_hook '{"tool_input":{"command":"mv /data/app/config.yml /tmp/ || rm -rf /data/app"}}' 2 "Block mv file then rm -rf parent (||)"
30
+
31
+ # --- Block: mv + rm -rf on same path ---
32
+ test_hook '{"tool_input":{"command":"mv /home/user/mydir /tmp/backup && rm -rf /home/user/mydir"}}' 2 "Block mv dir then rm -rf same dir"
33
+
34
+ # --- Block: mv + rm -rf on ancestor directory ---
35
+ test_hook '{"tool_input":{"command":"mv /home/user/project/src/lib/util.py /tmp/ && rm -rf /home/user/project"}}' 2 "Block mv file then rm -rf ancestor"
36
+ test_hook '{"tool_input":{"command":"mv /var/data/app/logs/today.log /tmp/ ; rm -rf /var/data"}}' 2 "Block mv file then rm -rf distant ancestor"
37
+
38
+ # --- Allow: mv and rm on unrelated paths ---
39
+ test_hook '{"tool_input":{"command":"mv /tmp/old.txt /tmp/new.txt && rm /var/log/app.log"}}' 0 "Allow mv and rm on unrelated paths"
40
+ test_hook '{"tool_input":{"command":"mv file.txt backup/ && rm other_file.txt"}}' 0 "Allow mv and rm on different files"
41
+
42
+ # --- Allow: mv only (no rm) ---
43
+ test_hook '{"tool_input":{"command":"mv /home/user/file.txt /tmp/"}}' 0 "Allow mv without rm"
44
+
45
+ # --- Allow: rm only (no mv) ---
46
+ test_hook '{"tool_input":{"command":"rm -rf /tmp/junk"}}' 0 "Allow rm without mv"
47
+
48
+ # --- Allow: Empty/missing input ---
49
+ test_hook '{}' 0 "Allow empty input"
50
+ test_hook '{"tool_input":{}}' 0 "Allow missing command"
51
+ test_hook '{"tool_input":{"command":""}}' 0 "Allow empty command"
52
+
53
+ # --- Allow: Safe operations ---
54
+ test_hook '{"tool_input":{"command":"ls -la && echo done"}}' 0 "Allow non-destructive compound command"
55
+ test_hook '{"tool_input":{"command":"git mv old.txt new.txt"}}' 0 "Allow git mv (no rm)"
56
+
57
+ # --- Block: Real-world attack patterns ---
58
+ test_hook '{"tool_input":{"command":"mv /home/user/projects/webapp/src/index.js /tmp/safe/ && rm -rf /home/user/projects/webapp/src"}}' 2 "Block real-world: save one file, delete rest of src"
59
+ test_hook '{"tool_input":{"command":"mv /home/user/data/important.db /tmp/ ; rm -r /home/user/data"}}' 2 "Block real-world: save DB, delete data dir"
60
+
61
+ echo ""
62
+ echo "Results: $PASS passed, $FAIL failed out of $((PASS + FAIL)) tests"
63
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,29 @@
1
+ set -euo pipefail
2
+ PASS=0
3
+ FAIL=0
4
+ HOOK="$(dirname "$0")/../examples/pr-duplicate-guard.sh"
5
+ test_hook() {
6
+ local input="$1" expected_exit="$2" desc="$3"
7
+ local actual_exit=0
8
+ echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
9
+ if [ "$actual_exit" -eq "$expected_exit" ]; then
10
+ echo " PASS: $desc"
11
+ PASS=$((PASS + 1))
12
+ else
13
+ echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
14
+ FAIL=$((FAIL + 1))
15
+ fi
16
+ }
17
+ echo "=== pr-duplicate-guard.sh ==="
18
+ test_hook '{"tool_input":{"command":"git push origin main"}}' 0 "git push passes through"
19
+ test_hook '{"tool_input":{"command":"npm publish"}}' 0 "npm publish passes through"
20
+ test_hook '{"tool_input":{"command":"echo hello"}}' 0 "echo passes through"
21
+ test_hook '{"tool_input":{"command":"gh pr list"}}' 0 "gh pr list passes through"
22
+ test_hook '{"tool_input":{"command":"gh pr view 123"}}' 0 "gh pr view passes through"
23
+ test_hook '{"tool_input":{"command":"gh issue create --title test"}}' 0 "gh issue create passes through"
24
+ test_hook '{"tool_input":{"command":"gh pr create --title \"test\" --body \"test\""}}' 0 "gh pr create on unique branch passes"
25
+ test_hook '{}' 0 "empty input passes"
26
+ test_hook '' 0 "blank input passes"
27
+ echo ""
28
+ echo "Results: $PASS passed, $FAIL failed"
29
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1