cc-safe-setup 6.1.0 → 6.3.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/index.mjs +145 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -86,6 +86,8 @@ 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');
|
|
90
|
+
const GENERATE_CI = process.argv.includes('--generate-ci');
|
|
89
91
|
const COMPARE_IDX = process.argv.findIndex(a => a === '--compare');
|
|
90
92
|
const COMPARE = COMPARE_IDX !== -1 ? { a: process.argv[COMPARE_IDX + 1], b: process.argv[COMPARE_IDX + 2] } : null;
|
|
91
93
|
const CREATE_IDX = process.argv.findIndex(a => a === '--create');
|
|
@@ -109,6 +111,8 @@ if (HELP) {
|
|
|
109
111
|
npx cc-safe-setup --audit --json Machine-readable output for CI/CD
|
|
110
112
|
npx cc-safe-setup --scan Detect tech stack, recommend hooks
|
|
111
113
|
npx cc-safe-setup --learn Learn from your block history
|
|
114
|
+
npx cc-safe-setup --generate-ci Generate GitHub Actions workflow for safety checks
|
|
115
|
+
npx cc-safe-setup --migrate Detect hooks from other projects, suggest replacements
|
|
112
116
|
npx cc-safe-setup --compare <a> <b> Compare two hooks side-by-side
|
|
113
117
|
npx cc-safe-setup --issues Show GitHub Issues each hook addresses
|
|
114
118
|
npx cc-safe-setup --dashboard Real-time status dashboard
|
|
@@ -824,6 +828,140 @@ async function fullSetup() {
|
|
|
824
828
|
console.log();
|
|
825
829
|
}
|
|
826
830
|
|
|
831
|
+
function generateCI() {
|
|
832
|
+
const workflowDir = join(process.cwd(), '.github', 'workflows');
|
|
833
|
+
const workflowPath = join(workflowDir, 'claude-code-safety.yml');
|
|
834
|
+
|
|
835
|
+
const workflow = `# Claude Code Safety Audit
|
|
836
|
+
# Generated by: npx cc-safe-setup --generate-ci
|
|
837
|
+
# Checks safety score on every PR and fails if below threshold
|
|
838
|
+
|
|
839
|
+
name: Claude Code Safety
|
|
840
|
+
on:
|
|
841
|
+
pull_request:
|
|
842
|
+
push:
|
|
843
|
+
branches: [main, master]
|
|
844
|
+
|
|
845
|
+
jobs:
|
|
846
|
+
safety-audit:
|
|
847
|
+
runs-on: ubuntu-latest
|
|
848
|
+
steps:
|
|
849
|
+
- uses: actions/checkout@v4
|
|
850
|
+
|
|
851
|
+
- name: Run safety audit
|
|
852
|
+
uses: yurukusa/cc-safe-setup@main
|
|
853
|
+
with:
|
|
854
|
+
threshold: 70
|
|
855
|
+
|
|
856
|
+
- name: Comment PR with score
|
|
857
|
+
if: github.event_name == 'pull_request'
|
|
858
|
+
uses: actions/github-script@v7
|
|
859
|
+
with:
|
|
860
|
+
script: |
|
|
861
|
+
const score = '\${{ steps.audit.outputs.score }}' || '?';
|
|
862
|
+
const grade = '\${{ steps.audit.outputs.grade }}' || '?';
|
|
863
|
+
github.rest.issues.createComment({
|
|
864
|
+
issue_number: context.issue.number,
|
|
865
|
+
owner: context.repo.owner,
|
|
866
|
+
repo: context.repo.repo,
|
|
867
|
+
body: \`## Claude Code Safety: \${score}/100 (Grade \${grade})\\n\\nRun \\\`npx cc-safe-setup --audit\\\` locally for details.\`
|
|
868
|
+
});
|
|
869
|
+
`;
|
|
870
|
+
|
|
871
|
+
console.log();
|
|
872
|
+
console.log(c.bold + ' cc-safe-setup --generate-ci' + c.reset);
|
|
873
|
+
console.log();
|
|
874
|
+
|
|
875
|
+
if (existsSync(workflowPath)) {
|
|
876
|
+
console.log(c.yellow + ' Workflow already exists: ' + workflowPath + c.reset);
|
|
877
|
+
console.log(c.dim + ' Delete it first if you want to regenerate.' + c.reset);
|
|
878
|
+
process.exit(0);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
mkdirSync(workflowDir, { recursive: true });
|
|
882
|
+
writeFileSync(workflowPath, workflow);
|
|
883
|
+
|
|
884
|
+
console.log(c.green + ' ✓ Created: ' + workflowPath + c.reset);
|
|
885
|
+
console.log();
|
|
886
|
+
console.log(c.dim + ' This workflow will:' + c.reset);
|
|
887
|
+
console.log(c.dim + ' 1. Run safety audit on every PR and push to main' + c.reset);
|
|
888
|
+
console.log(c.dim + ' 2. Fail CI if safety score < 70' + c.reset);
|
|
889
|
+
console.log(c.dim + ' 3. Comment PR with safety score' + c.reset);
|
|
890
|
+
console.log();
|
|
891
|
+
console.log(c.dim + ' Commit and push to activate:' + c.reset);
|
|
892
|
+
console.log(c.bold + ' git add .github/workflows/claude-code-safety.yml && git commit -m "ci: add safety audit" && git push' + c.reset);
|
|
893
|
+
console.log();
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
async function migrate() {
|
|
897
|
+
const { readdirSync } = await import('fs');
|
|
898
|
+
|
|
899
|
+
console.log();
|
|
900
|
+
console.log(c.bold + ' cc-safe-setup --migrate' + c.reset);
|
|
901
|
+
console.log(c.dim + ' Detecting hooks from other projects...' + c.reset);
|
|
902
|
+
console.log();
|
|
903
|
+
|
|
904
|
+
if (!existsSync(HOOKS_DIR)) {
|
|
905
|
+
console.log(c.dim + ' No hooks installed.' + c.reset);
|
|
906
|
+
process.exit(0);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const files = readdirSync(HOOKS_DIR).filter(f => f.endsWith('.sh') || f.endsWith('.js') || f.endsWith('.py'));
|
|
910
|
+
|
|
911
|
+
// Detection patterns for other projects
|
|
912
|
+
const detections = [
|
|
913
|
+
{ pattern: /safety-net|cc-safety-net|SAFETY_LEVEL/i, project: 'claude-code-safety-net', replacement: 'npx cc-safe-setup (destructive-guard, branch-guard)' },
|
|
914
|
+
{ pattern: /karanb192|block-dangerous-commands\.js/i, project: 'karanb192/claude-code-hooks', replacement: 'npx cc-safe-setup (destructive-guard)' },
|
|
915
|
+
{ pattern: /hooks-mastery|disler|pre_tool_use\.py/i, project: 'disler/claude-code-hooks-mastery', replacement: 'npx cc-safe-setup (multiple hooks)' },
|
|
916
|
+
{ pattern: /cchooks|from cchooks/i, project: 'GowayLee/cchooks', replacement: 'npx cc-safe-setup (bash equivalents)' },
|
|
917
|
+
{ pattern: /lasso.*security|prompt.*injection.*pattern/i, project: 'lasso-security/claude-hooks', replacement: 'No direct equivalent (unique functionality)' },
|
|
918
|
+
];
|
|
919
|
+
|
|
920
|
+
let found = 0;
|
|
921
|
+
const suggestions = [];
|
|
922
|
+
|
|
923
|
+
for (const file of files) {
|
|
924
|
+
const content = readFileSync(join(HOOKS_DIR, file), 'utf-8');
|
|
925
|
+
|
|
926
|
+
for (const det of detections) {
|
|
927
|
+
if (det.pattern.test(content) || det.pattern.test(file)) {
|
|
928
|
+
console.log(' ' + c.yellow + '!' + c.reset + ' ' + file + c.dim + ' ← from ' + det.project + c.reset);
|
|
929
|
+
console.log(' ' + c.dim + 'Replacement: ' + det.replacement + c.reset);
|
|
930
|
+
found++;
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// Detect hand-written hooks that duplicate built-in functionality
|
|
936
|
+
if (content.includes('rm -rf') && !file.includes('destructive-guard')) {
|
|
937
|
+
suggestions.push({ file, suggest: 'destructive-guard', reason: 'rm -rf detection already built-in' });
|
|
938
|
+
}
|
|
939
|
+
if (content.includes('git push') && content.includes('main') && !file.includes('branch-guard')) {
|
|
940
|
+
suggestions.push({ file, suggest: 'branch-guard', reason: 'branch protection already built-in' });
|
|
941
|
+
}
|
|
942
|
+
if (content.includes('.env') && content.includes('git add') && !file.includes('secret-guard')) {
|
|
943
|
+
suggestions.push({ file, suggest: 'secret-guard', reason: 'secret leak prevention already built-in' });
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (suggestions.length > 0) {
|
|
948
|
+
console.log();
|
|
949
|
+
console.log(c.bold + ' Duplicate functionality detected:' + c.reset);
|
|
950
|
+
for (const s of suggestions) {
|
|
951
|
+
console.log(' ' + c.dim + s.file + c.reset + ' → ' + c.green + s.suggest + c.reset);
|
|
952
|
+
console.log(' ' + c.dim + s.reason + c.reset);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
console.log();
|
|
957
|
+
if (found === 0 && suggestions.length === 0) {
|
|
958
|
+
console.log(c.green + ' No migration needed. All hooks are cc-safe-setup native.' + c.reset);
|
|
959
|
+
} else {
|
|
960
|
+
console.log(c.dim + ' Run npx cc-safe-setup to install built-in replacements.' + c.reset);
|
|
961
|
+
}
|
|
962
|
+
console.log();
|
|
963
|
+
}
|
|
964
|
+
|
|
827
965
|
async function compare(hookA, hookB) {
|
|
828
966
|
const { spawnSync } = await import('child_process');
|
|
829
967
|
const { statSync } = await import('fs');
|
|
@@ -937,6 +1075,11 @@ function issues() {
|
|
|
937
1075
|
{ hook: 'binary-file-guard', issues: ['Binary file corruption from Write tool'] },
|
|
938
1076
|
{ hook: 'stale-branch-guard', issues: ['Merge conflicts from stale branches'] },
|
|
939
1077
|
{ hook: 'reinject-claudemd', issues: ['#6354 CLAUDE.md lost after compaction (27r)'] },
|
|
1078
|
+
{ hook: 'no-sudo-guard', issues: ['Privilege escalation prevention'] },
|
|
1079
|
+
{ hook: 'no-install-global', issues: ['System package pollution'] },
|
|
1080
|
+
{ hook: 'protect-claudemd', issues: ['AI modifying its own config files'] },
|
|
1081
|
+
{ hook: 'git-tag-guard', issues: ['Accidental tag push'] },
|
|
1082
|
+
{ hook: 'npm-publish-guard', issues: ['Accidental publish without version check'] },
|
|
940
1083
|
];
|
|
941
1084
|
|
|
942
1085
|
console.log();
|
|
@@ -2432,6 +2575,8 @@ async function main() {
|
|
|
2432
2575
|
if (FULL) return fullSetup();
|
|
2433
2576
|
if (DOCTOR) return doctor();
|
|
2434
2577
|
if (WATCH) return watch();
|
|
2578
|
+
if (GENERATE_CI) return generateCI();
|
|
2579
|
+
if (MIGRATE) return migrate();
|
|
2435
2580
|
if (COMPARE) return compare(COMPARE.a, COMPARE.b);
|
|
2436
2581
|
if (ISSUES) return issues();
|
|
2437
2582
|
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.3.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": {
|