cc-safe-setup 5.0.0 → 5.1.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/docs/app.html ADDED
@@ -0,0 +1,271 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>cc-safe-setup — Claude Code Safety Suite</title>
7
+ <meta name="description" content="Audit, build, browse, and learn Claude Code hooks. All in one page, 100% client-side.">
8
+ <style>
9
+ *{box-sizing:border-box;margin:0;padding:0}
10
+ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0d1117;color:#c9d1d9;min-height:100vh}
11
+ .nav{display:flex;background:#161b22;border-bottom:1px solid #30363d;overflow-x:auto}
12
+ .nav a{padding:.75rem 1.2rem;color:#8b949e;text-decoration:none;font-size:.85rem;white-space:nowrap;border-bottom:2px solid transparent;cursor:pointer}
13
+ .nav a.active{color:#f0f6fc;border-bottom-color:#f78166}
14
+ .nav a:hover{color:#c9d1d9}
15
+ .page{display:none;max-width:800px;margin:0 auto;padding:1.5rem}
16
+ .page.active{display:block}
17
+ h1{font-size:1.3rem;color:#f0f6fc;margin-bottom:.5rem}
18
+ h2{font-size:1.1rem;color:#f0f6fc;margin:1.5rem 0 .5rem}
19
+ h3{font-size:.95rem;color:#c9d1d9;margin:1rem 0 .3rem}
20
+ p,.sub{color:#8b949e;font-size:.85rem;margin-bottom:.75rem}
21
+ a{color:#58a6ff;text-decoration:none}
22
+ textarea,input,select{width:100%;padding:.5rem;background:#161b22;border:1px solid #30363d;border-radius:4px;color:#c9d1d9;font-family:monospace;font-size:.8rem;margin:.25rem 0}
23
+ textarea{height:150px;resize:vertical}
24
+ button,.btn{background:#238636;color:#fff;border:none;padding:.5rem 1rem;border-radius:4px;cursor:pointer;font-size:.85rem;margin:.25rem .25rem .25rem 0}
25
+ button:hover,.btn:hover{background:#2ea043}
26
+ .btn-sm{padding:.25rem .5rem;font-size:.75rem}
27
+ .btn-secondary{background:#30363d}
28
+ pre{background:#161b22;border:1px solid #30363d;border-radius:4px;padding:.6rem;font-size:.75rem;overflow-x:auto;position:relative;margin:.3rem 0}
29
+ .copy{position:absolute;top:.3rem;right:.3rem;background:#30363d;color:#c9d1d9;border:none;padding:.15rem .4rem;border-radius:3px;font-size:.65rem;cursor:pointer}
30
+ .score{font-size:1.8rem;font-weight:bold;margin:.5rem 0}
31
+ .score.good{color:#3fb950} .score.mid{color:#d29922} .score.bad{color:#f85149}
32
+ .risk{background:#161b22;border:1px solid #30363d;border-radius:4px;padding:.75rem;margin:.3rem 0;font-size:.8rem}
33
+ .good-item{color:#3fb950;margin:.2rem 0;font-size:.8rem}
34
+ table{width:100%;border-collapse:collapse;font-size:.8rem;margin:.5rem 0}
35
+ th,td{text-align:left;padding:.35rem .5rem;border-bottom:1px solid #21262d}
36
+ th{font-weight:600;color:#f0f6fc}
37
+ .recipe{background:#161b22;border:1px solid #30363d;border-radius:4px;margin:.3rem 0;overflow:hidden}
38
+ .recipe summary{padding:.5rem .75rem;cursor:pointer;font-weight:600;color:#f0f6fc;font-size:.85rem}
39
+ .recipe-body{padding:0 .75rem .5rem}
40
+ .filter{padding:.2rem .5rem;border-radius:3px;border:1px solid #30363d;background:transparent;color:#8b949e;cursor:pointer;font-size:.7rem;margin:.15rem}
41
+ .filter.active{background:#238636;border-color:#238636;color:#fff}
42
+ .badge{display:inline-block;padding:.1rem .3rem;border-radius:3px;font-size:.65rem;font-weight:bold}
43
+ .badge-bash{background:#1f6feb22;color:#58a6ff;border:1px solid #1f6feb44}
44
+ .badge-js{background:#f0e68c22;color:#f0e68c;border:1px solid #f0e68c44}
45
+ .badge-py{background:#3fb95022;color:#3fb950;border:1px solid #3fb95044}
46
+ .badge-ts{background:#388bfd22;color:#388bfd;border:1px solid #388bfd44}
47
+ .check{color:#3fb950} .cross{color:#484f58}
48
+ .footer{text-align:center;color:#484f58;font-size:.7rem;padding:2rem 1rem}
49
+ @media print{.nav{display:none} .page{display:block!important}}
50
+ </style>
51
+ </head>
52
+ <body>
53
+
54
+ <nav class="nav">
55
+ <a onclick="go('audit')" id="nav-audit" class="active">Audit</a>
56
+ <a onclick="go('builder')" id="nav-builder">Hook Builder</a>
57
+ <a onclick="go('cookbook')" id="nav-cookbook">Cookbook</a>
58
+ <a onclick="go('ecosystem')" id="nav-ecosystem">Ecosystem</a>
59
+ <a onclick="go('cheatsheet')" id="nav-cheatsheet">Cheat Sheet</a>
60
+ </nav>
61
+
62
+ <!-- PAGE: AUDIT -->
63
+ <div class="page active" id="page-audit">
64
+ <h1>Safety Audit</h1>
65
+ <p>Paste your <code>~/.claude/settings.json</code>. Nothing leaves your browser.</p>
66
+ <textarea id="settings" placeholder='{"permissions":{"allow":["Bash(git:*)"]},"hooks":{"PreToolUse":[...]}}'></textarea>
67
+ <button onclick="runAudit()">Run Audit</button>
68
+ <button class="btn-secondary" onclick="generateFresh()">Generate Fresh Setup</button>
69
+ <div id="audit-results"></div>
70
+ </div>
71
+
72
+ <!-- PAGE: HOOK BUILDER -->
73
+ <div class="page" id="page-builder">
74
+ <h1>Hook Builder</h1>
75
+ <p>Build a custom hook without writing code.</p>
76
+ <div style="display:flex;gap:.75rem;flex-wrap:wrap">
77
+ <div style="flex:1;min-width:180px">
78
+ <label style="font-size:.75rem;color:#8b949e">Action</label>
79
+ <select id="hb-action"><option value="block">Block</option><option value="warn">Warn</option><option value="approve">Auto-approve</option></select>
80
+ </div>
81
+ <div style="flex:2;min-width:250px">
82
+ <label style="font-size:.75rem;color:#8b949e">Pattern (regex)</label>
83
+ <input id="hb-pattern" placeholder="e.g. rm\s+-rf, git push --force">
84
+ </div>
85
+ </div>
86
+ <label style="font-size:.75rem;color:#8b949e">Message</label>
87
+ <input id="hb-message" placeholder="e.g. Run tests before pushing">
88
+ <button onclick="buildHook()">Generate</button>
89
+ <div id="hb-result"></div>
90
+ </div>
91
+
92
+ <!-- PAGE: COOKBOOK -->
93
+ <div class="page" id="page-cookbook">
94
+ <h1>Hooks Cookbook</h1>
95
+ <p>Copy-paste recipes from real GitHub Issues.</p>
96
+ <input id="cb-search" placeholder="Search... (database, git, deploy)" oninput="filterRecipes()">
97
+ <div style="margin:.3rem 0" id="cb-filters"></div>
98
+ <div id="cb-count" style="color:#8b949e;font-size:.8rem"></div>
99
+ <div id="cb-list"></div>
100
+ </div>
101
+
102
+ <!-- PAGE: ECOSYSTEM -->
103
+ <div class="page" id="page-ecosystem">
104
+ <h1>Ecosystem Comparison</h1>
105
+ <p>All major Claude Code hook projects compared.</p>
106
+ <table>
107
+ <tr><th>Project</th><th>Lang</th><th>Hooks</th><th>Install</th></tr>
108
+ <tr><td><a href="https://github.com/kenryu42/claude-code-safety-net">safety-net</a></td><td><span class="badge badge-ts">TS</span></td><td>5</td><td>npx</td></tr>
109
+ <tr><td><a href="https://github.com/yurukusa/cc-safe-setup">cc-safe-setup</a></td><td><span class="badge badge-bash">Bash</span></td><td>8+39</td><td>npx</td></tr>
110
+ <tr><td><a href="https://github.com/karanb192/claude-code-hooks">karanb192</a></td><td><span class="badge badge-js">JS</span></td><td>5+</td><td>copy</td></tr>
111
+ <tr><td><a href="https://github.com/disler/claude-code-hooks-mastery">mastery</a></td><td><span class="badge badge-py">Python</span></td><td>12</td><td>copy</td></tr>
112
+ <tr><td><a href="https://github.com/lasso-security/claude-hooks">lasso</a></td><td><span class="badge badge-py">Python</span></td><td>1</td><td>install.sh</td></tr>
113
+ </table>
114
+ <h2>Feature Matrix</h2>
115
+ <table>
116
+ <tr><th>Feature</th><th>safety-net</th><th>cc-safe-setup</th><th>karanb192</th><th>mastery</th></tr>
117
+ <tr><td>rm -rf blocker</td><td class="check">✓</td><td class="check">✓</td><td class="check">✓</td><td class="check">✓</td></tr>
118
+ <tr><td>Branch guard</td><td class="check">✓</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
119
+ <tr><td>Secret guard</td><td class="cross">-</td><td class="check">✓</td><td class="check">✓</td><td class="cross">-</td></tr>
120
+ <tr><td>Syntax check</td><td class="cross">-</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
121
+ <tr><td>Context monitor</td><td class="cross">-</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
122
+ <tr><td>Hook generator</td><td class="cross">-</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
123
+ <tr><td>Dashboard</td><td class="cross">-</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
124
+ <tr><td>GitHub Action</td><td class="cross">-</td><td class="check">✓</td><td class="cross">-</td><td class="cross">-</td></tr>
125
+ </table>
126
+ </div>
127
+
128
+ <!-- PAGE: CHEAT SHEET -->
129
+ <div class="page" id="page-cheatsheet">
130
+ <h1>Hooks Cheat Sheet</h1>
131
+ <p>Print this page (Ctrl+P) for a quick reference.</p>
132
+
133
+ <h3>Lifecycle</h3>
134
+ <pre>Prompt → PreToolUse → Tool → PostToolUse → Stop</pre>
135
+
136
+ <h3>Exit Codes</h3>
137
+ <table><tr><th>Code</th><th>Meaning</th></tr>
138
+ <tr><td><code>0</code></td><td>Allow</td></tr>
139
+ <tr><td><code>2</code></td><td><strong>Block</strong></td></tr></table>
140
+
141
+ <h3>Minimal Block Hook</h3>
142
+ <pre>#!/bin/bash
143
+ CMD=$(cat | jq -r '.tool_input.command // empty')
144
+ [ -z "$CMD" ] && exit 0
145
+ echo "$CMD" | grep -qE 'PATTERN' && echo "BLOCKED" >&2 && exit 2
146
+ exit 0</pre>
147
+
148
+ <h3>Auto-Approve Hook</h3>
149
+ <pre>#!/bin/bash
150
+ CMD=$(cat | jq -r '.tool_input.command // empty')
151
+ [ -z "$CMD" ] && exit 0
152
+ echo "$CMD" | grep -qE '^git\s+(status|log|diff)' && \
153
+ jq -n '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}'
154
+ exit 0</pre>
155
+
156
+ <h3>Quick Commands</h3>
157
+ <table>
158
+ <tr><td><code>npx cc-safe-setup</code></td><td>Install 8 hooks</td></tr>
159
+ <tr><td><code>--create "desc"</code></td><td>Generate hook</td></tr>
160
+ <tr><td><code>--audit</code></td><td>Score 0-100</td></tr>
161
+ <tr><td><code>--dashboard</code></td><td>Live status</td></tr>
162
+ <tr><td><code>--doctor</code></td><td>Diagnose</td></tr>
163
+ <tr><td><code>--benchmark</code></td><td>Speed test</td></tr>
164
+ </table>
165
+ </div>
166
+
167
+ <div class="footer">
168
+ cc-safe-setup · 100% client-side · <a href="https://github.com/yurukusa/cc-safe-setup">GitHub</a> · <a href="https://www.npmjs.com/package/cc-safe-setup">npm</a>
169
+ </div>
170
+
171
+ <script>
172
+ // Navigation
173
+ function go(page) {
174
+ document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
175
+ document.querySelectorAll('.nav a').forEach(a => a.classList.remove('active'));
176
+ document.getElementById('page-' + page).classList.add('active');
177
+ document.getElementById('nav-' + page).classList.add('active');
178
+ }
179
+
180
+ // AUDIT
181
+ function runAudit() {
182
+ const raw = document.getElementById('settings').value.trim();
183
+ const el = document.getElementById('audit-results');
184
+ if (!raw) { generateFresh(); return; }
185
+ let s; try { s = JSON.parse(raw); } catch { el.innerHTML='<p style="color:#f85149">Invalid JSON</p>'; return; }
186
+ const {risks,good,score} = analyze(s);
187
+ renderAudit(risks, good, score, el);
188
+ }
189
+ function generateFresh() {
190
+ const {risks,good,score} = analyze({});
191
+ renderAudit(risks, good, score, document.getElementById('audit-results'));
192
+ }
193
+ function analyze(s) {
194
+ const risks=[], good=[];
195
+ const pre=s.hooks?.PreToolUse||[], post=s.hooks?.PostToolUse||[];
196
+ const all=JSON.stringify(pre).toLowerCase();
197
+ if(!pre.length) risks.push({s:'CRITICAL',i:'No PreToolUse hooks',f:'npx cc-safe-setup'});
198
+ else { good.push('PreToolUse ('+pre.length+')');
199
+ if(!all.match(/destructive|guard|rm.*rf/)) risks.push({s:'HIGH',i:'No destructive guard',f:'npx cc-safe-setup'});
200
+ else good.push('Destructive protection');
201
+ if(!all.match(/branch|push|main/)) risks.push({s:'HIGH',i:'No branch guard',f:'npx cc-safe-setup'});
202
+ else good.push('Branch protection');
203
+ if(!all.match(/secret|env|credential/)) risks.push({s:'HIGH',i:'No secret guard',f:'npx cc-safe-setup'});
204
+ else good.push('Secret protection');
205
+ }
206
+ if(!post.length) risks.push({s:'MEDIUM',i:'No PostToolUse hooks',f:'npx cc-safe-setup'});
207
+ else good.push('PostToolUse ('+post.length+')');
208
+ const score=Math.max(0,100-risks.reduce((n,r)=>n+(r.s==='CRITICAL'?30:r.s==='HIGH'?20:10),0));
209
+ return {risks,good,score};
210
+ }
211
+ function renderAudit(risks,good,score,el) {
212
+ const cls=score>=80?'good':score>=50?'mid':'bad';
213
+ let h='<div class="score '+cls+'">'+score+'/100</div>';
214
+ if(good.length) { h+='<h3 style="color:#3fb950">Working</h3>'; good.forEach(g=>h+='<div class="good-item">✓ '+g+'</div>'); }
215
+ if(risks.length) { h+='<h3>Risks ('+risks.length+')</h3>'; risks.forEach(r=>h+='<div class="risk"><strong>['+r.s+']</strong> '+r.i+'<br><code>'+r.f+'</code></div>'); }
216
+ if(!risks.length) h+='<p style="color:#3fb950">No risks detected.</p>';
217
+ el.innerHTML=h;
218
+ }
219
+
220
+ // HOOK BUILDER
221
+ function buildHook() {
222
+ const action=document.getElementById('hb-action').value;
223
+ const pattern=document.getElementById('hb-pattern').value.trim();
224
+ const message=document.getElementById('hb-message').value.trim()||'Blocked';
225
+ const el=document.getElementById('hb-result');
226
+ if(!pattern){el.innerHTML='<p style="color:#f85149">Enter a pattern</p>';return;}
227
+ let s='#!/bin/bash\nCMD=$(cat | jq -r \'.tool_input.command // empty\' 2>/dev/null)\n[ -z "$CMD" ] && exit 0\n';
228
+ if(action==='block') s+='echo "$CMD" | grep -qE \''+pattern+'\' && echo "BLOCKED: '+message+'" >&2 && exit 2\n';
229
+ else if(action==='warn') s+='echo "$CMD" | grep -qE \''+pattern+'\' && echo "WARNING: '+message+'" >&2\n';
230
+ else s+='echo "$CMD" | grep -qE \''+pattern+'\' && jq -n \'{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}\'\n';
231
+ s+='exit 0';
232
+ el.innerHTML='<h3>Script</h3><pre>'+esc(s)+'</pre><p style="font-size:.75rem;color:#8b949e">Save as ~/.claude/hooks/custom.sh, chmod +x, add to settings.json</p>';
233
+ }
234
+
235
+ // COOKBOOK
236
+ const RECIPES=[
237
+ {cat:'block',t:'Block rm -rf',d:'Destructive commands (#36339)',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\necho "$CMD"|grep -qE \'rm\\s+(-[rf]+\\s+)*/\' && echo "BLOCKED" >&2 && exit 2\nexit 0',trigger:'PreToolUse'},
238
+ {cat:'block',t:'Block force push',d:'Branch protection',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\necho "$CMD"|grep -qE \'git\\s+push.*--force\' && echo "BLOCKED" >&2 && exit 2\nexit 0',trigger:'PreToolUse'},
239
+ {cat:'block',t:'Block .env staging',d:'Secret leak prevention (#6527)',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\necho "$CMD"|grep -qiE \'git\\s+add.*\\.env\' && echo "BLOCKED" >&2 && exit 2\nexit 0',trigger:'PreToolUse'},
240
+ {cat:'block',t:'Block database wipe',d:'migrate:fresh, DROP DATABASE (#37405)',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\necho "$CMD"|grep -qiE \'migrate:fresh|DROP\\s+DATABASE|prisma\\s+migrate\\s+reset\' && echo "BLOCKED" >&2 && exit 2\nexit 0',trigger:'PreToolUse'},
241
+ {cat:'approve',t:'Auto-approve git read',d:'git status/log/diff with -C flag (#36900)',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\necho "$CMD"|grep -qE \'^\\s*git\\s+(-C\\s+\\S+\\s+)?(status|log|diff|branch|show)\' && jq -n \'{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow"}}\'\nexit 0',trigger:'PreToolUse'},
242
+ {cat:'approve',t:'Compound commands',d:'cd && git log auto-approve (#30519)',code:'# Full: npx cc-safe-setup --install-example compound-command-approver',trigger:'PreToolUse'},
243
+ {cat:'detect',t:'Loop detector',d:'Break command repetition (5+ times)',code:'CMD=$(cat|jq -r \'.tool_input.command // empty\')\n[ -z "$CMD" ] && exit 0\nSTATE=/tmp/cc-loop\necho "$CMD">>$STATE\ntail -n 10 $STATE>${STATE}.tmp&&mv ${STATE}.tmp $STATE\nCOUNT=$(grep -cF "$CMD" $STATE||echo 0)\n[ "$COUNT" -ge 5 ]&&echo "BLOCKED: repeated $COUNT times" >&2&&exit 2\nexit 0',trigger:'PreToolUse'},
244
+ {cat:'utility',t:'Session handoff',d:'Save state for next session',code:'# npx cc-safe-setup --install-example session-handoff',trigger:'Stop'},
245
+ {cat:'utility',t:'Cost tracker',d:'Estimate session token cost',code:'# npx cc-safe-setup --install-example cost-tracker',trigger:'PostToolUse'},
246
+ {cat:'utility',t:'tmp cleanup',d:'/tmp/claude-*-cwd files (#8856)',code:'find /tmp -maxdepth 1 -name \'claude-*-cwd\' -type f -mmin +60 -delete 2>/dev/null\nexit 0',trigger:'Stop'},
247
+ ];
248
+
249
+ function initCookbook() {
250
+ const cats=['all','block','approve','detect','utility'];
251
+ document.getElementById('cb-filters').innerHTML=cats.map(c=>'<button class="filter'+(c==='all'?' active':'')+'" onclick="setCbFilter(\''+c+'\')">'+c+'</button>').join('');
252
+ filterRecipes();
253
+ }
254
+ let cbFilter='all';
255
+ function setCbFilter(f){cbFilter=f;document.querySelectorAll('#cb-filters .filter').forEach(b=>b.classList.toggle('active',b.textContent===f));filterRecipes();}
256
+ function filterRecipes(){
257
+ const q=(document.getElementById('cb-search')?.value||'').toLowerCase();
258
+ const filtered=RECIPES.filter(r=>(cbFilter==='all'||r.cat===cbFilter)&&(!q||r.t.toLowerCase().includes(q)||r.d.toLowerCase().includes(q)||(r.code||'').toLowerCase().includes(q)));
259
+ document.getElementById('cb-count').textContent=filtered.length+' recipe(s)';
260
+ document.getElementById('cb-list').innerHTML=filtered.map(r=>'<details class="recipe"><summary>'+esc(r.t)+' <span style="color:#484f58;font-size:.7rem">['+r.trigger+']</span></summary><div class="recipe-body"><p style="color:#8b949e;font-size:.8rem">'+esc(r.d)+'</p>'+(r.code?'<pre>'+esc(r.code)+'</pre>':'')+'</div></details>').join('');
261
+ }
262
+
263
+ function esc(s){return(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
264
+
265
+ // URL param
266
+ (function(){const p=new URLSearchParams(location.search);const c=p.get('config');if(c){try{document.getElementById('settings').value=atob(c);runAudit();}catch{}}})();
267
+
268
+ initCookbook();
269
+ </script>
270
+ </body>
271
+ </html>
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # ================================================================
3
+ # env-source-guard.sh — Block sourcing .env into shell environment
4
+ # ================================================================
5
+ # PURPOSE:
6
+ # Claude Code sometimes sources .env files directly into bash,
7
+ # causing environment variables to leak across commands.
8
+ # This caused a Laravel test suite to use development database
9
+ # instead of test database, wiping real data.
10
+ #
11
+ # GitHub #401 (54 reactions)
12
+ #
13
+ # TRIGGER: PreToolUse
14
+ # MATCHER: "Bash"
15
+ #
16
+ # WHAT IT BLOCKS:
17
+ # - source .env
18
+ # - . .env
19
+ # - source .env.local
20
+ # - export $(cat .env)
21
+ #
22
+ # WHAT IT ALLOWS:
23
+ # - Reading .env with cat (no sourcing)
24
+ # - Framework commands that load env properly
25
+ # ================================================================
26
+
27
+ INPUT=$(cat)
28
+ COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
29
+
30
+ if [[ -z "$COMMAND" ]]; then
31
+ exit 0
32
+ fi
33
+
34
+ # Block direct sourcing of .env files
35
+ if echo "$COMMAND" | grep -qE '(source|\.\s)\s+\.env'; then
36
+ echo "BLOCKED: Sourcing .env into shell environment." >&2
37
+ echo "Command: $COMMAND" >&2
38
+ echo "" >&2
39
+ echo "This loads all variables into the shell, affecting subsequent commands." >&2
40
+ echo "Use your framework's env loader (dotenv, etc.) instead." >&2
41
+ exit 2
42
+ fi
43
+
44
+ # Block export $(cat .env) pattern
45
+ if echo "$COMMAND" | grep -qE 'export\s+\$\(cat\s+\.env'; then
46
+ echo "BLOCKED: Exporting .env contents into shell." >&2
47
+ echo "Command: $COMMAND" >&2
48
+ exit 2
49
+ fi
50
+
51
+ exit 0
package/index.mjs CHANGED
@@ -85,6 +85,7 @@ const DIFF_FILE = DIFF_IDX !== -1 ? process.argv[DIFF_IDX + 1] : null;
85
85
  const SHARE = process.argv.includes('--share');
86
86
  const BENCHMARK = process.argv.includes('--benchmark');
87
87
  const DASHBOARD = process.argv.includes('--dashboard');
88
+ const ISSUES = process.argv.includes('--issues');
88
89
  const CREATE_IDX = process.argv.findIndex(a => a === '--create');
89
90
  const CREATE_DESC = CREATE_IDX !== -1 ? process.argv.slice(CREATE_IDX + 1).join(' ') : null;
90
91
 
@@ -106,6 +107,7 @@ if (HELP) {
106
107
  npx cc-safe-setup --audit --json Machine-readable output for CI/CD
107
108
  npx cc-safe-setup --scan Detect tech stack, recommend hooks
108
109
  npx cc-safe-setup --learn Learn from your block history
110
+ npx cc-safe-setup --issues Show GitHub Issues each hook addresses
109
111
  npx cc-safe-setup --dashboard Real-time status dashboard
110
112
  npx cc-safe-setup --benchmark Measure hook execution time
111
113
  npx cc-safe-setup --share Generate shareable URL for your setup
@@ -805,6 +807,59 @@ async function fullSetup() {
805
807
  console.log();
806
808
  }
807
809
 
810
+ function issues() {
811
+ // Map hooks to the GitHub Issues they address
812
+ const ISSUE_MAP = [
813
+ { hook: 'destructive-guard', issues: ['#36339 rm -rf NTFS junction (93r)', '#36640 NFS mount deletion', '#37331 PowerShell Remove-Item (13r)', '#36233 Mac filesystem deleted (67r)'] },
814
+ { hook: 'branch-guard', issues: ['Untested code pushed to main at 3am'] },
815
+ { hook: 'secret-guard', issues: ['#6527 .env committed to public repo (94r)'] },
816
+ { hook: 'syntax-check', issues: ['Syntax errors cascading through 30+ files'] },
817
+ { hook: 'context-monitor', issues: ['Sessions dying at 3% context with no warning'] },
818
+ { hook: 'comment-strip', issues: ['#29582 Bash comments break permissions (18r)'] },
819
+ { hook: 'cd-git-allow', issues: ['#32985 cd+git permission spam (9r)', '#16561 Compound commands (101r)'] },
820
+ { hook: 'api-error-alert', issues: ['Sessions silently dying from rate limits'] },
821
+ { hook: 'block-database-wipe', issues: ['#37405 Database destroyed (0r)', '#34729 Prisma migrate reset data loss'] },
822
+ { hook: 'compound-command-approver', issues: ['#30519 Permission matching broken (53r)', '#16561 Parse compound commands (101r)'] },
823
+ { hook: 'case-sensitive-guard', issues: ['#37875 exFAT case collision (0r)'] },
824
+ { hook: 'tmp-cleanup', issues: ['#8856 /tmp/claude-*-cwd leak (67r)', '#17609 tmp files not cleaned (29r)'] },
825
+ { hook: 'loop-detector', issues: ['Command repetition loops wasting context'] },
826
+ { hook: 'session-handoff', issues: ['#17428 Enhanced /compact (104r)', '#6354 CLAUDE.md lost after compact (27r)'] },
827
+ { hook: 'cost-tracker', issues: ['No visibility into session token costs'] },
828
+ { hook: 'deploy-guard', issues: ['#37314 Deploy without commit'] },
829
+ { hook: 'protect-dotfiles', issues: ['#37478 .bashrc destroyed (3r)'] },
830
+ { hook: 'scope-guard', issues: ['#36233 Files deleted outside project (67r)'] },
831
+ { hook: 'env-source-guard', issues: ['#401 .env loaded into bash environment (54r)'] },
832
+ { hook: 'diff-size-guard', issues: ['Unreviable mega-commits'] },
833
+ { hook: 'dependency-audit', issues: ['Supply chain risk from unknown packages'] },
834
+ { hook: 'read-before-edit', issues: ['old_string mismatch from editing unread files'] },
835
+ ];
836
+
837
+ console.log();
838
+ console.log(c.bold + ' cc-safe-setup --issues' + c.reset);
839
+ console.log(c.dim + ' Which GitHub Issues each hook addresses' + c.reset);
840
+ console.log();
841
+
842
+ let totalIssues = 0;
843
+ for (const entry of ISSUE_MAP) {
844
+ console.log(' ' + c.green + entry.hook + c.reset);
845
+ for (const issue of entry.issues) {
846
+ const isLink = issue.startsWith('#');
847
+ if (isLink) {
848
+ const num = issue.match(/#(\d+)/)?.[1];
849
+ console.log(' ' + c.dim + 'https://github.com/anthropics/claude-code/issues/' + num + c.reset);
850
+ console.log(' ' + issue.replace(/#\d+\s*/, ''));
851
+ } else {
852
+ console.log(' ' + c.dim + issue + c.reset);
853
+ }
854
+ totalIssues++;
855
+ }
856
+ console.log();
857
+ }
858
+
859
+ console.log(c.bold + ' ' + ISSUE_MAP.length + ' hooks addressing ' + totalIssues + ' issues/problems' + c.reset);
860
+ console.log();
861
+ }
862
+
808
863
  async function dashboard() {
809
864
  const fsModule = await import('fs');
810
865
 
@@ -2272,6 +2327,7 @@ async function main() {
2272
2327
  if (FULL) return fullSetup();
2273
2328
  if (DOCTOR) return doctor();
2274
2329
  if (WATCH) return watch();
2330
+ if (ISSUES) return issues();
2275
2331
  if (DASHBOARD) return dashboard();
2276
2332
  if (BENCHMARK) return benchmark();
2277
2333
  if (SHARE) return share();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cc-safe-setup",
3
- "version": "5.0.0",
4
- "description": "One command to make Claude Code safe for autonomous operation. 8 built-in + 38 examples. 22 commands including dashboard, create, audit, lint, diff, benchmark. 2,500+ daily npm downloads.",
3
+ "version": "5.1.0",
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": {
7
7
  "cc-safe-setup": "index.mjs"