cc-safe-setup 29.6.40 → 29.7.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 (79) 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/context-size-alert.sh +38 -0
  17. package/examples/context-usage-drift-alert.sh +33 -0
  18. package/examples/dangerous-pip-flag-guard.sh +51 -0
  19. package/examples/deny-bypass-detector.sh +143 -0
  20. package/examples/dotenv-read-guard.sh +48 -0
  21. package/examples/dotfile-protection-guard.sh +60 -0
  22. package/examples/effort-tracking-logger.sh +30 -0
  23. package/examples/financial-operation-guard.sh +47 -0
  24. package/examples/full-rewrite-detector.sh +63 -0
  25. package/examples/home-critical-bash-guard.sh +56 -0
  26. package/examples/idle-session-cost-alert.sh +36 -0
  27. package/examples/model-version-alert.sh +18 -0
  28. package/examples/model-version-change-alert.sh +31 -0
  29. package/examples/move-delete-sequence-guard.sh +92 -0
  30. package/examples/pii-upload-guard.sh +72 -0
  31. package/examples/pr-duplicate-guard.sh +14 -0
  32. package/examples/production-port-kill-guard.sh +60 -0
  33. package/examples/quota-reset-cycle-monitor.sh +30 -0
  34. package/examples/repo-visibility-guard.sh +33 -0
  35. package/examples/sandbox-relative-path-audit.sh +51 -0
  36. package/examples/session-agent-cost-limiter.sh +43 -0
  37. package/examples/session-cost-alert.sh +62 -0
  38. package/examples/session-memory-watchdog.sh +9 -0
  39. package/examples/settings-integrity-monitor.sh +55 -0
  40. package/examples/settings-json-model-guard.sh +89 -0
  41. package/examples/shell-config-truncation-guard.sh +97 -0
  42. package/examples/shell-wrapper-guard.sh +4 -4
  43. package/examples/subagent-spawn-rate-monitor.sh +34 -0
  44. package/examples/subcommand-chain-guard.sh +44 -0
  45. package/examples/system-dir-protection-guard.sh +100 -0
  46. package/examples/thinking-display-enforcer.sh +25 -0
  47. package/examples/tool-retry-budget-guard.sh +59 -0
  48. package/examples/worktree-branch-pollution-detector.sh +35 -0
  49. package/examples/worktree-create-log.sh +6 -0
  50. package/examples/worktree-hook-linker.sh +72 -0
  51. package/examples/worktree-remove-uncommitted-guard.sh +20 -0
  52. package/hooks/hooks.json +60 -0
  53. package/index.mjs +92 -6
  54. package/memory/market-anthropic-japan-strategy-2026-04-13.md +4 -0
  55. package/package.json +2 -2
  56. package/plugins/credential-guard/.claude-plugin/plugin.json +58 -0
  57. package/plugins/git-protection/.claude-plugin/plugin.json +58 -0
  58. package/plugins/safety-essentials/.claude-plugin/plugin.json +58 -0
  59. package/plugins/token-guard/.claude-plugin/plugin.json +51 -0
  60. package/skills/safety-setup/SKILL.md +47 -0
  61. package/tests/dotenv-read-guard.test.sh +65 -0
  62. package/tests/test-auto-mode-safety-enforcer.sh +55 -0
  63. package/tests/test-case-insensitive-path-guard.sh +78 -0
  64. package/tests/test-context-usage-drift-alert.sh +52 -0
  65. package/tests/test-dangerous-pip-flag-guard.sh +56 -0
  66. package/tests/test-dotfile-protection-guard.sh +68 -0
  67. package/tests/test-effort-tracking-logger.sh +55 -0
  68. package/tests/test-financial-operation-guard.sh +59 -0
  69. package/tests/test-home-critical-bash-guard.sh +59 -0
  70. package/tests/test-model-version-change-alert.sh +55 -0
  71. package/tests/test-move-delete-sequence-guard.sh +63 -0
  72. package/tests/test-pr-duplicate-guard.sh +29 -0
  73. package/tests/test-quota-reset-cycle-monitor.sh +52 -0
  74. package/tests/test-shell-config-truncation-guard.sh +104 -0
  75. package/tests/test-subagent-spawn-rate-monitor.sh +43 -0
  76. package/tests/test-system-dir-protection-guard.sh +81 -0
  77. package/tests/test-tool-retry-budget-guard.sh +75 -0
  78. package/tests/test-worktree-branch-pollution-detector.sh +50 -0
  79. package/tests/test-worktree-lifecycle-hooks.sh +29 -0
