@panguard-ai/threat-cloud 1.5.6 → 1.6.1

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.
@@ -7,5 +7,5 @@
7
7
  *
8
8
  * @module @panguard-ai/threat-cloud/admin-dashboard
9
9
  */
10
- export declare function getAdminHTML(): string;
10
+ export declare function getAdminHTML(nonce: string): string;
11
11
  //# sourceMappingURL=admin-dashboard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"admin-dashboard.d.ts","sourceRoot":"","sources":["../src/admin-dashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wBAAgB,YAAY,IAAI,MAAM,CA+oBrC"}
1
+ {"version":3,"file":"admin-dashboard.d.ts","sourceRoot":"","sources":["../src/admin-dashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkvBlD"}
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @module @panguard-ai/threat-cloud/admin-dashboard
9
9
  */
10
- export function getAdminHTML() {
10
+ export function getAdminHTML(nonce) {
11
11
  return `<!DOCTYPE html>
12
12
  <html lang="en">
13
13
  <head>
@@ -96,7 +96,7 @@ tr:hover td{background:var(--surface2)}
96
96
  <body>
97
97
 
98
98
  <div id="login">
99
- <form onsubmit="return doLogin(event)">
99
+ <form id="loginForm">
100
100
  <h1>Threat Cloud <span>Admin</span></h1>
101
101
  <p>Enter your admin API key to continue.</p>
102
102
  <input type="password" id="keyInput" placeholder="TC_ADMIN_API_KEY" autofocus/>
@@ -110,7 +110,7 @@ tr:hover td{background:var(--surface2)}
110
110
  <h1>Threat Cloud <span>Admin</span></h1>
111
111
  <div class="meta">
112
112
  <span id="uptimeText"></span>
113
- <span class="logout" onclick="doLogout()">Logout</span>
113
+ <span class="logout" data-action="logout">Logout</span>
114
114
  </div>
115
115
  </header>
116
116
  <nav id="tabs">
@@ -122,6 +122,7 @@ tr:hover td{background:var(--surface2)}
122
122
  <button data-tab="skills">Skill Threats</button>
123
123
  <button data-tab="blacklist">Blacklist</button>
124
124
  <button data-tab="feeds">Feeds</button>
125
+ <button data-tab="keys">Client Keys</button>
125
126
  <button data-tab="audit">Audit Log</button>
126
127
  </nav>
127
128
  <main id="content">
@@ -129,7 +130,7 @@ tr:hover td{background:var(--surface2)}
129
130
  </main>
130
131
  </div>
131
132
 
132
- <script>
133
+ <script nonce="${nonce}">
133
134
  let API_KEY='';
134
135
  let stats=null;
135
136
  let currentTab='overview';
@@ -170,6 +171,59 @@ function doLogout(){
170
171
  if(k){document.getElementById('keyInput').value=k;doLogin(new Event('submit'));}
171
172
  })();
172
173
 
174
+ // Login form submit handler (CSP-compliant: no inline onsubmit)
175
+ document.getElementById('loginForm').addEventListener('submit',function(e){
176
+ doLogin(e);
177
+ });
178
+
179
+ // Delegated click handler for [data-action] elements. The CSP we serve
180
+ // (script-src 'nonce-...' 'strict-dynamic') unconditionally blocks inline
181
+ // event handlers, so every button/row/span that previously used onclick=""
182
+ // now declares data-action="..." and is routed here. New actions MUST be
183
+ // added to this switch — unknown actions are a no-op (defence against
184
+ // stale/injected data-action values).
185
+ document.addEventListener('click',function(e){
186
+ const el = e.target.closest && e.target.closest('[data-action]');
187
+ if(!el)return;
188
+ const a = el.getAttribute('data-action');
189
+ switch(a){
190
+ case 'logout': doLogout(); break;
191
+ // Rules pagination
192
+ case 'rules-page': rulesPage = parseInt(el.getAttribute('data-page'),10)||0; renderRules(); break;
193
+ case 'rules-page-inc': rulesPage++; renderRules(); break;
194
+ case 'rules-page-dec': rulesPage--; renderRules(); break;
195
+ // Threats pagination
196
+ case 'threats-page': threatsPage = parseInt(el.getAttribute('data-page'),10)||1; renderThreats(); break;
197
+ case 'threats-page-inc': threatsPage++; renderThreats(); break;
198
+ case 'threats-page-dec': threatsPage--; renderThreats(); break;
199
+ // Audit pagination
200
+ case 'audit-page': auditPage = parseInt(el.getAttribute('data-page'),10)||1; renderAuditLog(); break;
201
+ case 'audit-page-inc': auditPage++; renderAuditLog(); break;
202
+ case 'audit-page-dec': auditPage--; renderAuditLog(); break;
203
+ // Proposals — expand/collapse + actions
204
+ case 'toggle-proposal-detail': {
205
+ const detail = document.getElementById(el.getAttribute('data-target'));
206
+ if(detail) detail.style.display = (detail.style.display === 'none' ? 'table-row' : 'none');
207
+ break;
208
+ }
209
+ case 'approve-proposal':
210
+ e.stopPropagation();
211
+ approveProposal(el.getAttribute('data-hash'));
212
+ break;
213
+ case 'reject-proposal':
214
+ e.stopPropagation();
215
+ rejectProposal(el.getAttribute('data-hash'));
216
+ break;
217
+ // Blacklist / whitelist
218
+ case 'add-blacklist': addToBlacklist(); break;
219
+ case 'remove-blacklist': removeFromBlacklist(el.getAttribute('data-hash')); break;
220
+ case 'add-whitelist': addToWhitelist(); break;
221
+ case 'remove-whitelist': removeFromWhitelist(el.getAttribute('data-name')); break;
222
+ // Client-key tier
223
+ case 'update-tier': updateTier(el.getAttribute('data-client-id')); break;
224
+ }
225
+ });
226
+
173
227
  // Tabs
174
228
  document.getElementById('tabs').addEventListener('click',e=>{
175
229
  if(e.target.tagName!=='BUTTON')return;
@@ -189,6 +243,7 @@ function renderTab(tab){
189
243
  case 'skills':renderSkills();break;
190
244
  case 'blacklist':renderBlacklist();break;
191
245
  case 'feeds':renderFeeds();break;
246
+ case 'keys':renderClientKeys();break;
192
247
  case 'audit':renderAuditLog();break;
193
248
  }
194
249
  }
@@ -209,7 +264,7 @@ function apiDelete(path,body){
209
264
  return fetch(path,{method:'DELETE',headers:{Authorization:'Bearer '+API_KEY,'Content-Type':'application/json'},body:JSON.stringify(body)}).then(r=>r.json());
210
265
  }
211
266
  function $(s){return document.getElementById(s)||document.querySelector(s)}
212
- function h(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;')}
267
+ function h(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;')}
213
268
  function num(n){return Number(n).toLocaleString()}
214
269
  function badge(text,cls){return '<span class="badge '+cls+'">'+h(text)+'</span>'}
215
270
  function severityBadge(s){return badge(s,(s||'').toLowerCase())}
@@ -396,11 +451,11 @@ function renderRules(){
396
451
  });
397
452
  html+='</table>';
398
453
  html+='<div class="pagination">';
399
- html+='<button onclick="rulesPage=0;renderRules()" '+(rulesPage===0?'disabled':'')+'>First</button>';
400
- html+='<button onclick="rulesPage--;renderRules()" '+(rulesPage===0?'disabled':'')+'>Prev</button>';
454
+ html+='<button data-action="rules-page" data-page="0" '+(rulesPage===0?'disabled':'')+'>First</button>';
455
+ html+='<button data-action="rules-page-dec" '+(rulesPage===0?'disabled':'')+'>Prev</button>';
401
456
  html+='<span>Page '+(rulesPage+1)+' of '+Math.max(1,pages)+'</span>';
402
- html+='<button onclick="rulesPage++;renderRules()" '+(rulesPage>=pages-1?'disabled':'')+'>Next</button>';
403
- html+='<button onclick="rulesPage='+(pages-1)+';renderRules()" '+(rulesPage>=pages-1?'disabled':'')+'>Last</button>';
457
+ html+='<button data-action="rules-page-inc" '+(rulesPage>=pages-1?'disabled':'')+'>Next</button>';
458
+ html+='<button data-action="rules-page" data-page="'+(pages-1)+'" '+(rulesPage>=pages-1?'disabled':'')+'>Last</button>';
404
459
  html+='</div></div>';
405
460
  $('content').innerHTML=html;
406
461
  });
@@ -442,11 +497,11 @@ function renderThreats(){
442
497
  });
443
498
  html+='</table>';
444
499
  html+='<div class="pagination">';
445
- html+='<button onclick="threatsPage=1;renderThreats()" '+(meta.page<=1?'disabled':'')+'>First</button>';
446
- html+='<button onclick="threatsPage--;renderThreats()" '+(meta.page<=1?'disabled':'')+'>Prev</button>';
500
+ html+='<button data-action="threats-page" data-page="1" '+(meta.page<=1?'disabled':'')+'>First</button>';
501
+ html+='<button data-action="threats-page-dec" '+(meta.page<=1?'disabled':'')+'>Prev</button>';
447
502
  html+='<span>Page '+meta.page+' of '+meta.pages+'</span>';
448
- html+='<button onclick="threatsPage++;renderThreats()" '+(meta.page>=meta.pages?'disabled':'')+'>Next</button>';
449
- html+='<button onclick="threatsPage='+meta.pages+';renderThreats()" '+(meta.page>=meta.pages?'disabled':'')+'>Last</button>';
503
+ html+='<button data-action="threats-page-inc" '+(meta.page>=meta.pages?'disabled':'')+'>Next</button>';
504
+ html+='<button data-action="threats-page" data-page="'+meta.pages+'" '+(meta.page>=meta.pages?'disabled':'')+'>Last</button>';
450
505
  html+='</div>';
451
506
  }
452
507
  html+='</div>';
@@ -466,14 +521,14 @@ function renderProposals(){
466
521
  proposals.forEach(function(p,idx){
467
522
  const status=p.status||p.llm_verdict||'pending';
468
523
  const cls=status==='approved'?'low':status==='rejected'?'critical':'medium';
469
- html+='<tr style="cursor:pointer" onclick="var el=document.getElementById(\\'proposal-detail-'+idx+'\\');el.style.display=el.style.display===\\'none\\'?\\'table-row\\':\\'none\\'">';
524
+ html+='<tr style="cursor:pointer" data-action="toggle-proposal-detail" data-target="proposal-detail-'+idx+'">';
470
525
  html+='<td title="'+h(p.pattern_hash)+'">'+h((p.pattern_hash||'').slice(0,16))+'...</td>';
471
526
  html+='<td>'+badge(status,cls)+'</td>';
472
527
  html+='<td>'+num(p.confirmation_count||0)+'</td>';
473
528
  html+='<td>'+(p.llm_verdict?badge(p.llm_verdict,p.llm_verdict==='approve'?'low':'critical'):'<span style="color:var(--dim)">pending</span>')+'</td>';
474
529
  html+='<td>'+timeAgo(p.created_at)+'</td>';
475
- html+='<td><button style="padding:4px 10px;background:var(--green);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600;margin-right:4px" onclick="event.stopPropagation();approveProposal(\\''+h(p.pattern_hash)+'\\')">Approve</button>';
476
- html+='<button style="padding:4px 10px;background:var(--red);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600" onclick="event.stopPropagation();rejectProposal(\\''+h(p.pattern_hash)+'\\')">Reject</button></td>';
530
+ html+='<td><button style="padding:4px 10px;background:var(--green);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600;margin-right:4px" data-action="approve-proposal" data-hash="'+h(p.pattern_hash)+'">Approve</button>';
531
+ html+='<button style="padding:4px 10px;background:var(--red);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600" data-action="reject-proposal" data-hash="'+h(p.pattern_hash)+'">Reject</button></td>';
477
532
  html+='</tr>';
478
533
  html+='<tr id="proposal-detail-'+idx+'" style="display:none"><td colspan="6"><pre style="background:var(--bg);padding:12px;border-radius:6px;overflow-x:auto;font-size:12px;white-space:pre-wrap;max-height:300px;overflow-y:auto">'+h(p.rule_content||p.ruleContent||p.pattern||'No rule content available')+'</pre></td></tr>';
479
534
  });
@@ -527,7 +582,7 @@ function renderBlacklist(){
527
582
  html+='<div style="padding:16px;border-bottom:1px solid var(--border);display:flex;gap:8px;align-items:center;flex-wrap:wrap">';
528
583
  html+='<input type="text" id="blSkillName" placeholder="Skill name" style="padding:6px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;flex:1;min-width:160px"/>';
529
584
  html+='<input type="text" id="blReason" placeholder="Reason" style="padding:6px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;flex:1;min-width:160px"/>';
530
- html+='<button onclick="addToBlacklist()" style="padding:6px 14px;background:var(--red);color:var(--bg);border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer">Block</button>';
585
+ html+='<button data-action="add-blacklist" style="padding:6px 14px;background:var(--red);color:var(--bg);border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer">Block</button>';
531
586
  html+='</div>';
532
587
  if(!list.length){html+='<div class="empty">No blacklisted skills yet. Skills are blacklisted when 3+ distinct clients report avg risk >= 70.</div>';}
533
588
  else{
@@ -539,7 +594,7 @@ function renderBlacklist(){
539
594
  html+='<td>'+num(s.reportCount)+'</td>';
540
595
  html+='<td>'+timeAgo(s.firstReported)+'</td>';
541
596
  html+='<td>'+timeAgo(s.lastReported)+'</td>';
542
- html+='<td><button style="padding:4px 10px;background:var(--red);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600" onclick="removeFromBlacklist(\\''+h(s.skillHash||s.skill_hash||'')+'\\')">&times; Remove</button></td></tr>';
597
+ html+='<td><button style="padding:4px 10px;background:var(--red);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600" data-action="remove-blacklist" data-hash="'+h(s.skillHash||s.skill_hash||'')+'">&times; Remove</button></td></tr>';
543
598
  });
544
599
  html+='</table>';
545
600
  }
@@ -592,7 +647,7 @@ function renderFeeds(){
592
647
  html+='<div class="table-wrap"><div class="table-header"><h2>Community Skill Whitelist ('+whiteList.length+')</h2></div>';
593
648
  html+='<div style="padding:16px;border-bottom:1px solid var(--border);display:flex;gap:8px;align-items:center">';
594
649
  html+='<input type="text" id="wlSkillName" placeholder="Skill name to whitelist" style="padding:6px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:13px;flex:1;min-width:200px"/>';
595
- html+='<button onclick="addToWhitelist()" style="padding:6px 14px;background:var(--green);color:var(--bg);border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer">Add</button>';
650
+ html+='<button data-action="add-whitelist" style="padding:6px 14px;background:var(--green);color:var(--bg);border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer">Add</button>';
596
651
  html+='</div>';
597
652
  if(!whiteList.length){html+='<div class="empty">No whitelisted skills yet.</div>';}
598
653
  else{
@@ -602,7 +657,7 @@ function renderFeeds(){
602
657
  html+='<tr><td>'+h(sName)+'</td>';
603
658
  html+='<td>'+num(s.report_count||s.reportCount||0)+'</td>';
604
659
  html+='<td title="'+h(s.fingerprint_hash||'')+'">'+h((s.fingerprint_hash||'-').slice(0,16))+'</td>';
605
- html+='<td><button style="padding:4px 10px;background:var(--red);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600" onclick="removeFromWhitelist(\\''+h(sName)+'\\')">&times; Remove</button></td></tr>';
660
+ html+='<td><button style="padding:4px 10px;background:var(--red);color:var(--bg);border:none;border-radius:4px;cursor:pointer;font-size:12px;font-weight:600" data-action="remove-whitelist" data-name="'+h(sName)+'">&times; Remove</button></td></tr>';
606
661
  });
607
662
  html+='</table>';
608
663
  }
@@ -622,6 +677,50 @@ function removeFromWhitelist(name){
622
677
  apiDelete('/api/skill-whitelist',{skillName:name}).then(function(){renderFeeds();}).catch(function(){alert('Failed to remove from whitelist');});
623
678
  }
624
679
 
680
+ // Client Keys + Tier Management
681
+ function renderClientKeys(){
682
+ $('content').innerHTML='<div class="loading">Loading client keys...</div>';
683
+ api('/api/admin/client-keys?limit=200').then(function(d){
684
+ var rows=d.data||[];
685
+ var html='<div class="table-wrap"><div class="table-header"><h2>Client Keys ('+num(rows.length)+')</h2></div>';
686
+ html+='<table><tr><th>Client ID</th><th>Tier</th><th>Created</th><th>Last Used</th><th>Revoked</th><th>Actions</th></tr>';
687
+ if(!rows.length){html+='<tr><td colspan="6" class="empty">No client keys registered yet.</td></tr>';}
688
+ rows.forEach(function(r){
689
+ var tier=r.tier||'community';
690
+ html+='<tr><td title="'+h(r.clientId)+'">'+h(r.clientId)+'</td>';
691
+ html+='<td><span class="badge '+(tier==='enterprise'?'high':tier==='pilot'?'medium':'low')+'">'+h(tier)+'</span></td>';
692
+ html+='<td>'+timeAgo(r.createdAt)+'</td>';
693
+ html+='<td>'+(r.lastUsedAt?timeAgo(r.lastUsedAt):'-')+'</td>';
694
+ html+='<td>'+(r.revoked?'yes':'no')+'</td>';
695
+ html+='<td>';
696
+ if(!r.revoked){
697
+ html+='<select id="tier-'+h(r.clientId)+'">';
698
+ ['community','pilot','enterprise'].forEach(function(t){
699
+ html+='<option value="'+t+'"'+(t===tier?' selected':'')+'>'+t+'</option>';
700
+ });
701
+ html+='</select> ';
702
+ const safeId = encodeURIComponent(r.clientId).replace(/'/g, "%27");
703
+ html += '<button data-action="update-tier" data-client-id="' + h(safeId) + '">Save</button>';
704
+ }
705
+ html+='</td></tr>';
706
+ });
707
+ html+='</table></div>';
708
+ $('content').innerHTML=html;
709
+ }).catch(function(){
710
+ $('content').innerHTML='<div class="empty">Failed to load client keys.</div>';
711
+ });
712
+ }
713
+ function updateTier(clientIdEnc){
714
+ var clientId=decodeURIComponent(clientIdEnc);
715
+ var sel=document.getElementById('tier-'+clientId);
716
+ if(!sel)return;
717
+ var tier=sel.value;
718
+ apiPost('/api/admin/client-keys/'+encodeURIComponent(clientId)+'/tier',{tier:tier}).then(function(r){
719
+ if(r&&r.ok){renderClientKeys();}
720
+ else{alert('Failed to update tier: '+((r&&r.error)||'unknown error'));}
721
+ }).catch(function(){alert('Failed to update tier');});
722
+ }
723
+
625
724
  // Audit Log
626
725
  let auditPage=1;
627
726
  function renderAuditLog(){
@@ -645,11 +744,11 @@ function renderAuditLog(){
645
744
  html+='</table>';
646
745
  if(meta.pages>1){
647
746
  html+='<div class="pagination">';
648
- html+='<button onclick="auditPage=1;renderAuditLog()" '+(meta.page<=1?'disabled':'')+'>First</button>';
649
- html+='<button onclick="auditPage--;renderAuditLog()" '+(meta.page<=1?'disabled':'')+'>Prev</button>';
747
+ html+='<button data-action="audit-page" data-page="1" '+(meta.page<=1?'disabled':'')+'>First</button>';
748
+ html+='<button data-action="audit-page-dec" '+(meta.page<=1?'disabled':'')+'>Prev</button>';
650
749
  html+='<span>Page '+meta.page+' of '+meta.pages+'</span>';
651
- html+='<button onclick="auditPage++;renderAuditLog()" '+(meta.page>=meta.pages?'disabled':'')+'>Next</button>';
652
- html+='<button onclick="auditPage='+meta.pages+';renderAuditLog()" '+(meta.page>=meta.pages?'disabled':'')+'>Last</button>';
750
+ html+='<button data-action="audit-page-inc" '+(meta.page>=meta.pages?'disabled':'')+'>Next</button>';
751
+ html+='<button data-action="audit-page" data-page="'+meta.pages+'" '+(meta.page>=meta.pages?'disabled':'')+'>Last</button>';
653
752
  html+='</div>';
654
753
  }
655
754
  }
@@ -1 +1 @@
1
- {"version":3,"file":"admin-dashboard.js","sourceRoot":"","sources":["../src/admin-dashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,UAAU,YAAY;IAC1B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA6oBD,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"admin-dashboard.js","sourceRoot":"","sources":["../src/admin-dashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0HQ,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAsnBd,CAAC;AACT,CAAC"}
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import type Database from 'better-sqlite3';
11
11
  /** Valid audit action types / 有效的審計動作類型 */
12
- export type AuditAction = 'rule.create' | 'rule.sync' | 'rule.delete' | 'rule.bulk-delete' | 'proposal.approve' | 'proposal.reject' | 'threat.submit' | 'skill_threat.submit' | 'scan_event.submit' | 'admin.login' | 'client_key.register' | 'client_key.revoke' | 'partner_key.issue';
12
+ export type AuditAction = 'rule.create' | 'rule.sync' | 'rule.delete' | 'rule.bulk-delete' | 'proposal.approve' | 'proposal.reject' | 'threat.submit' | 'skill_threat.submit' | 'scan_event.submit' | 'admin.login' | 'client_key.register' | 'client_key.register_github' | 'client_key.revoke' | 'client_key.tier_update' | 'partner_key.issue';
13
13
  /** An entry from the audit_log table / audit_log 資料表的條目 */
14
14
  export interface AuditLogEntry {
15
15
  id: number;
@@ -1 +1 @@
1
- {"version":3,"file":"audit-logger.d.ts","sourceRoot":"","sources":["../src/audit-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,2CAA2C;AAC3C,MAAM,MAAM,WAAW,GACnB,aAAa,GACb,WAAW,GACX,aAAa,GACb,kBAAkB,GAClB,kBAAkB,GAClB,iBAAiB,GACjB,eAAe,GACf,qBAAqB,GACrB,mBAAmB,GACnB,aAAa,GACb,qBAAqB,GACrB,mBAAmB,GACnB,mBAAmB,CAAC;AAExB,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAcD;;;;;;;GAOG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAoB;gBAE3B,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAIjC;;;;;;;;;;OAUG;IACH,SAAS,CACP,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,EACnB,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI;IAaP;;;;;;;OAOG;IACH,WAAW,CAAC,KAAK,GAAE,MAAY,EAAE,MAAM,GAAE,MAAU,GAAG,aAAa,EAAE;IAwBrE;;;OAGG;IACH,gBAAgB,IAAI,MAAM;CAO3B"}
1
+ {"version":3,"file":"audit-logger.d.ts","sourceRoot":"","sources":["../src/audit-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,2CAA2C;AAC3C,MAAM,MAAM,WAAW,GACnB,aAAa,GACb,WAAW,GACX,aAAa,GACb,kBAAkB,GAClB,kBAAkB,GAClB,iBAAiB,GACjB,eAAe,GACf,qBAAqB,GACrB,mBAAmB,GACnB,aAAa,GACb,qBAAqB,GACrB,4BAA4B,GAC5B,mBAAmB,GACnB,wBAAwB,GACxB,mBAAmB,CAAC;AAExB,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAcD;;;;;;;GAOG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAoB;gBAE3B,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAIjC;;;;;;;;;;OAUG;IACH,SAAS,CACP,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,WAAW,EACnB,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,SAAS,CAAC,EAAE,MAAM,GACjB,IAAI;IAaP;;;;;;;OAOG;IACH,WAAW,CAAC,KAAK,GAAE,MAAY,EAAE,MAAM,GAAE,MAAU,GAAG,aAAa,EAAE;IAwBrE;;;OAGG;IACH,gBAAgB,IAAI,MAAM;CAO3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"audit-logger.js","sourceRoot":"","sources":["../src/audit-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA4CH;;;;;;;GAOG;AACH,MAAM,OAAO,WAAW;IACL,EAAE,CAAoB;IAEvC,YAAY,EAAqB;QAC/B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;IAED;;;;;;;;;;OAUG;IACH,SAAS,CACP,KAAa,EACb,MAAmB,EACnB,YAAoB,EACpB,UAAmB,EACnB,OAAiC,EACjC,SAAkB;QAElB,MAAM,WAAW,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAE3E,IAAI,CAAC,EAAE;aACJ,OAAO,CACN;;;OAGD,CACA;aACA,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,IAAI,IAAI,EAAE,WAAW,EAAE,SAAS,IAAI,IAAI,CAAC,CAAC;IAC1F,CAAC;IAED;;;;;;;OAOG;IACH,WAAW,CAAC,QAAgB,GAAG,EAAE,SAAiB,CAAC;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CACN;;;;;OAKD,CACA;aACA,GAAG,CAAC,KAAK,EAAE,MAAM,CAAkB,CAAC;QAEvC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACxB,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,YAAY,EAAE,GAAG,CAAC,aAAa;YAC/B,UAAU,EAAE,GAAG,CAAC,WAAW;YAC3B,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAA6B,CAAC,CAAC,CAAC,IAAI;YAC3F,SAAS,EAAE,GAAG,CAAC,UAAU;SAC1B,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;;OAGG;IACH,gBAAgB;QACd,OACE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC,GAAG,EAG/D,CAAC,KAAK,CAAC;IACV,CAAC;CACF"}
1
+ {"version":3,"file":"audit-logger.js","sourceRoot":"","sources":["../src/audit-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA8CH;;;;;;;GAOG;AACH,MAAM,OAAO,WAAW;IACL,EAAE,CAAoB;IAEvC,YAAY,EAAqB;QAC/B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;IAED;;;;;;;;;;OAUG;IACH,SAAS,CACP,KAAa,EACb,MAAmB,EACnB,YAAoB,EACpB,UAAmB,EACnB,OAAiC,EACjC,SAAkB;QAElB,MAAM,WAAW,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAE3E,IAAI,CAAC,EAAE;aACJ,OAAO,CACN;;;OAGD,CACA;aACA,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,IAAI,IAAI,EAAE,WAAW,EAAE,SAAS,IAAI,IAAI,CAAC,CAAC;IAC1F,CAAC;IAED;;;;;;;OAOG;IACH,WAAW,CAAC,QAAgB,GAAG,EAAE,SAAiB,CAAC;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CACN;;;;;OAKD,CACA;aACA,GAAG,CAAC,KAAK,EAAE,MAAM,CAAkB,CAAC;QAEvC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;YACxB,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,YAAY,EAAE,GAAG,CAAC,aAAa;YAC/B,UAAU,EAAE,GAAG,CAAC,WAAW;YAC3B,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAA6B,CAAC,CAAC,CAAC,IAAI;YAC3F,SAAS,EAAE,GAAG,CAAC,UAAU;SAC1B,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;;OAGG;IACH,gBAAgB;QACd,OACE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC,GAAG,EAG/D,CAAC,KAAK,CAAC;IACV,CAAC;CACF"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Tier resolution + per-tier quota table.
3
+ * 階層解析與配額表
4
+ *
5
+ * Maps the authenticated identity of a request (admin / static / client-guard /
6
+ * client-partner / anonymous) plus the workspace tier stored on the client
7
+ * key row to a single TcTier value. That value drives the per-token rate
8
+ * limiter in the HTTP server: each tier gets its own requests-per-minute
9
+ * budget.
10
+ *
11
+ * Resolution rules
12
+ * - admin -> enterprise (TC_ADMIN_API_KEY = full quota)
13
+ * - static -> enterprise (TC_API_KEYS = pre-shared internal)
14
+ * - anonymous -> community (unauthenticated public reads)
15
+ * - client-guard/partner -> tier from client_keys.tier column
16
+ * (falls back to community when missing/unknown)
17
+ *
18
+ * @module @panguard-ai/threat-cloud/auth/tier-resolver
19
+ */
20
+ /** Workspace tier — drives requests-per-minute budget. */
21
+ export type TcTier = 'community' | 'pilot' | 'enterprise';
22
+ /** Requests-per-minute budget per tier. */
23
+ export declare const TIER_LIMITS: Record<TcTier, number>;
24
+ /** Authenticated role attached to the request by the server's auth gate. */
25
+ export type TcAuthRole = 'admin' | 'static' | 'client-guard' | 'client-partner' | 'anonymous';
26
+ /**
27
+ * Client key row info (the subset the resolver cares about). The optional
28
+ * `tier` field is what the resolver actually reads — clientId/role exist only
29
+ * for log/audit context at the call site.
30
+ */
31
+ export interface ClientKeyInfo {
32
+ readonly clientId: string;
33
+ readonly role: string;
34
+ readonly tier?: string;
35
+ }
36
+ /**
37
+ * Decide which tier this request is rate-limited under.
38
+ * 決定此請求屬於哪個階層。
39
+ *
40
+ * @param authRole Role attached by the auth gate in server.ts.
41
+ * @param clientKeyInfo Row data for client-guard/client-partner keys; null otherwise.
42
+ */
43
+ export declare function resolveTier(authRole: TcAuthRole, clientKeyInfo: ClientKeyInfo | null): TcTier;
44
+ /** Type guard: is the string a known tier name? Used by the admin endpoint. */
45
+ export declare function isTcTier(value: unknown): value is TcTier;
46
+ //# sourceMappingURL=tier-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tier-resolver.d.ts","sourceRoot":"","sources":["../../src/auth/tier-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,MAAM,GAAG,WAAW,GAAG,OAAO,GAAG,YAAY,CAAC;AAE1D,2CAA2C;AAC3C,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAI9C,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,cAAc,GAAG,gBAAgB,GAAG,WAAW,CAAC;AAE9F;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,GAAG,IAAI,GAAG,MAAM,CAc7F;AAED,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAExD"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Tier resolution + per-tier quota table.
3
+ * 階層解析與配額表
4
+ *
5
+ * Maps the authenticated identity of a request (admin / static / client-guard /
6
+ * client-partner / anonymous) plus the workspace tier stored on the client
7
+ * key row to a single TcTier value. That value drives the per-token rate
8
+ * limiter in the HTTP server: each tier gets its own requests-per-minute
9
+ * budget.
10
+ *
11
+ * Resolution rules
12
+ * - admin -> enterprise (TC_ADMIN_API_KEY = full quota)
13
+ * - static -> enterprise (TC_API_KEYS = pre-shared internal)
14
+ * - anonymous -> community (unauthenticated public reads)
15
+ * - client-guard/partner -> tier from client_keys.tier column
16
+ * (falls back to community when missing/unknown)
17
+ *
18
+ * @module @panguard-ai/threat-cloud/auth/tier-resolver
19
+ */
20
+ /** Requests-per-minute budget per tier. */
21
+ export const TIER_LIMITS = {
22
+ community: 120,
23
+ pilot: 1200,
24
+ enterprise: 12000,
25
+ };
26
+ /**
27
+ * Decide which tier this request is rate-limited under.
28
+ * 決定此請求屬於哪個階層。
29
+ *
30
+ * @param authRole Role attached by the auth gate in server.ts.
31
+ * @param clientKeyInfo Row data for client-guard/client-partner keys; null otherwise.
32
+ */
33
+ export function resolveTier(authRole, clientKeyInfo) {
34
+ // admin / static keys are pre-shared with internal services — they always
35
+ // get the full enterprise budget so internal jobs aren't throttled.
36
+ if (authRole === 'admin')
37
+ return 'enterprise';
38
+ if (authRole === 'static')
39
+ return 'enterprise';
40
+ // Unauthenticated public reads (/health, /api/stats, etc.) get the
41
+ // baseline community budget; this branch is normally short-circuited at
42
+ // the call site since anonymous requests go through the per-IP limiter.
43
+ if (authRole === 'anonymous')
44
+ return 'community';
45
+ // client-guard / client-partner: tier lives on the client_keys row.
46
+ const tier = clientKeyInfo?.tier;
47
+ if (tier === 'enterprise')
48
+ return 'enterprise';
49
+ if (tier === 'pilot')
50
+ return 'pilot';
51
+ return 'community';
52
+ }
53
+ /** Type guard: is the string a known tier name? Used by the admin endpoint. */
54
+ export function isTcTier(value) {
55
+ return value === 'community' || value === 'pilot' || value === 'enterprise';
56
+ }
57
+ //# sourceMappingURL=tier-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tier-resolver.js","sourceRoot":"","sources":["../../src/auth/tier-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,2CAA2C;AAC3C,MAAM,CAAC,MAAM,WAAW,GAA2B;IACjD,SAAS,EAAE,GAAG;IACd,KAAK,EAAE,IAAI;IACX,UAAU,EAAE,KAAK;CAClB,CAAC;AAgBF;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,QAAoB,EAAE,aAAmC;IACnF,0EAA0E;IAC1E,oEAAoE;IACpE,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,YAAY,CAAC;IAC9C,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC;IAC/C,mEAAmE;IACnE,wEAAwE;IACxE,wEAAwE;IACxE,IAAI,QAAQ,KAAK,WAAW;QAAE,OAAO,WAAW,CAAC;IACjD,oEAAoE;IACpE,MAAM,IAAI,GAAG,aAAa,EAAE,IAAI,CAAC;IACjC,IAAI,IAAI,KAAK,YAAY;QAAE,OAAO,YAAY,CAAC;IAC/C,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC;IACrC,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,YAAY,CAAC;AAC9E,CAAC"}
package/dist/cli.js CHANGED
@@ -20,13 +20,32 @@ function parseArgs(args) {
20
20
  config.dbPath = args[++i];
21
21
  break;
22
22
  case '--api-key':
23
+ process.stderr.write('[DEPRECATION WARNING] --api-key CLI flag is deprecated and will be removed in a future release.\n' +
24
+ ' Use the TC_API_KEYS environment variable instead.\n');
23
25
  config.apiKeyRequired = true;
24
26
  config.apiKeys = (args[++i] ?? '').split(',');
25
27
  break;
26
28
  case '--anthropic-api-key':
29
+ // Transitional behaviour: previously removed in v15, but that broke
30
+ // existing systemd / docker-compose units on first restart. Restored
31
+ // as DEPRECATED for one release. Will be hard-removed after the
32
+ // grace window (2026-08-19). See MIGRATION_v15.md.
33
+ process.stderr.write('[DEPRECATION WARNING] --anthropic-api-key CLI flag is deprecated.\n' +
34
+ ' Set the ANTHROPIC_API_KEY environment variable instead.\n' +
35
+ ' This flag will be REMOVED after 2026-08-19 because argv is\n' +
36
+ ' visible in `ps aux` output to any local user.\n');
27
37
  config.anthropicApiKey = args[++i];
28
38
  break;
29
39
  case '--admin-api-key':
40
+ // Same transitional treatment as --anthropic-api-key. Admin keys are
41
+ // higher-sensitivity, but a hard-removal break in v15 was reported
42
+ // to cause production restart failures. Soft-deprecate with a louder
43
+ // banner; hard-remove on 2026-08-19. See MIGRATION_v15.md.
44
+ process.stderr.write('[DEPRECATION WARNING] --admin-api-key CLI flag is deprecated and\n' +
45
+ ' HIGHLY DANGEROUS — argv is visible to every local user via\n' +
46
+ ' `ps aux`, and shell history may persist the value. Migrate to\n' +
47
+ ' the TC_ADMIN_API_KEY environment variable immediately.\n' +
48
+ ' This flag will be REMOVED after 2026-08-19.\n');
30
49
  config.adminApiKey = args[++i];
31
50
  break;
32
51
  case '--help':
@@ -36,13 +55,16 @@ Threat Cloud Server - Collective Threat Intelligence Backend
36
55
  Usage: threat-cloud [options]
37
56
 
38
57
  Options:
39
- --port <number> Listen port (default: 8080)
40
- --host <string> Listen host (default: 127.0.0.1)
41
- --db <path> SQLite database path (default: ./threat-cloud.db)
42
- --api-key <keys> Comma-separated API keys (enables auth)
43
- --anthropic-api-key <key> Anthropic API key for LLM review of ATR proposals
44
- --admin-api-key <key> Admin key for write-protected endpoints (POST /api/rules)
45
- --help Show this help
58
+ --port <number> Listen port (default: 8080)
59
+ --host <string> Listen host (default: 127.0.0.1)
60
+ --db <path> SQLite database path (default: ./threat-cloud.db)
61
+ --api-key <keys> Comma-separated API keys (DEPRECATED — use TC_API_KEYS env var)
62
+ --help Show this help
63
+
64
+ Secret configuration (environment variables only — never pass via CLI flags):
65
+ TC_ADMIN_API_KEY Admin key for write-protected endpoints
66
+ TC_API_KEYS Comma-separated API keys (preferred over --api-key)
67
+ ANTHROPIC_API_KEY Anthropic API key for LLM review of ATR proposals
46
68
  `);
47
69
  process.exit(0);
48
70
  }
@@ -51,15 +73,33 @@ Options:
51
73
  }
