cc-safe-setup 29.6.0 → 29.6.2
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.
- package/COOKBOOK.md +70 -0
- package/README.md +43 -4
- package/TROUBLESHOOTING.md +30 -0
- package/examples/api-rate-limit-tracker.sh +51 -0
- package/examples/auto-answer-question.sh +67 -0
- package/examples/auto-approve-readonly-tools.sh +10 -0
- package/examples/aws-production-guard.sh +40 -0
- package/examples/banned-command-guard.sh +48 -0
- package/examples/bash-heuristic-approver.sh +59 -0
- package/examples/bash-trace-guard.sh +48 -0
- package/examples/block-database-wipe.sh +1 -1
- package/examples/classifier-fallback-allow.sh +70 -0
- package/examples/commit-message-check.sh +8 -1
- package/examples/commit-message-quality.sh +35 -0
- package/examples/credential-exfil-guard.sh +12 -0
- package/examples/cwd-reminder.sh +37 -0
- package/examples/dependency-install-guard.sh +84 -0
- package/examples/deploy-guard.sh +1 -1
- package/examples/detect-mixed-indentation.sh +33 -0
- package/examples/disk-space-check.sh +42 -0
- package/examples/docker-dangerous-guard.sh +47 -0
- package/examples/dockerfile-lint.sh +58 -0
- package/examples/edit-always-allow.sh +53 -0
- package/examples/env-file-gitignore-check.sh +39 -0
- package/examples/env-source-guard.sh +1 -1
- package/examples/git-stash-before-danger.sh +58 -0
- package/examples/github-actions-guard.sh +49 -0
- package/examples/gitignore-auto-add.sh +30 -0
- package/examples/go-vet-after-edit.sh +33 -0
- package/examples/hook-tamper-guard.sh +67 -0
- package/examples/kubernetes-guard.sh +2 -1
- package/examples/large-file-write-guard.sh +40 -0
- package/examples/main-branch-warn.sh +40 -0
- package/examples/max-edit-size-guard.sh +9 -15
- package/examples/mcp-server-guard.sh +70 -0
- package/examples/multiline-command-approver.sh +89 -0
- package/examples/no-base64-exfil.sh +27 -0
- package/examples/no-debug-commit.sh +60 -0
- package/examples/no-exposed-port-in-dockerfile.sh +32 -0
- package/examples/no-fixme-ship.sh +41 -0
- package/examples/no-hardcoded-ip.sh +26 -0
- package/examples/no-http-in-code.sh +19 -0
- package/examples/no-push-without-tests.sh +33 -0
- package/examples/no-self-signed-cert.sh +19 -0
- package/examples/no-star-import-python.sh +28 -0
- package/examples/no-wget-piped-bash.sh +22 -0
- package/examples/node-version-check.sh +40 -0
- package/examples/npm-publish-guard.sh +5 -2
- package/examples/output-token-env-check.sh +44 -0
- package/examples/package-lock-frozen.sh +25 -0
- package/examples/pip-venv-required.sh +40 -0
- package/examples/port-conflict-check.sh +62 -0
- package/examples/post-compact-safety.sh +61 -0
- package/examples/prefer-builtin-tools.sh +33 -0
- package/examples/python-import-check.sh +52 -0
- package/examples/python-ruff-on-edit.sh +51 -0
- package/examples/quoted-flag-approver.sh +51 -0
- package/examples/react-key-warn.sh +32 -0
- package/examples/read-budget-guard.sh +63 -0
- package/examples/rm-safety-net.sh +9 -0
- package/examples/rust-clippy-after-edit.sh +37 -0
- package/examples/session-drift-guard.sh +73 -0
- package/examples/session-quota-tracker.sh +44 -0
- package/examples/session-start-safety-check.sh +60 -0
- package/examples/session-summary-stop.sh +49 -0
- package/examples/session-time-limit.sh +34 -0
- package/examples/strip-coauthored-by.sh +46 -0
- package/examples/temp-file-cleanup.sh +41 -0
- package/examples/test-before-push.sh +8 -1
- package/examples/test-coverage-reminder.sh +49 -0
- package/examples/test-exit-code-verify.sh +60 -0
- package/examples/tool-file-logger.sh +46 -0
- package/examples/typescript-lint-on-edit.sh +61 -0
- package/examples/typescript-strict-check.sh +35 -0
- package/examples/uncommitted-changes-stop.sh +16 -0
- package/examples/uncommitted-discard-guard.sh +72 -0
- package/examples/variable-expansion-guard.sh +51 -0
- package/examples/worktree-unmerged-guard.sh +13 -3
- package/examples/yaml-syntax-check.sh +50 -0
- package/index.mjs +9 -0
- package/package.json +2 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# variable-expansion-guard.sh — Block destructive commands with shell variable expansion
|
|
3
|
+
#
|
|
4
|
+
# Solves: Claude running rm -rf "${LOCALAPPDATA}/" where Bash expands the
|
|
5
|
+
# variable to a real system path, deleting 50+ app folders.
|
|
6
|
+
# See: https://github.com/anthropics/claude-code/issues/39460
|
|
7
|
+
#
|
|
8
|
+
# TRIGGER: PreToolUse
|
|
9
|
+
# MATCHER: Bash
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# {
|
|
13
|
+
# "hooks": {
|
|
14
|
+
# "PreToolUse": [{
|
|
15
|
+
# "matcher": "Bash",
|
|
16
|
+
# "hooks": [{
|
|
17
|
+
# "type": "command",
|
|
18
|
+
# "if": "Bash(rm *)",
|
|
19
|
+
# "command": "~/.claude/hooks/variable-expansion-guard.sh"
|
|
20
|
+
# }]
|
|
21
|
+
# }]
|
|
22
|
+
# }
|
|
23
|
+
# }
|
|
24
|
+
#
|
|
25
|
+
# The "if" field (v2.1.85+) limits this to rm commands only.
|
|
26
|
+
|
|
27
|
+
INPUT=$(cat)
|
|
28
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
29
|
+
[ -z "$COMMAND" ] && exit 0
|
|
30
|
+
|
|
31
|
+
# Only check destructive commands
|
|
32
|
+
echo "$COMMAND" | grep -qE '^\s*(sudo\s+)?(rm|mv|cp|chmod|chown)\b' || exit 0
|
|
33
|
+
|
|
34
|
+
# Detect shell variable patterns in arguments
|
|
35
|
+
# $VAR, ${VAR}, $(command), `command`
|
|
36
|
+
if echo "$COMMAND" | grep -qE '\$\{[A-Z_]+\}|\$[A-Z_]{2,}'; then
|
|
37
|
+
VAR=$(echo "$COMMAND" | grep -oE '\$\{[A-Z_]+\}|\$[A-Z_]{2,}' | head -1)
|
|
38
|
+
echo "BLOCKED: Destructive command uses shell variable $VAR" >&2
|
|
39
|
+
echo "Variables may expand to system paths (e.g., \$LOCALAPPDATA → C:/Users/.../AppData/Local)" >&2
|
|
40
|
+
echo "Use explicit paths instead of variables in destructive commands." >&2
|
|
41
|
+
exit 2
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Detect command substitution in rm arguments
|
|
45
|
+
if echo "$COMMAND" | grep -qE '^\s*(sudo\s+)?rm\b.*(\$\(|`)'; then
|
|
46
|
+
echo "BLOCKED: rm with command substitution detected" >&2
|
|
47
|
+
echo "The substituted path could expand to a system directory." >&2
|
|
48
|
+
exit 2
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
exit 0
|
|
@@ -19,7 +19,12 @@
|
|
|
19
19
|
# }
|
|
20
20
|
|
|
21
21
|
INPUT=$(cat)
|
|
22
|
-
|
|
22
|
+
# jq with python3 fallback (macOS may not have jq)
|
|
23
|
+
if command -v jq &>/dev/null; then
|
|
24
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
25
|
+
else
|
|
26
|
+
COMMAND=$(echo "$INPUT" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('tool_input',{}).get('command',''))" 2>/dev/null)
|
|
27
|
+
fi
|
|
23
28
|
|
|
24
29
|
[ -z "$COMMAND" ] && exit 0
|
|
25
30
|
|
|
@@ -48,9 +53,14 @@ if [ -z "$BRANCH" ] || [ "$BRANCH" = "HEAD" ]; then
|
|
|
48
53
|
exit 0
|
|
49
54
|
fi
|
|
50
55
|
|
|
51
|
-
# Find the default branch
|
|
56
|
+
# Find the default branch (portable: checks symbolic ref, then tries main/master)
|
|
52
57
|
DEFAULT_BRANCH=$(git -C "$WORKTREE_PATH" symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')
|
|
53
|
-
[ -z "$DEFAULT_BRANCH" ]
|
|
58
|
+
if [ -z "$DEFAULT_BRANCH" ]; then
|
|
59
|
+
for candidate in main master; do
|
|
60
|
+
git -C "$WORKTREE_PATH" rev-parse --verify "$candidate" &>/dev/null && DEFAULT_BRANCH="$candidate" && break
|
|
61
|
+
done
|
|
62
|
+
fi
|
|
63
|
+
[ -z "$DEFAULT_BRANCH" ] && exit 0
|
|
54
64
|
|
|
55
65
|
# Count unmerged commits
|
|
56
66
|
UNMERGED=$(git -C "$WORKTREE_PATH" log --oneline "$DEFAULT_BRANCH..$BRANCH" 2>/dev/null | wc -l)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# yaml-syntax-check.sh — Validate YAML after editing
|
|
3
|
+
#
|
|
4
|
+
# Prevents: Broken YAML configs (docker-compose, CI pipelines, k8s manifests).
|
|
5
|
+
# YAML indentation errors are invisible until deployment fails.
|
|
6
|
+
#
|
|
7
|
+
# TRIGGER: PostToolUse
|
|
8
|
+
# MATCHER: "Write|Edit"
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# {
|
|
12
|
+
# "hooks": {
|
|
13
|
+
# "PostToolUse": [{
|
|
14
|
+
# "matcher": "Write|Edit",
|
|
15
|
+
# "hooks": [{ "type": "command", "command": "~/.claude/hooks/yaml-syntax-check.sh" }]
|
|
16
|
+
# }]
|
|
17
|
+
# }
|
|
18
|
+
# }
|
|
19
|
+
|
|
20
|
+
INPUT=$(cat)
|
|
21
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
22
|
+
[ -z "$FILE" ] && exit 0
|
|
23
|
+
|
|
24
|
+
# Only check YAML files
|
|
25
|
+
case "$FILE" in
|
|
26
|
+
*.yml|*.yaml) ;;
|
|
27
|
+
*) exit 0 ;;
|
|
28
|
+
esac
|
|
29
|
+
|
|
30
|
+
[ ! -f "$FILE" ] && exit 0
|
|
31
|
+
|
|
32
|
+
# Try python yaml parser
|
|
33
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
34
|
+
ERROR=$(python3 -c "
|
|
35
|
+
import yaml, sys
|
|
36
|
+
try:
|
|
37
|
+
with open('$FILE') as f:
|
|
38
|
+
yaml.safe_load(f)
|
|
39
|
+
except yaml.YAMLError as e:
|
|
40
|
+
print(str(e)[:200])
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
" 2>&1)
|
|
43
|
+
if [ $? -ne 0 ]; then
|
|
44
|
+
echo "YAML SYNTAX ERROR in $FILE:" >&2
|
|
45
|
+
echo " $ERROR" >&2
|
|
46
|
+
exit 2
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
exit 0
|
package/index.mjs
CHANGED
|
@@ -405,6 +405,12 @@ function examples() {
|
|
|
405
405
|
'pip-venv-guard.sh': 'Warn on pip install outside venv',
|
|
406
406
|
'no-git-amend-push.sh': 'Warn on amending pushed commits',
|
|
407
407
|
'typosquat-guard.sh': 'Detect npm/pip typosquatting attacks',
|
|
408
|
+
'variable-expansion-guard.sh': 'Block rm/mv with unexpanded shell variables (prevents system path deletion)',
|
|
409
|
+
'bash-trace-guard.sh': 'Block bash -x debug tracing that exposes secrets',
|
|
410
|
+
'strip-coauthored-by.sh': 'Warn on Co-Authored-By trailers in commit messages',
|
|
411
|
+
'session-drift-guard.sh': 'Progressive safety as session ages (warn at 200, block at 500 calls)',
|
|
412
|
+
'post-compact-safety.sh': 'Block irreversible commands after context compaction',
|
|
413
|
+
'read-budget-guard.sh': 'Limit excessive file reading to prevent token waste',
|
|
408
414
|
'hook-permission-fixer.sh': 'Auto-fix missing execute permissions on hooks (SessionStart)',
|
|
409
415
|
'response-budget-guard.sh': 'Track and limit tool calls per response (anti-loop)',
|
|
410
416
|
},
|
|
@@ -456,6 +462,8 @@ function examples() {
|
|
|
456
462
|
'package-script-guard.sh': 'Warn when package.json scripts change',
|
|
457
463
|
'lockfile-guard.sh': 'Warn when lockfiles modified in commits',
|
|
458
464
|
'git-lfs-guard.sh': 'Suggest Git LFS for large files',
|
|
465
|
+
'python-ruff-on-edit.sh': 'PostToolUse: lint Python files after edit (ruff/flake8/pylint)',
|
|
466
|
+
'typescript-lint-on-edit.sh': 'PostToolUse: type check TypeScript files after edit (tsc --noEmit)',
|
|
459
467
|
},
|
|
460
468
|
'Recovery': {
|
|
461
469
|
'auto-checkpoint.sh': 'Auto-commit after edits for rollback protection',
|
|
@@ -465,6 +473,7 @@ function examples() {
|
|
|
465
473
|
'UX': {
|
|
466
474
|
'prompt-length-guard.sh': 'UserPromptSubmit: warn on long prompts (>5000 chars)',
|
|
467
475
|
'prompt-injection-detector.sh': 'UserPromptSubmit: detect prompt injection patterns',
|
|
476
|
+
'auto-answer-question.sh': 'PreToolUse: auto-answer AskUserQuestion for headless mode (v2.1.85)',
|
|
468
477
|
'notify-waiting.sh': 'Desktop notification when Claude waits for input',
|
|
469
478
|
'tmp-cleanup.sh': 'Clean up /tmp/claude-*-cwd files on session end',
|
|
470
479
|
'hook-debug-wrapper.sh': 'Wrap any hook to log input/output/exit/timing',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "29.6.
|
|
4
|
-
"description": "One command to make Claude Code safe.
|
|
3
|
+
"version": "29.6.2",
|
|
4
|
+
"description": "One command to make Claude Code safe. 412 example hooks + 8 built-in. 52 CLI commands. 5601 tests. Works with Auto Mode.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cc-safe-setup": "index.mjs"
|