cc-safe-setup 6.0.0 → 6.2.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.
- package/README.md +9 -0
- package/examples/protect-claudemd.sh +45 -0
- package/index.mjs +86 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -226,6 +226,15 @@ Or browse all available examples in [`examples/`](examples/):
|
|
|
226
226
|
- **dependency-audit.sh** — Warn when installing packages not in manifest (npm/pip/cargo supply chain awareness)
|
|
227
227
|
- **env-source-guard.sh** — Block sourcing .env files into shell environment ([#401](https://github.com/anthropics/claude-code/issues/401))
|
|
228
228
|
- **symlink-guard.sh** — Detect symlink/junction traversal in rm targets ([#36339](https://github.com/anthropics/claude-code/issues/36339) [#764](https://github.com/anthropics/claude-code/issues/764))
|
|
229
|
+
- **no-sudo-guard.sh** — Block all sudo commands
|
|
230
|
+
- **no-install-global.sh** — Block npm -g and system-wide pip
|
|
231
|
+
- **no-curl-upload.sh** — Warn on curl POST/upload (data exfiltration)
|
|
232
|
+
- **no-port-bind.sh** — Warn on network port binding
|
|
233
|
+
- **git-tag-guard.sh** — Block pushing all tags at once
|
|
234
|
+
- **npm-publish-guard.sh** — Version check before npm publish
|
|
235
|
+
- **max-file-count-guard.sh** — Warn when 20+ new files created per session
|
|
236
|
+
- **protect-claudemd.sh** — Block edits to CLAUDE.md and settings files
|
|
237
|
+
- **reinject-claudemd.sh** — Re-inject CLAUDE.md rules after compaction ([#6354](https://github.com/anthropics/claude-code/issues/6354))
|
|
229
238
|
- **binary-file-guard.sh** — Warn when Write targets binary file types (images, archives)
|
|
230
239
|
- **stale-branch-guard.sh** — Warn when working branch is far behind default
|
|
231
240
|
- **cost-tracker.sh** — Estimate session token cost and warn at thresholds ($1, $5)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ================================================================
|
|
3
|
+
# protect-claudemd.sh — Block edits to CLAUDE.md and settings files
|
|
4
|
+
# ================================================================
|
|
5
|
+
# PURPOSE:
|
|
6
|
+
# Claude Code sometimes modifies CLAUDE.md, settings.json, or
|
|
7
|
+
# other configuration files without permission. This hook blocks
|
|
8
|
+
# Edit/Write to these critical files.
|
|
9
|
+
#
|
|
10
|
+
# TRIGGER: PreToolUse
|
|
11
|
+
# MATCHER: "Edit|Write"
|
|
12
|
+
# ================================================================
|
|
13
|
+
|
|
14
|
+
INPUT=$(cat)
|
|
15
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
|
|
16
|
+
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
17
|
+
|
|
18
|
+
if [[ "$TOOL" != "Edit" && "$TOOL" != "Write" ]]; then
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if [[ -z "$FILE" ]]; then
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
BASENAME=$(basename "$FILE")
|
|
27
|
+
|
|
28
|
+
# Protected files
|
|
29
|
+
case "$BASENAME" in
|
|
30
|
+
CLAUDE.md|.claude.json|settings.json|settings.local.json)
|
|
31
|
+
echo "BLOCKED: Cannot modify configuration file: $BASENAME" >&2
|
|
32
|
+
echo "File: $FILE" >&2
|
|
33
|
+
echo "" >&2
|
|
34
|
+
echo "Configuration files should be edited manually, not by Claude." >&2
|
|
35
|
+
exit 2
|
|
36
|
+
;;
|
|
37
|
+
esac
|
|
38
|
+
|
|
39
|
+
# Protected directories
|
|
40
|
+
if echo "$FILE" | grep -qE '\.claude/(hooks|settings|plugins)/'; then
|
|
41
|
+
echo "BLOCKED: Cannot modify .claude system directory: $FILE" >&2
|
|
42
|
+
exit 2
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
exit 0
|
package/index.mjs
CHANGED
|
@@ -86,6 +86,7 @@ const SHARE = process.argv.includes('--share');
|
|
|
86
86
|
const BENCHMARK = process.argv.includes('--benchmark');
|
|
87
87
|
const DASHBOARD = process.argv.includes('--dashboard');
|
|
88
88
|
const ISSUES = process.argv.includes('--issues');
|
|
89
|
+
const MIGRATE = process.argv.includes('--migrate');
|
|
89
90
|
const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
|
|
90
91
|
const COMPARE = COMPARE_IDX !== -1 ? { a: process.argv[COMPARE_IDX + 1], b: process.argv[COMPARE_IDX + 2] } : null;
|
|
91
92
|
const CREATE_IDX = process.argv.findIndex(a => a === '--create');
|
|
@@ -109,6 +110,7 @@ if (HELP) {
|
|
|
109
110
|
npx cc-safe-setup --audit --json Machine-readable output for CI/CD
|
|
110
111
|
npx cc-safe-setup --scan Detect tech stack, recommend hooks
|
|
111
112
|
npx cc-safe-setup --learn Learn from your block history
|
|
113
|
+
npx cc-safe-setup --migrate Detect hooks from other projects, suggest replacements
|
|
112
114
|
npx cc-safe-setup --compare <a> <b> Compare two hooks side-by-side
|
|
113
115
|
npx cc-safe-setup --issues Show GitHub Issues each hook addresses
|
|
114
116
|
npx cc-safe-setup --dashboard Real-time status dashboard
|
|
@@ -370,6 +372,15 @@ function examples() {
|
|
|
370
372
|
'symlink-guard.sh': 'Detect symlink/junction traversal in rm targets',
|
|
371
373
|
'cost-tracker.sh': 'Estimate session token cost ($1 warn, $5 alert)',
|
|
372
374
|
'read-before-edit.sh': 'Warn when editing files not recently read',
|
|
375
|
+
'no-sudo-guard.sh': 'Block all sudo commands',
|
|
376
|
+
'no-install-global.sh': 'Block npm -g and system-wide pip',
|
|
377
|
+
'no-curl-upload.sh': 'Warn on curl POST/upload',
|
|
378
|
+
'no-port-bind.sh': 'Warn on network port binding',
|
|
379
|
+
'git-tag-guard.sh': 'Block pushing all tags at once',
|
|
380
|
+
'npm-publish-guard.sh': 'Version check before npm publish',
|
|
381
|
+
'max-file-count-guard.sh': 'Warn when 20+ files created per session',
|
|
382
|
+
'protect-claudemd.sh': 'Block edits to CLAUDE.md and settings files',
|
|
383
|
+
'reinject-claudemd.sh': 'Re-inject CLAUDE.md rules after compaction',
|
|
373
384
|
},
|
|
374
385
|
};
|
|
375
386
|
|
|
@@ -815,6 +826,75 @@ async function fullSetup() {
|
|
|
815
826
|
console.log();
|
|
816
827
|
}
|
|
817
828
|
|
|
829
|
+
async function migrate() {
|
|
830
|
+
const { readdirSync } = await import('fs');
|
|
831
|
+
|
|
832
|
+
console.log();
|
|
833
|
+
console.log(c.bold + ' cc-safe-setup --migrate' + c.reset);
|
|
834
|
+
console.log(c.dim + ' Detecting hooks from other projects...' + c.reset);
|
|
835
|
+
console.log();
|
|
836
|
+
|
|
837
|
+
if (!existsSync(HOOKS_DIR)) {
|
|
838
|
+
console.log(c.dim + ' No hooks installed.' + c.reset);
|
|
839
|
+
process.exit(0);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const files = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh') || f.endsWith('.js') || f.endsWith('.py'));
|
|
843
|
+
|
|
844
|
+
// Detection patterns for other projects
|
|
845
|
+
const detections = [
|
|
846
|
+
{ pattern: /safety-net|cc-safety-net|SAFETY_LEVEL/i, project: 'claude-code-safety-net', replacement: 'npx cc-safe-setup (destructive-guard, branch-guard)' },
|
|
847
|
+
{ pattern: /karanb192|block-dangerous-commands\.js/i, project: 'karanb192/claude-code-hooks', replacement: 'npx cc-safe-setup (destructive-guard)' },
|
|
848
|
+
{ pattern: /hooks-mastery|disler|pre_tool_use\.py/i, project: 'disler/claude-code-hooks-mastery', replacement: 'npx cc-safe-setup (multiple hooks)' },
|
|
849
|
+
{ pattern: /cchooks|from cchooks/i, project: 'GowayLee/cchooks', replacement: 'npx cc-safe-setup (bash equivalents)' },
|
|
850
|
+
{ pattern: /lasso.*security|prompt.*injection.*pattern/i, project: 'lasso-security/claude-hooks', replacement: 'No direct equivalent (unique functionality)' },
|
|
851
|
+
];
|
|
852
|
+
|
|
853
|
+
let found = 0;
|
|
854
|
+
const suggestions = [];
|
|
855
|
+
|
|
856
|
+
for (const file of files) {
|
|
857
|
+
const content = readFileSync(join(HOOKS_DIR, file), 'utf-8');
|
|
858
|
+
|
|
859
|
+
for (const det of detections) {
|
|
860
|
+
if (det.pattern.test(content) || det.pattern.test(file)) {
|
|
861
|
+
console.log(' ' + c.yellow + '!' + c.reset + ' ' + file + c.dim + ' ← from ' + det.project + c.reset);
|
|
862
|
+
console.log(' ' + c.dim + 'Replacement: ' + det.replacement + c.reset);
|
|
863
|
+
found++;
|
|
864
|
+
break;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Detect hand-written hooks that duplicate built-in functionality
|
|
869
|
+
if (content.includes('rm -rf') && !file.includes('destructive-guard')) {
|
|
870
|
+
suggestions.push({ file, suggest: 'destructive-guard', reason: 'rm -rf detection already built-in' });
|
|
871
|
+
}
|
|
872
|
+
if (content.includes('git push') && content.includes('main') && !file.includes('branch-guard')) {
|
|
873
|
+
suggestions.push({ file, suggest: 'branch-guard', reason: 'branch protection already built-in' });
|
|
874
|
+
}
|
|
875
|
+
if (content.includes('.env') && content.includes('git add') && !file.includes('secret-guard')) {
|
|
876
|
+
suggestions.push({ file, suggest: 'secret-guard', reason: 'secret leak prevention already built-in' });
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (suggestions.length > 0) {
|
|
881
|
+
console.log();
|
|
882
|
+
console.log(c.bold + ' Duplicate functionality detected:' + c.reset);
|
|
883
|
+
for (const s of suggestions) {
|
|
884
|
+
console.log(' ' + c.dim + s.file + c.reset + ' → ' + c.green + s.suggest + c.reset);
|
|
885
|
+
console.log(' ' + c.dim + s.reason + c.reset);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
console.log();
|
|
890
|
+
if (found === 0 && suggestions.length === 0) {
|
|
891
|
+
console.log(c.green + ' No migration needed. All hooks are cc-safe-setup native.' + c.reset);
|
|
892
|
+
} else {
|
|
893
|
+
console.log(c.dim + ' Run npx cc-safe-setup to install built-in replacements.' + c.reset);
|
|
894
|
+
}
|
|
895
|
+
console.log();
|
|
896
|
+
}
|
|
897
|
+
|
|
818
898
|
async function compare(hookA, hookB) {
|
|
819
899
|
const { spawnSync } = await import('child_process');
|
|
820
900
|
const { statSync } = await import('fs');
|
|
@@ -928,6 +1008,11 @@ function issues() {
|
|
|
928
1008
|
{ hook: 'binary-file-guard', issues: ['Binary file corruption from Write tool'] },
|
|
929
1009
|
{ hook: 'stale-branch-guard', issues: ['Merge conflicts from stale branches'] },
|
|
930
1010
|
{ hook: 'reinject-claudemd', issues: ['#6354 CLAUDE.md lost after compaction (27r)'] },
|
|
1011
|
+
{ hook: 'no-sudo-guard', issues: ['Privilege escalation prevention'] },
|
|
1012
|
+
{ hook: 'no-install-global', issues: ['System package pollution'] },
|
|
1013
|
+
{ hook: 'protect-claudemd', issues: ['AI modifying its own config files'] },
|
|
1014
|
+
{ hook: 'git-tag-guard', issues: ['Accidental tag push'] },
|
|
1015
|
+
{ hook: 'npm-publish-guard', issues: ['Accidental publish without version check'] },
|
|
931
1016
|
];
|
|
932
1017
|
|
|
933
1018
|
console.log();
|
|
@@ -2423,6 +2508,7 @@ async function main() {
|
|
|
2423
2508
|
if (FULL) return fullSetup();
|
|
2424
2509
|
if (DOCTOR) return doctor();
|
|
2425
2510
|
if (WATCH) return watch();
|
|
2511
|
+
if (MIGRATE) return migrate();
|
|
2426
2512
|
if (COMPARE) return compare(COMPARE.a, COMPARE.b);
|
|
2427
2513
|
if (ISSUES) return issues();
|
|
2428
2514
|
if (DASHBOARD) return dashboard();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-safe-setup",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"description": "One command to make Claude Code safe for autonomous operation. 8 built-in + 39 examples. 23 commands including dashboard, issues, create, audit, lint, diff. 260 tests. 2,500+ daily npm downloads.",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"bin": {
|