@@ -0,0 +1,65 @@
1
+ #!/bin/bash
2
+ # Tests for dotenv-read-guard.sh
3
+ HOOK="$(dirname "$0")/../examples/dotenv-read-guard.sh"
4
+ PASS=0; FAIL=0
5
+
6
+ run_test() {
7
+ local desc="$1" input="$2" expect="$3"
8
+ result=$(echo "$input" | bash "$HOOK" 2>/dev/null; echo $?)
9
+ code=$(echo "$result" | tail -1)
10
+ if [ "$code" = "$expect" ]; then
11
+ echo "PASS: $desc"
12
+ ((PASS++))
13
+ else
14
+ echo "FAIL: $desc (expected $expect, got $code)"
15
+ ((FAIL++))
16
+ fi
17
+ }
18
+
19
+ # Should block .env files
20
+ run_test "Block .env" \
21
+ '{"tool_input":{"file_path":"/home/user/project/.env"}}' "2"
22
+
23
+ run_test "Block .env.local" \
24
+ '{"tool_input":{"file_path":"/app/.env.local"}}' "2"
25
+
26
+ run_test "Block .env.production" \
27
+ '{"tool_input":{"file_path":"/deploy/.env.production"}}' "2"
28
+
29
+ run_test "Block .env.staging" \
30
+ '{"tool_input":{"file_path":"/app/.env.staging"}}' "2"
31
+
32
+ run_test "Block .env.development" \
33
+ '{"tool_input":{"file_path":"/app/.env.development"}}' "2"
34
+
35
+ run_test "Block .env.test" \
36
+ '{"tool_input":{"file_path":"/project/.env.test"}}' "2"
37
+
38
+ # Should allow non-.env files
39
+ run_test "Allow .env.example" \
40
+ '{"tool_input":{"file_path":"/project/.env.example"}}' "0"
41
+
42
+ run_test "Allow README.md" \
43
+ '{"tool_input":{"file_path":"/project/README.md"}}' "0"
44
+
45
+ run_test "Allow package.json" \
46
+ '{"tool_input":{"file_path":"/project/package.json"}}' "0"
47
+
48
+ run_test "Allow config.ts" \
49
+ '{"tool_input":{"file_path":"/src/config.ts"}}' "0"
50
+
51
+ run_test "Allow env.ts (not dotenv)" \
52
+ '{"tool_input":{"file_path":"/src/env.ts"}}' "0"
53
+
54
+ run_test "Allow .envrc (direnv)" \
55
+ '{"tool_input":{"file_path":"/project/.envrc"}}' "0"
56
+
57
+ # Edge cases
58
+ run_test "Empty input" '{}' "0"
59
+
60
+ run_test "No file_path" \
61
+ '{"tool_input":{}}' "0"
62
+
63
+ echo ""
64
+ echo "Results: $PASS passed, $FAIL failed"
65
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,55 @@
1
+ #!/bin/bash
2
+ # Tests for auto-mode-safety-enforcer.sh
3
+ set -euo pipefail
4
+
5
+ PASS=0
6
+ FAIL=0
7
+ HOOK="$(dirname "$0")/../examples/auto-mode-safety-enforcer.sh"
8
+
9
+ test_hook() {
10
+ local input="$1" expected_exit="$2" desc="$3"
11
+ local actual_exit=0
12
+ echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
13
+ if [ "$actual_exit" -eq "$expected_exit" ]; then
14
+ echo " PASS: $desc"
15
+ PASS=$((PASS + 1))
16
+ else
17
+ echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
18
+ FAIL=$((FAIL + 1))
19
+ fi
20
+ }
21
+
22
+ echo "auto-mode-safety-enforcer.sh tests"
23
+ echo ""
24
+
25
+ # --- Block: Critical rm operations ---
26
+ test_hook '{"tool_input":{"command":"rm -rf /"}}' 2 "Block rm -rf /"
27
+ test_hook '{"tool_input":{"command":"rm -rf ~"}}' 2 "Block rm -rf ~"
28
+ test_hook '{"tool_input":{"command":"rm -rf ~/."}}' 2 "Block rm -rf ~/."
29
+ test_hook '{"tool_input":{"command":"sudo rm -rf /home"}}' 2 "Block sudo rm -rf /home"
30
+ test_hook '{"tool_input":{"command":"rm -rf /etc"}}' 2 "Block rm -rf /etc"
31
+ test_hook '{"tool_input":{"command":"rm -rf /usr"}}' 2 "Block rm -rf /usr"
32
+ test_hook "{\"tool_input\":{\"command\":\"rm -rf $HOME/.ssh\"}}" 2 "Block rm -rf ~/.ssh"
33
+ test_hook "{\"tool_input\":{\"command\":\"rm $HOME/.git-credentials\"}}" 2 "Block rm ~/.git-credentials"
34
+ test_hook "{\"tool_input\":{\"command\":\"rm -f $HOME/.bashrc\"}}" 2 "Block rm ~/.bashrc"
35
+
36
+ # --- Block: Disk operations ---
37
+ test_hook '{"tool_input":{"command":"sudo dd if=/dev/zero of=/dev/sda"}}' 2 "Block dd to disk"
38
+ test_hook '{"tool_input":{"command":"sudo mkfs.ext4 /dev/sda1"}}' 2 "Block mkfs"
39
+ test_hook '{"tool_input":{"command":"sudo fdisk /dev/sda"}}' 2 "Block fdisk"
40
+
41
+ # --- Block: System process kill ---
42
+ test_hook '{"tool_input":{"command":"kill -9 1"}}' 2 "Block kill PID 1"
43
+ test_hook '{"tool_input":{"command":"killall systemd"}}' 2 "Block killall systemd"
44
+
45
+ # --- Allow: Safe operations ---
46
+ test_hook '{"tool_input":{"command":"rm -rf node_modules"}}' 0 "Allow rm node_modules"
47
+ test_hook '{"tool_input":{"command":"rm /tmp/test.txt"}}' 0 "Allow rm in /tmp"
48
+ test_hook '{"tool_input":{"command":"ls -la"}}' 0 "Allow ls"
49
+ test_hook '{"tool_input":{"command":"git status"}}' 0 "Allow git"
50
+ test_hook '{"tool_input":{"command":"npm install"}}' 0 "Allow npm install"
51
+ test_hook '{}' 0 "Allow empty input"
52
+
53
+ echo ""
54
+ echo "Results: $PASS passed, $FAIL failed out of $((PASS + FAIL)) tests"
55
+ [ "$FAIL" -eq 0 ] && exit 0 || exit 1
@@ -0,0 +1,78 @@
1
+ #!/bin/bash
2
+ # Tests for case-insensitive-path-guard.sh
3
+ # Run: bash tests/test-case-insensitive-path-guard.sh
4
+ # NOTE: Full case-mismatch detection only works on macOS APFS.
5
+ # On Linux, the hook exits 0 for all inputs (no case-insensitive FS).
6
+ # These tests verify the non-macOS path (exit 0) and input parsing.
7
+ set -euo pipefail
8
+
9
+ PASS=0
10
+ FAIL=0
11
+ HOOK="$(dirname "$0")/../examples/case-insensitive-path-guard.sh"
12
+
13
+ test_hook() {
14
+ local input="$1" expected_exit="$2" desc="$3"
15
+ local actual_exit=0
16
+ echo "$input" | bash "$HOOK" > /dev/null 2>/dev/null || actual_exit=$?
17
+ if [ "$actual_exit" -eq "$expected_exit" ]; then
18
+ echo " PASS: $desc"
19
+ PASS=$((PASS + 1))
20
+ else
21
+ echo " FAIL: $desc (expected exit $expected_exit, got $actual_exit)"
22
+ FAIL=$((FAIL + 1))
23
+ fi
24
+ }
25
+
26
+ echo "case-insensitive-path-guard.sh tests"
27
+ echo ""
28
+
29
+ # On Linux, all commands should pass through (exit 0)
30
+ # The hook only activates on macOS (uname == Darwin)
31
+ IS_LINUX=0
32
+ [ "$(uname)" != "Darwin" ] && IS_LINUX=1
33
+
34
+ if [ "$IS_LINUX" -eq 1 ]; then
35
+ echo "Running on Linux — all tests should pass through (exit 0)"
36
+ echo ""
37
+
38
+ # --- Pass-through on Linux ---
39
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"rm -rf ~/Projects"}}' 0 "Linux: rm -rf passes through"
40
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"rm -rf ~/Documents"}}' 0 "Linux: rm Documents passes through"
41
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"mv ~/old ~/new"}}' 0 "Linux: mv passes through"
42
+
43
+ # --- Non-destructive commands always pass ---
44
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"ls ~/Projects"}}' 0 "Linux: ls passes through"
45
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"git status"}}' 0 "Linux: git status passes through"
46
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"echo hello"}}' 0 "Linux: echo passes through"
47
+
48
+ # --- Safe paths always pass ---
49
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"rm -rf node_modules"}}' 0 "Linux: rm node_modules passes"
50
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"rm -rf /tmp/test"}}' 0 "Linux: rm /tmp passes"
51
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"rm -rf .cache"}}' 0 "Linux: rm .cache passes"
52
+
53
+ # --- Empty/missing inputs ---
54
+ test_hook '{"tool_name":"Bash","tool_input":{"command":""}}' 0 "Empty command"
55
+ test_hook '{"tool_name":"Bash","tool_input":{}}' 0 "No command"
56
+ test_hook '{}' 0 "Empty JSON"
57
+
58
+ else
59
+ echo "Running on macOS — testing case-mismatch detection"
60
+ echo ""
61
+
62
+ # On macOS, safe paths should still pass
63
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"rm -rf node_modules"}}' 0 "macOS: rm node_modules passes"
64
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"rm -rf /tmp/test"}}' 0 "macOS: rm /tmp passes"
65
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"rm -rf __pycache__"}}' 0 "macOS: rm __pycache__ passes"
66
+
67
+ # Non-destructive commands pass
68
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"ls ~/Projects"}}' 0 "macOS: ls passes through"
69
+ test_hook '{"tool_name":"Bash","tool_input":{"command":"git status"}}' 0 "macOS: git status passes"
70
+
71
+ # Empty inputs
72
+ test_hook '{"tool_name":"Bash","tool_input":{"command":""}}' 0 "Empty command"
73
+ test_hook '{"tool_name":"Bash","tool_input":{}}' 0 "No command"
74
+ fi
75
+
76
+ echo ""
77
+ echo "Results: $PASS passed, $FAIL failed out of $((PASS + FAIL))"
78
+ [ "$FAIL" -eq 0 ] && echo "ALL TESTS PASSED" || exit 1
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ # Tests for context-usage-drift-alert.sh
3
+ HOOK="examples/context-usage-drift-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
+
9
+ # Use a test-specific counter file
10
+ COUNTER_FILE="/tmp/cc-context-usage-counter-$(date +%Y%m%d)"
11
+ rm -f "$COUNTER_FILE"
12
+
13
+ # Test 1: Calls 1-49 should not warn
14
+ for i in $(seq 1 49); do
15
+ echo '{}' | bash "$HOOK" 2>/dev/null
16
+ done
17
+ OUT=$(echo '{}' | bash "$HOOK" 2>&1)
18
+ # Call 50 should give checkpoint
19
+ assert_contains "call 50 should checkpoint" "$OUT" "checkpoint"
20
+ assert_contains "should mention /cost" "$OUT" "/cost"
21
+
22
+ # Test 2: Calls 51-99 should not warn
23
+ for i in $(seq 51 99); do
24
+ echo '{}' | bash "$HOOK" 2>/dev/null
25
+ done
26
+ OUT=$(echo '{}' | bash "$HOOK" 2>&1)
27
+ # Call 100 should give strong warning
28
+ assert_contains "call 100 should warn high" "$OUT" "HIGH CONTEXT"
29
+ assert_contains "should mention /compact" "$OUT" "/compact"
30
+ assert_contains "should reference issue" "$OUT" "#50204"
31
+
32
+ # Test 3: Calls 101-149
33
+ for i in $(seq 101 149); do
34
+ echo '{}' | bash "$HOOK" 2>/dev/null
35
+ done
36
+ OUT=$(echo '{}' | bash "$HOOK" 2>&1)
37
+ # Call 150 should give critical warning
38
+ assert_contains "call 150 critical warning" "$OUT" "VERY HIGH"
39
+ assert_contains "should mention saving state" "$OUT" "Save"
40
+
41
+ # Test 4: Normal calls between thresholds should be silent
42
+ rm -f "$COUNTER_FILE"
43
+ echo "10" > "$COUNTER_FILE"
44
+ OUT=$(echo '{}' | bash "$HOOK" 2>&1)
45
+ assert_not_contains "non-threshold call should be silent" "$OUT" "checkpoint"
46
+ assert_not_contains "non-threshold no warning" "$OUT" "HIGH"
47
+
48
+ # Cleanup
49
+ rm -f "$COUNTER_FILE"
50
+
51
+ echo "Results: $PASS passed, $FAIL failed"
52
+ [ "$FAIL" -eq 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,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