52
74
  async function main() {
53
75
  const args = parseArgs(process.argv.slice(2));
76
+ // Fail-closed: API key enforcement is ON by default.
77
+ // To disable (local development only), set TC_API_KEY_REQUIRED=false explicitly.
78
+ const apiKeyRequiredEnv = process.env['TC_API_KEY_REQUIRED'];
79
+ let apiKeyRequired;
80
+ if (args.apiKeyRequired === true) {
81
+ // --api-key flag was provided; honour its implied true
82
+ apiKeyRequired = true;
83
+ }
84
+ else if (apiKeyRequiredEnv !== undefined) {
85
+ apiKeyRequired = apiKeyRequiredEnv.toLowerCase() !== 'false';
86
+ }
87
+ else {
88
+ // Default: fail-closed (require auth)
89
+ apiKeyRequired = true;
90
+ }
91
+ if (!apiKeyRequired) {
92
+ process.stderr.write('WARNING: API key requirement is DISABLED. This is dangerous outside local development.\n');
93
+ }
54
94
  const config = {
55
95
  port: args.port ?? Number(process.env['PORT'] ?? '8080'),
56
96
  host: args.host ?? process.env['TC_HOST'] ?? process.env['HOST'] ?? '127.0.0.1',
57
97
  dbPath: args.dbPath ?? process.env['TC_DB_PATH'] ?? process.env['DB_PATH'] ?? './threat-cloud.db',
58
- apiKeyRequired: args.apiKeyRequired ?? process.env['NODE_ENV'] === 'production',
98
+ apiKeyRequired,
59
99
  apiKeys: args.apiKeys ?? process.env['TC_API_KEYS']?.split(',') ?? [],
60
100
  rateLimitPerMinute: Number(process.env['TC_RATE_LIMIT'] ?? '120'),
61
- anthropicApiKey: args.anthropicApiKey ?? process.env['ANTHROPIC_API_KEY'],
62
- adminApiKey: args.adminApiKey ?? process.env['TC_ADMIN_API_KEY'],
101
+ anthropicApiKey: process.env['ANTHROPIC_API_KEY'],
102
+ adminApiKey: process.env['TC_ADMIN_API_KEY'],
63
103
  };
64
104
  const server = new ThreatCloudServer(config);
65
105
  const shutdown = async () => {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}