@panguard-ai/threat-cloud 1.5.6 → 1.6.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/dist/admin-dashboard.d.ts +1 -1
- package/dist/admin-dashboard.d.ts.map +1 -1
- package/dist/admin-dashboard.js +123 -24
- package/dist/admin-dashboard.js.map +1 -1
- package/dist/audit-logger.d.ts +1 -1
- package/dist/audit-logger.d.ts.map +1 -1
- package/dist/audit-logger.js.map +1 -1
- package/dist/auth/tier-resolver.d.ts +46 -0
- package/dist/auth/tier-resolver.d.ts.map +1 -0
- package/dist/auth/tier-resolver.js +57 -0
- package/dist/auth/tier-resolver.js.map +1 -0
- package/dist/cli.js +50 -10
- package/dist/cli.js.map +1 -1
- package/dist/database.d.ts +72 -5
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +193 -19
- package/dist/database.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +90 -0
- package/dist/migrations.js.map +1 -1
- package/dist/server.d.ts +55 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +510 -36
- package/dist/server.js.map +1 -1
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-dashboard.d.ts","sourceRoot":"","sources":["../src/admin-dashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wBAAgB,YAAY,
|
|
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"}
|
package/dist/admin-dashboard.js
CHANGED
|
@@ -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
|
|
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"
|
|
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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')}
|
|
267
|
+
function h(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''')}
|
|
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
|
|
400
|
-
html+='<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
|
|
403
|
-
html+='<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
|
|
446
|
-
html+='<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
|
|
449
|
-
html+='<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"
|
|
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"
|
|
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"
|
|
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
|
|
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"
|
|
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||'')+'">× 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
|
|
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"
|
|
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)+'">× 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
|
|
649
|
-
html+='<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
|
|
652
|
-
html+='<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;
|
|
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"}
|
package/dist/audit-logger.d.ts
CHANGED
|
@@ -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"}
|
package/dist/audit-logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-logger.js","sourceRoot":"","sources":["../src/audit-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
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>
|
|
40
|
-
--host <string>
|
|
41
|
-
--db <path>
|
|
42
|
-
--api-key <keys>
|
|
43
|
-
--
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
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:
|
|
62
|
-
adminApiKey:
|
|
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":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhD,SAAS,SAAS,CAAC,IAAc;IAC/B,MAAM,MAAM,GAA0B,EAAE,CAAC;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,QAAQ,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAChB,KAAK,QAAQ;gBACX,MAAM,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;gBAChC,MAAM;YACR,KAAK,QAAQ;gBACX,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACxB,MAAM;YACR,KAAK,MAAM;gBACT,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC1B,MAAM;YACR,KAAK,WAAW;gBACd,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;gBAC7B,MAAM,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAC9C,MAAM;YACR,KAAK,qBAAqB;gBACxB,MAAM,CAAC,eAAe,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACnC,MAAM;YACR,KAAK,iBAAiB;gBACpB,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC/B,MAAM;YACR,KAAK,QAAQ;gBACX,OAAO,CAAC,GAAG,CAAC
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|