@panguard-ai/panguard 0.3.4 → 0.3.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/audit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,YAAY,IAAI,OAAO,CAwGtC"}
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/audit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC,wBAAgB,YAAY,IAAI,OAAO,CAsKtC"}
@@ -3,8 +3,22 @@
3
3
  * panguard audit - 技能安全審計命令
4
4
  */
5
5
  import { Command } from 'commander';
6
+ import { createHash } from 'node:crypto';
7
+ import { readFileSync, existsSync } from 'node:fs';
6
8
  import path from 'node:path';
7
9
  import { c, banner, divider, box, symbols, setLogLevel } from '@panguard-ai/core';
10
+ /** Default Threat Cloud endpoint */
11
+ const DEFAULT_TC_ENDPOINT = 'https://tc.panguard.ai';
12
+ /**
13
+ * Compute a SHA-256 hash of a skill's SKILL.md content for anonymized tracking.
14
+ */
15
+ function computeSkillHash(skillDir) {
16
+ const skillMdPath = path.join(skillDir, 'SKILL.md');
17
+ if (!existsSync(skillMdPath))
18
+ return createHash('sha256').update(skillDir).digest('hex');
19
+ const content = readFileSync(skillMdPath, 'utf-8');
20
+ return createHash('sha256').update(content).digest('hex');
21
+ }
8
22
  export function auditCommand() {
9
23
  const cmd = new Command('audit').description('Audit security of OpenClaw skills / 審計 OpenClaw 技能的安全性');
10
24
  cmd
@@ -13,6 +27,8 @@ export function auditCommand() {
13
27
  .argument('<path>', 'Path to skill directory containing SKILL.md / 包含 SKILL.md 的技能目錄路徑')
14
28
  .option('--json', 'Output as JSON / 以 JSON 格式輸出', false)
15
29
  .option('--verbose', 'Verbose output / 詳細輸出', false)
30
+ .option('--no-cloud', 'Skip Threat Cloud submission / 不回報至 Threat Cloud')
31
+ .option('--tc-endpoint <url>', 'Threat Cloud endpoint', DEFAULT_TC_ENDPOINT)
16
32
  .action(async (skillPath, options) => {
17
33
  if (options.verbose)
18
34
  setLogLevel('debug');
@@ -26,59 +42,100 @@ export function auditCommand() {
26
42
  const report = await auditSkill(resolvedPath);
27
43
  if (options.json) {
28
44
  console.log(JSON.stringify(report, null, 2));
29
- return;
30
45
  }
31
- // Pretty output
32
- const levelColors = {
33
- LOW: c.green,
34
- MEDIUM: c.yellow,
35
- HIGH: c.red,
36
- CRITICAL: (s) => c.bold(c.red(s)),
37
- };
38
- const colorFn = levelColors[report.riskLevel] ?? c.dim;
39
- console.log(box([
40
- `${c.bold('Panguard Skill Audit Report')}`,
41
- '',
42
- `Skill: ${report.manifest?.name ?? 'Unknown'}${report.manifest?.metadata?.version ? ` v${report.manifest.metadata.version}` : ''}`,
43
- `Author: ${report.manifest?.metadata?.author ?? 'Unknown'}`,
44
- `Risk Score: ${colorFn(`${report.riskScore}/100 (${report.riskLevel})`)}`,
45
- `Duration: ${report.durationMs}ms`,
46
- ].join('\n')));
47
- console.log();
48
- for (const check of report.checks) {
49
- const icon = check.status === 'pass'
50
- ? c.green(symbols.pass)
51
- : check.status === 'fail'
52
- ? c.red(symbols.fail)
53
- : check.status === 'warn'
54
- ? c.yellow(symbols.warn)
55
- : c.blue(symbols.info);
56
- const statusLabel = check.status === 'pass'
57
- ? 'PASS'
58
- : check.status === 'fail'
59
- ? 'FAIL'
60
- : check.status === 'warn'
61
- ? 'WARN'
62
- : 'INFO';
63
- console.log(` ${icon} [${statusLabel}] ${check.label}`);
64
- }
65
- console.log();
66
- divider();
67
- if (report.findings.length > 0 && options.verbose) {
46
+ else {
47
+ // Pretty output
48
+ const levelColors = {
49
+ LOW: c.green,
50
+ MEDIUM: c.yellow,
51
+ HIGH: c.red,
52
+ CRITICAL: (s) => c.bold(c.red(s)),
53
+ };
54
+ const colorFn = levelColors[report.riskLevel] ?? c.dim;
55
+ console.log(box([
56
+ `${c.bold('Panguard Skill Audit Report')}`,
57
+ '',
58
+ `Skill: ${report.manifest?.name ?? 'Unknown'}${report.manifest?.metadata?.version ? ` v${report.manifest.metadata.version}` : ''}`,
59
+ `Author: ${report.manifest?.metadata?.author ?? 'Unknown'}`,
60
+ `Risk Score: ${colorFn(`${report.riskScore}/100 (${report.riskLevel})`)}`,
61
+ `Duration: ${report.durationMs}ms`,
62
+ ].join('\n')));
68
63
  console.log();
69
- console.log(c.bold(' Detailed Findings:'));
64
+ for (const check of report.checks) {
65
+ const icon = check.status === 'pass'
66
+ ? c.green(symbols.pass)
67
+ : check.status === 'fail'
68
+ ? c.red(symbols.fail)
69
+ : check.status === 'warn'
70
+ ? c.yellow(symbols.warn)
71
+ : c.blue(symbols.info);
72
+ const statusLabel = check.status === 'pass'
73
+ ? 'PASS'
74
+ : check.status === 'fail'
75
+ ? 'FAIL'
76
+ : check.status === 'warn'
77
+ ? 'WARN'
78
+ : 'INFO';
79
+ console.log(` ${icon} [${statusLabel}] ${check.label}`);
80
+ }
70
81
  console.log();
71
- for (const finding of report.findings) {
72
- const sevColor = finding.severity === 'critical'
73
- ? c.red
74
- : finding.severity === 'high'
75
- ? c.yellow
76
- : c.dim;
77
- console.log(` ${sevColor(`[${finding.severity.toUpperCase()}]`)} ${finding.title}`);
78
- console.log(` ${c.dim(finding.description)}`);
79
- if (finding.location)
80
- console.log(` ${c.dim(`at ${finding.location}`)}`);
82
+ divider();
83
+ if (report.findings.length > 0 && options.verbose) {
81
84
  console.log();
85
+ console.log(c.bold(' Detailed Findings:'));
86
+ console.log();
87
+ for (const finding of report.findings) {
88
+ const sevColor = finding.severity === 'critical'
89
+ ? c.red
90
+ : finding.severity === 'high'
91
+ ? c.yellow
92
+ : c.dim;
93
+ console.log(` ${sevColor(`[${finding.severity.toUpperCase()}]`)} ${finding.title}`);
94
+ console.log(` ${c.dim(finding.description)}`);
95
+ if (finding.location)
96
+ console.log(` ${c.dim(`at ${finding.location}`)}`);
97
+ console.log();
98
+ }
99
+ }
100
+ }
101
+ // ── Report to Threat Cloud (flywheel) ──
102
+ if (options.cloud && report.riskScore > 0) {
103
+ const skillHash = computeSkillHash(resolvedPath);
104
+ const skillName = report.manifest?.name ?? path.basename(resolvedPath);
105
+ const submission = {
106
+ skillHash,
107
+ skillName,
108
+ riskScore: report.riskScore,
109
+ riskLevel: report.riskLevel,
110
+ findingSummaries: report.findings.slice(0, 10).map((f) => ({
111
+ id: f.id,
112
+ category: f.category,
113
+ severity: f.severity,
114
+ title: f.title,
115
+ })),
116
+ };
117
+ try {
118
+ const { ThreatCloudClient } = await import('@panguard-ai/panguard-guard');
119
+ const dataDir = path.join(process.env['HOME'] ?? process.env['USERPROFILE'] ?? '.', '.panguard-guard');
120
+ const tc = new ThreatCloudClient(options.tcEndpoint, dataDir);
121
+ const submitted = await tc.submitSkillThreat(submission);
122
+ if (!options.json) {
123
+ if (submitted) {
124
+ console.log();
125
+ console.log(` ${c.green(symbols.pass)} ${c.dim('Threat intelligence shared with Threat Cloud')}`);
126
+ }
127
+ else {
128
+ console.log();
129
+ console.log(` ${c.dim(`${symbols.info} Threat Cloud offline — results saved locally`)}`);
130
+ }
131
+ }
132
+ }
133
+ catch {
134
+ // Threat Cloud submission is best-effort — never block the audit
135
+ if (!options.json) {
136
+ console.log();
137
+ console.log(` ${c.dim(`${symbols.info} Threat Cloud unavailable — results saved locally`)}`);
138
+ }
82
139
  }
83
140
  }
84
141
  // Exit code
@@ -1 +1 @@
1
- {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/cli/commands/audit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/cli/commands/audit.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoCpC,wBAAgB,YAAY,IAAI,OAAO,CAkXtC"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmCpC,wBAAgB,YAAY,IAAI,OAAO,CAiXtC"}
@@ -99,8 +99,13 @@ export function serveCommand() {
99
99
  }
100
100
  console.log('');
101
101
  }
102
- catch {
103
- console.log(` ${c.dim('Threat Cloud DB not available threat API routes disabled')}`);
102
+ catch (err) {
103
+ const msg = err instanceof Error ? err.message : String(err);
104
+ console.error(` [ERROR] Threat Cloud initialization failed: ${msg}`);
105
+ if (err instanceof Error && err.stack) {
106
+ console.error(` ${err.stack}`);
107
+ }
108
+ console.log(` ${c.dim('Threat Cloud API routes disabled due to error above')}`);
104
109
  console.log('');
105
110
  }
106
111
  // Initialize LLM Reviewer for ATR proposals (optional — needs ANTHROPIC_API_KEY)
@@ -197,14 +202,6 @@ export function serveCommand() {
197
202
  privateKey: process.env['GOOGLE_SHEETS_PRIVATE_KEY'] ?? '',
198
203
  }
199
204
  : undefined;
200
- const lemonsqueezy = process.env['LEMON_SQUEEZY_API_KEY']
201
- ? {
202
- apiKey: process.env['LEMON_SQUEEZY_API_KEY'],
203
- storeId: process.env['LEMON_SQUEEZY_STORE_ID'] ?? '',
204
- webhookSecret: process.env['LEMON_SQUEEZY_WEBHOOK_SECRET'] ?? '',
205
- variantTierMap: JSON.parse(process.env['LEMON_SQUEEZY_VARIANT_MAP'] ?? '{}'),
206
- }
207
- : undefined;
208
205
  const baseUrl = process.env['PANGUARD_BASE_URL'] ?? `http://${host}:${port}`;
209
206
  const authConfig = {
210
207
  db,
@@ -212,7 +209,6 @@ export function serveCommand() {
212
209
  baseUrl,
213
210
  google,
214
211
  sheets,
215
- lemonsqueezy,
216
212
  };
217
213
  const handlers = createAuthHandlers(authConfig);
218
214
  // Initialize Manager proxy for agent/event admin API routes
@@ -242,9 +238,6 @@ export function serveCommand() {
242
238
  console.log(` Routes:`);
243
239
  console.log(` ${c.dim('/api/auth/*')} Auth API`);
244
240
  console.log(` ${c.dim('/api/admin/*')} Admin API`);
245
- if (lemonsqueezy) {
246
- console.log(` ${c.dim('/api/billing/*')} Billing API (Lemon Squeezy)`);
247
- }
248
241
  console.log(` ${c.dim('/api/usage/*')} Usage & Quota API`);
249
242
  if (adminDir) {
250
243
  console.log(` ${c.dim('/admin')} Admin Dashboard`);
@@ -265,7 +258,6 @@ export function serveCommand() {
265
258
  console.log(` Services:`);
266
259
  console.log(` Email: ${emailConfig ? ('apiKey' in emailConfig ? c.safe('Resend API') : c.safe('SMTP')) : c.caution('Not configured')}`);
267
260
  console.log(` OAuth: ${google ? c.safe('Google') : c.dim('Not configured')}`);
268
- console.log(` Billing: ${lemonsqueezy ? c.safe('Lemon Squeezy') : c.dim('Not configured')}`);
269
261
  console.log(` Sheets: ${sheets ? c.safe('Google Sheets') : c.dim('Not configured')}`);
270
262
  console.log(` Manager: ${c.safe(`port ${managerPort}`)}${process.env['MANAGER_AUTH_TOKEN'] ? '' : c.dim(' (no auth)')}`);
271
263
  console.log('');
@@ -399,7 +391,6 @@ async function handleRequest(req, res, handlers, _db, adminDir, managerProxy, th
399
391
  const services = {
400
392
  email: !!(process.env['RESEND_API_KEY'] || process.env['SMTP_HOST']),
401
393
  oauth: !!process.env['GOOGLE_CLIENT_ID'],
402
- billing: !!process.env['LEMON_SQUEEZY_API_KEY'],
403
394
  errorTracking: !!process.env['SENTRY_DSN'],
404
395
  threatCloud: !!threatDb,
405
396
  tcApiKey: !!process.env['TC_API_KEY'],
@@ -410,7 +401,9 @@ async function handleRequest(req, res, handlers, _db, adminDir, managerProxy, th
410
401
  const s = threatDb.getStats();
411
402
  threatStats = { rules: s.totalRules, threats: s.totalThreats };
412
403
  }
413
- catch { /* ignore */ }
404
+ catch {
405
+ /* ignore */
406
+ }
414
407
  }
415
408
  sendJson(res, 200, {
416
409
  ok: true,
@@ -420,7 +413,10 @@ async function handleRequest(req, res, handlers, _db, adminDir, managerProxy, th
420
413
  uptime: Math.round(process.uptime()),
421
414
  db: 'connected',
422
415
  threatStats,
423
- memory: { rss: Math.round(mem.rss / 1024 / 1024), heapUsed: Math.round(mem.heapUsed / 1024 / 1024) },
416
+ memory: {
417
+ rss: Math.round(mem.rss / 1024 / 1024),
418
+ heapUsed: Math.round(mem.heapUsed / 1024 / 1024),
419
+ },
424
420
  services,
425
421
  },
426
422
  });
@@ -429,7 +425,20 @@ async function handleRequest(req, res, handlers, _db, adminDir, managerProxy, th
429
425
  // ── Threat Cloud API Routes ────────────────────────────────────
430
426
  // Security: rate limiting, auth, input validation
431
427
  // Rate limit for Threat Cloud endpoints (per-IP, shared state)
432
- if (threatDb && pathname.startsWith('/api/') && ['/api/threats', '/api/rules', '/api/stats', '/api/atr-proposals', '/api/atr-feedback', '/api/skill-threats', '/api/atr-rules', '/api/yara-rules', '/api/feeds/ip-blocklist', '/api/feeds/domain-blocklist'].some((p) => pathname === p)) {
428
+ if (threatDb &&
429
+ pathname.startsWith('/api/') &&
430
+ [
431
+ '/api/threats',
432
+ '/api/rules',
433
+ '/api/stats',
434
+ '/api/atr-proposals',
435
+ '/api/atr-feedback',
436
+ '/api/skill-threats',
437
+ '/api/atr-rules',
438
+ '/api/yara-rules',
439
+ '/api/feeds/ip-blocklist',
440
+ '/api/feeds/domain-blocklist',
441
+ ].some((p) => pathname === p)) {
433
442
  const clientIP = req.socket.remoteAddress ?? 'unknown';
434
443
  if (!checkTCRateLimit(clientIP)) {
435
444
  sendJson(res, 429, { ok: false, error: 'Rate limit exceeded. Try again later.' });
@@ -455,7 +464,12 @@ async function handleRequest(req, res, handlers, _db, adminDir, managerProxy, th
455
464
  sendJson(res, 400, { ok: false, error: 'Invalid JSON body' });
456
465
  return;
457
466
  }
458
- if (!data['attackSourceIP'] || !data['attackType'] || !data['mitreTechnique'] || !data['sigmaRuleMatched'] || !data['timestamp'] || !data['region']) {
467
+ if (!data['attackSourceIP'] ||
468
+ !data['attackType'] ||
469
+ !data['mitreTechnique'] ||
470
+ !data['sigmaRuleMatched'] ||
471
+ !data['timestamp'] ||
472
+ !data['region']) {
459
473
  sendJson(res, 400, { ok: false, error: 'Missing required fields' });
460
474
  return;
461
475
  }
@@ -511,7 +525,10 @@ async function handleRequest(req, res, handlers, _db, adminDir, managerProxy, th
511
525
  return;
512
526
  }
513
527
  if (!rule['ruleId'] || !rule['ruleContent'] || !rule['source']) {
514
- sendJson(res, 400, { ok: false, error: 'Missing required fields: ruleId, ruleContent, source' });
528
+ sendJson(res, 400, {
529
+ ok: false,
530
+ error: 'Missing required fields: ruleId, ruleContent, source',
531
+ });
515
532
  return;
516
533
  }
517
534
  // Field-level size limits
@@ -557,30 +574,46 @@ async function handleRequest(req, res, handlers, _db, adminDir, managerProxy, th
557
574
  sendJson(res, 400, { ok: false, error: 'Invalid JSON body' });
558
575
  return;
559
576
  }
560
- if (!proposal['patternHash'] || !proposal['ruleContent'] || !proposal['llmProvider'] || !proposal['llmModel'] || !proposal['selfReviewVerdict']) {
577
+ if (!proposal['patternHash'] ||
578
+ !proposal['ruleContent'] ||
579
+ !proposal['llmProvider'] ||
580
+ !proposal['llmModel'] ||
581
+ !proposal['selfReviewVerdict']) {
561
582
  sendJson(res, 400, { ok: false, error: 'Missing required fields' });
562
583
  return;
563
584
  }
564
585
  // Validate and sanitize client ID
565
586
  const rawClientId = req.headers['x-panguard-client-id'];
566
- const clientId = typeof rawClientId === 'string' && /^[a-zA-Z0-9_-]{1,64}$/.test(rawClientId) ? rawClientId : null;
587
+ const clientId = typeof rawClientId === 'string' && /^[a-zA-Z0-9_-]{1,64}$/.test(rawClientId)
588
+ ? rawClientId
589
+ : null;
567
590
  proposal['clientId'] = clientId;
568
591
  // Check if this pattern already has a proposal - if so, increment confirmation
569
592
  const pHash = String(proposal['patternHash']);
570
- const existing = threatDb.getATRProposals().find((p) => p['pattern_hash'] === pHash);
593
+ const existing = threatDb
594
+ .getATRProposals()
595
+ .find((p) => p['pattern_hash'] === pHash);
571
596
  if (existing) {
572
597
  threatDb.confirmATRProposal(pHash);
573
- sendJson(res, 200, { ok: true, data: { message: 'Confirmation recorded', patternHash: pHash } });
598
+ sendJson(res, 200, {
599
+ ok: true,
600
+ data: { message: 'Confirmation recorded', patternHash: pHash },
601
+ });
574
602
  }
575
603
  else {
576
604
  threatDb.insertATRProposal(proposal);
577
605
  // Fire-and-forget LLM review on first submission
578
606
  if (llmReviewer?.isAvailable()) {
579
- void llmReviewer.reviewProposal(pHash, String(proposal['ruleContent'])).catch((err) => {
607
+ void llmReviewer
608
+ .reviewProposal(pHash, String(proposal['ruleContent']))
609
+ .catch((err) => {
580
610
  console.error(`LLM review error for ${pHash}:`, err);
581
611
  });
582
612
  }
583
- sendJson(res, 201, { ok: true, data: { message: 'Proposal submitted', patternHash: pHash } });
613
+ sendJson(res, 201, {
614
+ ok: true,
615
+ data: { message: 'Proposal submitted', patternHash: pHash },
616
+ });
584
617
  }
585
618
  return;
586
619
  }
@@ -618,7 +651,10 @@ async function handleRequest(req, res, handlers, _db, adminDir, managerProxy, th
618
651
  return;
619
652
  }
620
653
  if (!feedback['ruleId'] || typeof feedback['isTruePositive'] !== 'boolean') {
621
- sendJson(res, 400, { ok: false, error: 'Missing or invalid fields: ruleId (string), isTruePositive (boolean)' });
654
+ sendJson(res, 400, {
655
+ ok: false,
656
+ error: 'Missing or invalid fields: ruleId (string), isTruePositive (boolean)',
657
+ });
622
658
  return;
623
659
  }
624
660
  const rawCid = req.headers['x-panguard-client-id'];
@@ -652,16 +688,23 @@ async function handleRequest(req, res, handlers, _db, adminDir, managerProxy, th
652
688
  return;
653
689
  }
654
690
  const riskScore = submission['riskScore'];
655
- if (typeof riskScore !== 'number' || !isFinite(riskScore) || riskScore < 0 || riskScore > 100) {
691
+ if (typeof riskScore !== 'number' ||
692
+ !isFinite(riskScore) ||
693
+ riskScore < 0 ||
694
+ riskScore > 100) {
656
695
  sendJson(res, 400, { ok: false, error: 'riskScore must be a number between 0 and 100' });
657
696
  return;
658
697
  }
659
698
  if (!VALID_RISK_LEVELS.has(String(submission['riskLevel']))) {
660
- sendJson(res, 400, { ok: false, error: 'riskLevel must be one of: LOW, MEDIUM, HIGH, CRITICAL' });
699
+ sendJson(res, 400, {
700
+ ok: false,
701
+ error: 'riskLevel must be one of: LOW, MEDIUM, HIGH, CRITICAL',
702
+ });
661
703
  return;
662
704
  }
663
705
  const rawCid2 = req.headers['x-panguard-client-id'];
664
- submission['clientId'] = typeof rawCid2 === 'string' && /^[a-zA-Z0-9_-]{1,64}$/.test(rawCid2) ? rawCid2 : null;
706
+ submission['clientId'] =
707
+ typeof rawCid2 === 'string' && /^[a-zA-Z0-9_-]{1,64}$/.test(rawCid2) ? rawCid2 : null;
665
708
  threatDb.insertSkillThreat(submission);
666
709
  sendJson(res, 201, { ok: true, data: { message: 'Skill threat recorded' } });
667
710
  return;
@@ -866,23 +909,6 @@ async function handleRequest(req, res, handlers, _db, adminDir, managerProxy, th
866
909
  handlers.handleWaitlistList(req, res);
867
910
  return;
868
911
  }
869
- // Billing API routes (Lemon Squeezy)
870
- if (pathname === '/api/billing/webhook') {
871
- await handlers.handleBillingWebhook(req, res);
872
- return;
873
- }
874
- if (pathname === '/api/billing/checkout') {
875
- await handlers.handleBillingCheckout(req, res);
876
- return;
877
- }
878
- if (pathname === '/api/billing/portal') {
879
- await handlers.handleBillingPortal(req, res);
880
- return;
881
- }
882
- if (pathname === '/api/billing/status') {
883
- handlers.handleBillingStatus(req, res);
884
- return;
885
- }
886
912
  // Usage / Quota API routes
887
913
  if (pathname === '/api/usage') {
888
914
  handlers.handleUsageSummary(req, res);
@@ -1229,6 +1255,7 @@ function sendJson(res, status, data) {
1229
1255
  // ── Threat Cloud Security Helpers ──────────────────────────────
1230
1256
  /** Timing-safe string comparison to prevent side-channel attacks */
1231
1257
  function timingSafeCompare(a, b) {
1258
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1232
1259
  const { timingSafeEqual } = require('node:crypto');
1233
1260
  const ab = Buffer.from(a);
1234
1261
  const bb = Buffer.from(b);
@@ -1248,7 +1275,10 @@ function requireTCWriteAuth(req, res) {
1248
1275
  const tcApiKey = process.env['TC_API_KEY'];
1249
1276
  if (!tcApiKey) {
1250
1277
  if (process.env['NODE_ENV'] === 'production') {
1251
- sendJson(res, 503, { ok: false, error: 'Threat Cloud write API not configured (TC_API_KEY missing)' });
1278
+ sendJson(res, 503, {
1279
+ ok: false,
1280
+ error: 'Threat Cloud write API not configured (TC_API_KEY missing)',
1281
+ });
1252
1282
  return false;
1253
1283
  }
1254
1284
  return true; // dev passthrough
@@ -1342,8 +1372,10 @@ async function seedRulesFromBundled(threatDb) {
1342
1372
  });
1343
1373
  if (!configDir) {
1344
1374
  console.log(` ${c.dim(' No config/ directory found — skipping rule seeding')}`);
1375
+ console.log(` ${c.dim(` Searched: ${configDirs.join(', ')}`)}`);
1345
1376
  return 0;
1346
1377
  }
1378
+ console.log(` ${c.dim(` Using config directory: ${configDir}`)}`);
1347
1379
  /** Recursively collect files matching extensions */
1348
1380
  function collectFiles(dir, extensions) {
1349
1381
  const results = [];
@@ -1358,7 +1390,9 @@ async function seedRulesFromBundled(threatDb) {
1358
1390
  }
1359
1391
  }
1360
1392
  }
1361
- catch { /* skip unreadable dirs */ }
1393
+ catch (err) {
1394
+ console.error(` [WARN] Cannot read directory ${dir}: ${err instanceof Error ? err.message : String(err)}`);
1395
+ }
1362
1396
  return results;
1363
1397
  }
1364
1398
  // 1. Sigma rules (.yml, .yaml)
@@ -1371,8 +1405,11 @@ async function seedRulesFromBundled(threatDb) {
1371
1405
  threatDb.upsertRule({ ruleId, ruleContent: content, publishedAt: now, source: 'sigma' });
1372
1406
  seeded++;
1373
1407
  }
1408
+ console.log(` ${c.dim(` Sigma: ${sigmaFiles.length} files processed`)}`);
1409
+ }
1410
+ catch (err) {
1411
+ console.error(` [WARN] Sigma rule seeding failed: ${err instanceof Error ? err.message : String(err)}`);
1374
1412
  }
1375
- catch { /* no sigma rules */ }
1376
1413
  // 2. YARA rules (.yar, .yara)
1377
1414
  const yaraDir = joinPath(configDir, 'yara-rules');
1378
1415
  try {
@@ -1396,8 +1433,11 @@ async function seedRulesFromBundled(threatDb) {
1396
1433
  seeded++;
1397
1434
  }
1398
1435
  }
1436
+ console.log(` ${c.dim(` YARA: ${yaraFiles.length} files processed`)}`);
1437
+ }
1438
+ catch (err) {
1439
+ console.error(` [WARN] YARA rule seeding failed: ${err instanceof Error ? err.message : String(err)}`);
1399
1440
  }
1400
- catch { /* no yara rules */ }
1401
1441
  // 3. ATR rules (.yaml, .yml) from atr package
1402
1442
  const atrDirs = [
1403
1443
  joinPath(process.cwd(), 'node_modules', 'agent-threat-rules', 'rules'),
@@ -1420,8 +1460,11 @@ async function seedRulesFromBundled(threatDb) {
1420
1460
  threatDb.upsertRule({ ruleId, ruleContent: content, publishedAt: now, source: 'atr' });
1421
1461
  seeded++;
1422
1462
  }
1463
+ console.log(` ${c.dim(` ATR: ${atrFiles.length} files processed`)}`);
1464
+ }
1465
+ catch (err) {
1466
+ console.error(` [WARN] ATR rule seeding failed: ${err instanceof Error ? err.message : String(err)}`);
1423
1467
  }
1424
- catch { /* no atr rules */ }
1425
1468
  }
1426
1469
  return seeded;
1427
1470
  }