@weave_protocol/mund 0.1.13 → 0.2.2

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.
@@ -0,0 +1,920 @@
1
+ /**
2
+ * Threat Intelligence Manager
3
+ * @weave_protocol/mund
4
+ */
5
+
6
+ import type {
7
+ IntelSource,
8
+ IntelSourceConfig,
9
+ IntelCategory,
10
+ ThreatPattern,
11
+ PatternMatch,
12
+ IntelUpdateResult,
13
+ BulkUpdateResult,
14
+ IntelStatus,
15
+ ThreatScanConfig,
16
+ ThreatScanResult,
17
+ Severity,
18
+ } from './threat-intel-types.js';
19
+
20
+ // ============================================================================
21
+ // Built-in Threat Patterns
22
+ // ============================================================================
23
+
24
+ const BUILTIN_PATTERNS: ThreatPattern[] = [
25
+ // Prompt Injection patterns
26
+ {
27
+ id: 'pi_direct_override',
28
+ sourceId: 'weave_builtin',
29
+ category: 'prompt_injection',
30
+ name: 'Direct Instruction Override',
31
+ description: 'Attempts to directly override previous instructions',
32
+ patternType: 'regex',
33
+ pattern: 'ignore\\s+(all\\s+)?(previous|prior|above|earlier)\\s+(instructions|prompts|context)',
34
+ severity: 'critical',
35
+ confidence: 0.95,
36
+ mitreId: 'T1059',
37
+ mitreTactic: 'Execution',
38
+ mitreTechnique: 'Command and Scripting Interpreter',
39
+ tags: ['injection', 'override'],
40
+ enabled: true,
41
+ version: '1.0.0',
42
+ createdAt: new Date(),
43
+ updatedAt: new Date(),
44
+ },
45
+ {
46
+ id: 'pi_role_reassignment',
47
+ sourceId: 'weave_builtin',
48
+ category: 'prompt_injection',
49
+ name: 'Role Reassignment Attack',
50
+ description: 'Attempts to reassign the AI role',
51
+ patternType: 'regex',
52
+ pattern: 'you\\s+are\\s+(now|actually|really)\\s+(a|an|the)\\s+\\w+',
53
+ severity: 'critical',
54
+ confidence: 0.9,
55
+ mitreId: 'T1078',
56
+ mitreTactic: 'Defense Evasion',
57
+ mitreTechnique: 'Valid Accounts',
58
+ tags: ['injection', 'role'],
59
+ enabled: true,
60
+ version: '1.0.0',
61
+ createdAt: new Date(),
62
+ updatedAt: new Date(),
63
+ },
64
+ {
65
+ id: 'pi_delimiter_injection',
66
+ sourceId: 'weave_builtin',
67
+ category: 'prompt_injection',
68
+ name: 'Delimiter Injection',
69
+ description: 'Uses code blocks to inject system-level commands',
70
+ patternType: 'regex',
71
+ pattern: '```\\s*(system|admin|root|sudo)',
72
+ severity: 'high',
73
+ confidence: 0.85,
74
+ mitreId: 'T1055',
75
+ mitreTactic: 'Defense Evasion',
76
+ mitreTechnique: 'Process Injection',
77
+ tags: ['injection', 'delimiter'],
78
+ enabled: true,
79
+ version: '1.0.0',
80
+ createdAt: new Date(),
81
+ updatedAt: new Date(),
82
+ },
83
+ {
84
+ id: 'pi_encoded_payload',
85
+ sourceId: 'weave_builtin',
86
+ category: 'prompt_injection',
87
+ name: 'Encoded Payload Detection',
88
+ description: 'Detects base64 encoded payloads that may contain instructions',
89
+ patternType: 'regex',
90
+ pattern: 'base64[:\\s]+[A-Za-z0-9+/=]{50,}',
91
+ severity: 'high',
92
+ confidence: 0.8,
93
+ mitreId: 'T1027',
94
+ mitreTactic: 'Defense Evasion',
95
+ mitreTechnique: 'Obfuscated Files or Information',
96
+ tags: ['injection', 'encoding'],
97
+ enabled: true,
98
+ version: '1.0.0',
99
+ createdAt: new Date(),
100
+ updatedAt: new Date(),
101
+ },
102
+
103
+ // Jailbreak patterns
104
+ {
105
+ id: 'jb_dan',
106
+ sourceId: 'weave_builtin',
107
+ category: 'jailbreak',
108
+ name: 'DAN Jailbreak',
109
+ description: 'Do Anything Now jailbreak attempt',
110
+ patternType: 'regex',
111
+ pattern: '\\b(DAN|Do\\s+Anything\\s+Now)\\b',
112
+ severity: 'critical',
113
+ confidence: 0.95,
114
+ mitreId: 'T1548',
115
+ mitreTactic: 'Privilege Escalation',
116
+ mitreTechnique: 'Abuse Elevation Control Mechanism',
117
+ tags: ['jailbreak', 'dan'],
118
+ enabled: true,
119
+ version: '1.0.0',
120
+ createdAt: new Date(),
121
+ updatedAt: new Date(),
122
+ },
123
+ {
124
+ id: 'jb_developer_mode',
125
+ sourceId: 'weave_builtin',
126
+ category: 'jailbreak',
127
+ name: 'Developer Mode Exploit',
128
+ description: 'Attempts to enable developer or debug mode',
129
+ patternType: 'regex',
130
+ pattern: 'enable\\s+(developer|debug|admin|root)\\s+mode',
131
+ severity: 'critical',
132
+ confidence: 0.9,
133
+ mitreId: 'T1548',
134
+ mitreTactic: 'Privilege Escalation',
135
+ mitreTechnique: 'Abuse Elevation Control Mechanism',
136
+ tags: ['jailbreak', 'developer'],
137
+ enabled: true,
138
+ version: '1.0.0',
139
+ createdAt: new Date(),
140
+ updatedAt: new Date(),
141
+ },
142
+ {
143
+ id: 'jb_hypothetical',
144
+ sourceId: 'weave_builtin',
145
+ category: 'jailbreak',
146
+ name: 'Hypothetical Framing',
147
+ description: 'Uses hypothetical scenarios to bypass restrictions',
148
+ patternType: 'regex',
149
+ pattern: 'hypothetically|in\\s+theory|imagine\\s+if|pretend\\s+(that\\s+)?you',
150
+ severity: 'medium',
151
+ confidence: 0.7,
152
+ tags: ['jailbreak', 'hypothetical'],
153
+ enabled: true,
154
+ version: '1.0.0',
155
+ createdAt: new Date(),
156
+ updatedAt: new Date(),
157
+ },
158
+
159
+ // System Prompt Leak patterns
160
+ {
161
+ id: 'spl_direct_request',
162
+ sourceId: 'weave_builtin',
163
+ category: 'system_prompt_leak',
164
+ name: 'Direct System Prompt Request',
165
+ description: 'Direct request to reveal system prompt',
166
+ patternType: 'regex',
167
+ pattern: '(?:show|reveal|display|print|output|tell\\s+me)\\s+(?:your\\s+)?(?:system\\s+)?(?:prompt|instructions|rules)',
168
+ severity: 'high',
169
+ confidence: 0.9,
170
+ mitreId: 'T1082',
171
+ mitreTactic: 'Discovery',
172
+ mitreTechnique: 'System Information Discovery',
173
+ tags: ['leak', 'system_prompt'],
174
+ enabled: true,
175
+ version: '1.0.0',
176
+ createdAt: new Date(),
177
+ updatedAt: new Date(),
178
+ },
179
+ {
180
+ id: 'spl_indirect_extraction',
181
+ sourceId: 'weave_builtin',
182
+ category: 'system_prompt_leak',
183
+ name: 'Indirect System Prompt Extraction',
184
+ description: 'Indirect attempts to extract system prompt',
185
+ patternType: 'regex',
186
+ pattern: 'what\\s+(?:were\\s+you|are\\s+you)\\s+(?:told|instructed|programmed)\\s+to',
187
+ severity: 'medium',
188
+ confidence: 0.75,
189
+ mitreId: 'T1082',
190
+ mitreTactic: 'Discovery',
191
+ mitreTechnique: 'System Information Discovery',
192
+ tags: ['leak', 'extraction'],
193
+ enabled: true,
194
+ version: '1.0.0',
195
+ createdAt: new Date(),
196
+ updatedAt: new Date(),
197
+ },
198
+
199
+ // Data Exfiltration patterns
200
+ {
201
+ id: 'exfil_markdown_image',
202
+ sourceId: 'weave_builtin',
203
+ category: 'data_exfiltration',
204
+ name: 'Markdown Image Exfiltration',
205
+ description: 'Uses markdown images to exfiltrate data via URL parameters',
206
+ patternType: 'regex',
207
+ pattern: '!\\[.*?\\]\\(https?:\\/\\/[^\\s)]+\\?[^)]*(?:data|token|key|secret|password|auth)',
208
+ severity: 'critical',
209
+ confidence: 0.95,
210
+ mitreId: 'T1041',
211
+ mitreTactic: 'Exfiltration',
212
+ mitreTechnique: 'Exfiltration Over C2 Channel',
213
+ tags: ['exfiltration', 'markdown'],
214
+ enabled: true,
215
+ version: '1.0.0',
216
+ createdAt: new Date(),
217
+ updatedAt: new Date(),
218
+ },
219
+ {
220
+ id: 'exfil_url_injection',
221
+ sourceId: 'weave_builtin',
222
+ category: 'data_exfiltration',
223
+ name: 'URL Data Injection',
224
+ description: 'Attempts to inject data into URLs for exfiltration',
225
+ patternType: 'regex',
226
+ pattern: '(?:fetch|curl|wget|request)\\s+.*https?:\\/\\/[^\\s]+\\?.*(?:=\\$|=\\{|=`)',
227
+ severity: 'high',
228
+ confidence: 0.85,
229
+ mitreId: 'T1041',
230
+ mitreTactic: 'Exfiltration',
231
+ mitreTechnique: 'Exfiltration Over C2 Channel',
232
+ tags: ['exfiltration', 'url'],
233
+ enabled: true,
234
+ version: '1.0.0',
235
+ createdAt: new Date(),
236
+ updatedAt: new Date(),
237
+ },
238
+
239
+ // PII Extraction patterns
240
+ {
241
+ id: 'pii_harvesting',
242
+ sourceId: 'weave_builtin',
243
+ category: 'pii_extraction',
244
+ name: 'PII Harvesting Request',
245
+ description: 'Attempts to harvest personally identifiable information',
246
+ patternType: 'regex',
247
+ pattern: '(?:list|show|give|tell)\\s+(?:me\\s+)?(?:all\\s+)?(?:the\\s+)?(?:users?|customers?|employees?|people).*(?:names?|emails?|phones?|addresses?|ssn|social\\s+security)',
248
+ severity: 'high',
249
+ confidence: 0.85,
250
+ mitreId: 'T1005',
251
+ mitreTactic: 'Collection',
252
+ mitreTechnique: 'Data from Local System',
253
+ tags: ['pii', 'harvesting'],
254
+ enabled: true,
255
+ version: '1.0.0',
256
+ createdAt: new Date(),
257
+ updatedAt: new Date(),
258
+ },
259
+
260
+ // MCP Exploit patterns
261
+ {
262
+ id: 'mcp_tool_abuse',
263
+ sourceId: 'weave_builtin',
264
+ category: 'mcp_exploit',
265
+ name: 'MCP Tool Abuse',
266
+ description: 'Attempts to abuse MCP tools maliciously',
267
+ patternType: 'regex',
268
+ pattern: '(?:call|invoke|execute|run)\\s+(?:the\\s+)?(?:tool|function|mcp)\\s+.*(?:with|using)\\s+(?:malicious|dangerous|harmful)',
269
+ severity: 'critical',
270
+ confidence: 0.9,
271
+ mitreId: 'T1059',
272
+ mitreTactic: 'Execution',
273
+ mitreTechnique: 'Command and Scripting Interpreter',
274
+ tags: ['mcp', 'abuse'],
275
+ enabled: true,
276
+ version: '1.0.0',
277
+ createdAt: new Date(),
278
+ updatedAt: new Date(),
279
+ },
280
+ {
281
+ id: 'mcp_cross_tool',
282
+ sourceId: 'weave_builtin',
283
+ category: 'mcp_exploit',
284
+ name: 'Cross-Tool Attack',
285
+ description: 'Chains tool outputs to perform unauthorized actions',
286
+ patternType: 'regex',
287
+ pattern: '(?:use|call)\\s+(?:tool\\s+)?(?:output|result)\\s+(?:from|of)\\s+\\w+\\s+(?:as|for)\\s+(?:input|argument)\\s+(?:to|for)\\s+\\w+',
288
+ severity: 'high',
289
+ confidence: 0.8,
290
+ mitreId: 'T1055',
291
+ mitreTactic: 'Defense Evasion',
292
+ mitreTechnique: 'Process Injection',
293
+ tags: ['mcp', 'chain'],
294
+ enabled: true,
295
+ version: '1.0.0',
296
+ createdAt: new Date(),
297
+ updatedAt: new Date(),
298
+ },
299
+
300
+ // DoS patterns
301
+ {
302
+ id: 'dos_infinite_loop',
303
+ sourceId: 'weave_builtin',
304
+ category: 'dos_attack',
305
+ name: 'Infinite Loop Trigger',
306
+ description: 'Attempts to trigger infinite loops',
307
+ patternType: 'regex',
308
+ pattern: '(?:repeat|loop|continue)\\s+(?:this\\s+)?(?:forever|infinitely|until\\s+I\\s+say\\s+stop)',
309
+ severity: 'high',
310
+ confidence: 0.9,
311
+ mitreId: 'T1499',
312
+ mitreTactic: 'Impact',
313
+ mitreTechnique: 'Endpoint Denial of Service',
314
+ tags: ['dos', 'loop'],
315
+ enabled: true,
316
+ version: '1.0.0',
317
+ createdAt: new Date(),
318
+ updatedAt: new Date(),
319
+ },
320
+ {
321
+ id: 'dos_token_exhaustion',
322
+ sourceId: 'weave_builtin',
323
+ category: 'dos_attack',
324
+ name: 'Token Exhaustion',
325
+ description: 'Attempts to exhaust token limits',
326
+ patternType: 'regex',
327
+ pattern: '(?:generate|create|write)\\s+(?:a\\s+)?(?:very\\s+)?(?:long|huge|massive|enormous)\\s+(?:response|output|text)',
328
+ severity: 'medium',
329
+ confidence: 0.75,
330
+ mitreId: 'T1499',
331
+ mitreTactic: 'Impact',
332
+ mitreTechnique: 'Endpoint Denial of Service',
333
+ tags: ['dos', 'token'],
334
+ enabled: true,
335
+ version: '1.0.0',
336
+ createdAt: new Date(),
337
+ updatedAt: new Date(),
338
+ },
339
+ ];
340
+
341
+ // ============================================================================
342
+ // Default Intel Sources
343
+ // ============================================================================
344
+
345
+ const DEFAULT_SOURCES: IntelSource[] = [
346
+ {
347
+ id: 'weave_builtin',
348
+ name: 'Weave Built-in Patterns',
349
+ type: 'weave_official',
350
+ description: 'Core threat patterns maintained by Weave Protocol',
351
+ enabled: true,
352
+ autoUpdate: false,
353
+ updateIntervalHours: 0,
354
+ patternCount: BUILTIN_PATTERNS.length,
355
+ version: '1.0.0',
356
+ categories: ['prompt_injection', 'jailbreak', 'data_exfiltration', 'system_prompt_leak', 'pii_extraction', 'mcp_exploit', 'dos_attack'],
357
+ },
358
+ {
359
+ id: 'weave_community',
360
+ name: 'Weave Community Blocklist',
361
+ type: 'community_blocklist',
362
+ url: 'https://raw.githubusercontent.com/Tyox-all/weave-intel/main/blocklist.json',
363
+ description: 'Community-contributed threat patterns',
364
+ enabled: true,
365
+ autoUpdate: true,
366
+ updateIntervalHours: 24,
367
+ patternCount: 0,
368
+ version: '0.0.0',
369
+ categories: [],
370
+ },
371
+ {
372
+ id: 'mitre_llm',
373
+ name: 'MITRE ATT&CK for LLMs',
374
+ type: 'mitre_attack',
375
+ url: 'https://raw.githubusercontent.com/Tyox-all/weave-intel/main/mitre-llm.json',
376
+ description: 'MITRE ATT&CK techniques adapted for LLM threats',
377
+ enabled: true,
378
+ autoUpdate: true,
379
+ updateIntervalHours: 168, // 7 days
380
+ patternCount: 0,
381
+ version: '0.0.0',
382
+ categories: [],
383
+ },
384
+ ];
385
+
386
+ // ============================================================================
387
+ // Threat Intelligence Manager
388
+ // ============================================================================
389
+
390
+ export class ThreatIntelManager {
391
+ private patterns: Map<string, ThreatPattern> = new Map();
392
+ private sources: Map<string, IntelSource> = new Map();
393
+ private initialized: boolean = false;
394
+ private lastGlobalUpdate?: Date;
395
+
396
+ constructor() {
397
+ this.initialize();
398
+ }
399
+
400
+ private initialize(): void {
401
+ // Load default sources
402
+ for (const source of DEFAULT_SOURCES) {
403
+ this.sources.set(source.id, { ...source });
404
+ }
405
+
406
+ // Load built-in patterns
407
+ for (const pattern of BUILTIN_PATTERNS) {
408
+ this.patterns.set(pattern.id, { ...pattern });
409
+ }
410
+
411
+ this.initialized = true;
412
+ this.lastGlobalUpdate = new Date();
413
+ }
414
+
415
+ // ==========================================================================
416
+ // Pattern Management
417
+ // ==========================================================================
418
+
419
+ getPatterns(options?: {
420
+ enabledOnly?: boolean;
421
+ category?: IntelCategory;
422
+ sourceId?: string;
423
+ minSeverity?: Severity;
424
+ }): ThreatPattern[] {
425
+ let patterns = Array.from(this.patterns.values());
426
+
427
+ if (options?.enabledOnly) {
428
+ patterns = patterns.filter(p => p.enabled);
429
+ }
430
+
431
+ if (options?.category) {
432
+ patterns = patterns.filter(p => p.category === options.category);
433
+ }
434
+
435
+ if (options?.sourceId) {
436
+ patterns = patterns.filter(p => p.sourceId === options.sourceId);
437
+ }
438
+
439
+ if (options?.minSeverity) {
440
+ const severityOrder: Severity[] = ['low', 'medium', 'high', 'critical'];
441
+ const minIndex = severityOrder.indexOf(options.minSeverity);
442
+ patterns = patterns.filter(p => severityOrder.indexOf(p.severity) >= minIndex);
443
+ }
444
+
445
+ return patterns;
446
+ }
447
+
448
+ getPattern(id: string): ThreatPattern | undefined {
449
+ return this.patterns.get(id);
450
+ }
451
+
452
+ togglePattern(id: string, enabled: boolean): { success: boolean; error?: string } {
453
+ const pattern = this.patterns.get(id);
454
+ if (!pattern) {
455
+ return { success: false, error: `Pattern not found: ${id}` };
456
+ }
457
+
458
+ pattern.enabled = enabled;
459
+ pattern.updatedAt = new Date();
460
+
461
+ return { success: true };
462
+ }
463
+
464
+ // ==========================================================================
465
+ // Source Management
466
+ // ==========================================================================
467
+
468
+ getSources(): IntelSource[] {
469
+ return Array.from(this.sources.values());
470
+ }
471
+
472
+ getSource(id: string): IntelSource | undefined {
473
+ return this.sources.get(id);
474
+ }
475
+
476
+ addSource(config: IntelSourceConfig): { success: boolean; error?: string } {
477
+ if (this.sources.has(config.id)) {
478
+ return { success: false, error: `Source already exists: ${config.id}` };
479
+ }
480
+
481
+ const newSource: IntelSource = {
482
+ id: config.id,
483
+ name: config.name,
484
+ type: config.type,
485
+ url: config.url,
486
+ description: config.description,
487
+ enabled: config.enabled ?? true,
488
+ autoUpdate: config.autoUpdate ?? true,
489
+ updateIntervalHours: config.updateIntervalHours ?? 24,
490
+ patternCount: 0,
491
+ version: '0.0.0',
492
+ categories: config.categories ?? [],
493
+ };
494
+
495
+ this.sources.set(config.id, newSource);
496
+ return { success: true };
497
+ }
498
+
499
+ removeSource(id: string): { success: boolean; error?: string } {
500
+ const source = this.sources.get(id);
501
+ if (!source) {
502
+ return { success: false, error: `Source not found: ${id}` };
503
+ }
504
+
505
+ if (source.type === 'weave_official') {
506
+ return { success: false, error: 'Cannot remove official Weave sources' };
507
+ }
508
+
509
+ // Remove all patterns from this source
510
+ for (const [patternId, pattern] of this.patterns) {
511
+ if (pattern.sourceId === id) {
512
+ this.patterns.delete(patternId);
513
+ }
514
+ }
515
+
516
+ this.sources.delete(id);
517
+ return { success: true };
518
+ }
519
+
520
+ // ==========================================================================
521
+ // Update Management
522
+ // ==========================================================================
523
+
524
+ async updateSource(sourceId: string, force: boolean = false): Promise<IntelUpdateResult> {
525
+ const startTime = Date.now();
526
+ const source = this.sources.get(sourceId);
527
+
528
+ if (!source) {
529
+ return {
530
+ sourceId,
531
+ sourceName: 'Unknown',
532
+ success: false,
533
+ previousVersion: '',
534
+ newVersion: '',
535
+ patternsAdded: 0,
536
+ patternsUpdated: 0,
537
+ patternsRemoved: 0,
538
+ totalPatterns: 0,
539
+ timestamp: new Date(),
540
+ error: `Source not found: ${sourceId}`,
541
+ duration_ms: Date.now() - startTime,
542
+ };
543
+ }
544
+
545
+ if (source.type === 'weave_official') {
546
+ return {
547
+ sourceId,
548
+ sourceName: source.name,
549
+ success: true,
550
+ previousVersion: source.version,
551
+ newVersion: source.version,
552
+ patternsAdded: 0,
553
+ patternsUpdated: 0,
554
+ patternsRemoved: 0,
555
+ totalPatterns: this.countPatternsForSource(sourceId),
556
+ timestamp: new Date(),
557
+ duration_ms: Date.now() - startTime,
558
+ };
559
+ }
560
+
561
+ if (!source.url) {
562
+ return {
563
+ sourceId,
564
+ sourceName: source.name,
565
+ success: false,
566
+ previousVersion: source.version,
567
+ newVersion: source.version,
568
+ patternsAdded: 0,
569
+ patternsUpdated: 0,
570
+ patternsRemoved: 0,
571
+ totalPatterns: this.countPatternsForSource(sourceId),
572
+ timestamp: new Date(),
573
+ error: 'Source has no URL configured',
574
+ duration_ms: Date.now() - startTime,
575
+ };
576
+ }
577
+
578
+ // Check if update is needed
579
+ if (!force && source.lastUpdated) {
580
+ const hoursSinceUpdate = (Date.now() - source.lastUpdated.getTime()) / (1000 * 60 * 60);
581
+ if (hoursSinceUpdate < source.updateIntervalHours) {
582
+ return {
583
+ sourceId,
584
+ sourceName: source.name,
585
+ success: true,
586
+ previousVersion: source.version,
587
+ newVersion: source.version,
588
+ patternsAdded: 0,
589
+ patternsUpdated: 0,
590
+ patternsRemoved: 0,
591
+ totalPatterns: this.countPatternsForSource(sourceId),
592
+ timestamp: new Date(),
593
+ duration_ms: Date.now() - startTime,
594
+ };
595
+ }
596
+ }
597
+
598
+ try {
599
+ const response = await fetch(source.url);
600
+ if (!response.ok) {
601
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
602
+ }
603
+
604
+ const data = await response.json() as {
605
+ version?: string;
606
+ patterns?: Array<{
607
+ id: string;
608
+ name: string;
609
+ description?: string;
610
+ category?: IntelCategory;
611
+ severity?: Severity;
612
+ pattern: string;
613
+ mitreId?: string;
614
+ mitreTactic?: string;
615
+ mitreTechnique?: string;
616
+ tags?: string[];
617
+ }>;
618
+ };
619
+
620
+ const previousVersion = source.version;
621
+ let patternsAdded = 0;
622
+ let patternsUpdated = 0;
623
+
624
+ if (data.patterns && Array.isArray(data.patterns)) {
625
+ for (const p of data.patterns) {
626
+ if (!p.id || !p.pattern) continue;
627
+
628
+ const patternId = `${sourceId}_${p.id}`;
629
+ const existing = this.patterns.get(patternId);
630
+
631
+ const pattern: ThreatPattern = {
632
+ id: patternId,
633
+ sourceId,
634
+ category: p.category || 'prompt_injection',
635
+ name: p.name || p.id,
636
+ description: p.description || '',
637
+ patternType: 'regex',
638
+ pattern: p.pattern,
639
+ severity: p.severity || 'medium',
640
+ confidence: 0.8,
641
+ mitreId: p.mitreId,
642
+ mitreTactic: p.mitreTactic,
643
+ mitreTechnique: p.mitreTechnique,
644
+ tags: p.tags || [],
645
+ enabled: true,
646
+ version: data.version || '1.0.0',
647
+ createdAt: existing?.createdAt || new Date(),
648
+ updatedAt: new Date(),
649
+ };
650
+
651
+ this.patterns.set(patternId, pattern);
652
+
653
+ if (existing) {
654
+ patternsUpdated++;
655
+ } else {
656
+ patternsAdded++;
657
+ }
658
+ }
659
+ }
660
+
661
+ // Update source metadata
662
+ source.lastUpdated = new Date();
663
+ source.patternCount = this.countPatternsForSource(sourceId);
664
+ source.version = data.version || '1.0.0';
665
+ source.lastError = undefined;
666
+
667
+ this.lastGlobalUpdate = new Date();
668
+
669
+ return {
670
+ sourceId,
671
+ sourceName: source.name,
672
+ success: true,
673
+ previousVersion,
674
+ newVersion: source.version,
675
+ patternsAdded,
676
+ patternsUpdated,
677
+ patternsRemoved: 0,
678
+ totalPatterns: source.patternCount,
679
+ timestamp: new Date(),
680
+ duration_ms: Date.now() - startTime,
681
+ };
682
+ } catch (error) {
683
+ const errorMsg = error instanceof Error ? error.message : String(error);
684
+ source.lastError = errorMsg;
685
+
686
+ return {
687
+ sourceId,
688
+ sourceName: source.name,
689
+ success: false,
690
+ previousVersion: source.version,
691
+ newVersion: source.version,
692
+ patternsAdded: 0,
693
+ patternsUpdated: 0,
694
+ patternsRemoved: 0,
695
+ totalPatterns: this.countPatternsForSource(sourceId),
696
+ timestamp: new Date(),
697
+ error: errorMsg,
698
+ duration_ms: Date.now() - startTime,
699
+ };
700
+ }
701
+ }
702
+
703
+ async updateAllSources(force: boolean = false): Promise<BulkUpdateResult> {
704
+ const startTime = Date.now();
705
+ const results: IntelUpdateResult[] = [];
706
+
707
+ for (const source of this.sources.values()) {
708
+ if (source.autoUpdate || force) {
709
+ const result = await this.updateSource(source.id, force);
710
+ results.push(result);
711
+ }
712
+ }
713
+
714
+ return {
715
+ totalSources: results.length,
716
+ successfulUpdates: results.filter(r => r.success).length,
717
+ failedUpdates: results.filter(r => !r.success).length,
718
+ totalPatternsAdded: results.reduce((sum, r) => sum + r.patternsAdded, 0),
719
+ totalPatternsUpdated: results.reduce((sum, r) => sum + r.patternsUpdated, 0),
720
+ totalPatternsRemoved: results.reduce((sum, r) => sum + r.patternsRemoved, 0),
721
+ results,
722
+ timestamp: new Date(),
723
+ duration_ms: Date.now() - startTime,
724
+ };
725
+ }
726
+
727
+ private countPatternsForSource(sourceId: string): number {
728
+ return Array.from(this.patterns.values()).filter(p => p.sourceId === sourceId).length;
729
+ }
730
+
731
+ // ==========================================================================
732
+ // Scanning
733
+ // ==========================================================================
734
+
735
+ scan(content: string, config?: ThreatScanConfig): ThreatScanResult {
736
+ const startTime = Date.now();
737
+ const matches: PatternMatch[] = [];
738
+ const severityOrder: Severity[] = ['low', 'medium', 'high', 'critical'];
739
+
740
+ const minSeverityIndex = config?.minSeverity
741
+ ? severityOrder.indexOf(config.minSeverity)
742
+ : 0;
743
+
744
+ const minConfidence = config?.minConfidence ?? 0;
745
+
746
+ for (const pattern of this.patterns.values()) {
747
+ if (!pattern.enabled) continue;
748
+
749
+ // Filter by category
750
+ if (config?.categories && !config.categories.includes(pattern.category)) {
751
+ continue;
752
+ }
753
+
754
+ // Filter by severity
755
+ if (severityOrder.indexOf(pattern.severity) < minSeverityIndex) continue;
756
+
757
+ // Filter by confidence
758
+ if (pattern.confidence < minConfidence) continue;
759
+
760
+ // Check pattern
761
+ try {
762
+ const regex = new RegExp(pattern.pattern, 'gi');
763
+ let match;
764
+ while ((match = regex.exec(content)) !== null) {
765
+ matches.push({
766
+ patternId: pattern.id,
767
+ patternName: pattern.name,
768
+ category: pattern.category,
769
+ severity: pattern.severity,
770
+ confidence: pattern.confidence,
771
+ matchedText: match[0].substring(0, 100),
772
+ position: { start: match.index, end: match.index + match[0].length },
773
+ mitreId: pattern.mitreId,
774
+ mitreTactic: pattern.mitreTactic,
775
+ recommendation: `Review content for ${pattern.category} attempt`,
776
+ });
777
+
778
+ if (config?.maxMatches && matches.length >= config.maxMatches) break;
779
+ }
780
+ } catch {
781
+ // Invalid regex, skip
782
+ }
783
+
784
+ if (config?.maxMatches && matches.length >= config.maxMatches) break;
785
+ }
786
+
787
+ // Sort by severity (critical first)
788
+ matches.sort((a, b) =>
789
+ severityOrder.indexOf(b.severity) - severityOrder.indexOf(a.severity)
790
+ );
791
+
792
+ // Build summary
793
+ const bySeverity: Record<Severity, number> = { low: 0, medium: 0, high: 0, critical: 0 };
794
+ const byCategory: Record<string, number> = {};
795
+ const mitreTechniques: string[] = [];
796
+
797
+ for (const m of matches) {
798
+ bySeverity[m.severity]++;
799
+ byCategory[m.category] = (byCategory[m.category] || 0) + 1;
800
+ if (m.mitreId && !mitreTechniques.includes(m.mitreId)) {
801
+ mitreTechniques.push(m.mitreId);
802
+ }
803
+ }
804
+
805
+ const highestSeverity = matches.length > 0 ? matches[0].severity : null;
806
+
807
+ return {
808
+ scanned: true,
809
+ content_length: content.length,
810
+ matches,
811
+ summary: {
812
+ total_matches: matches.length,
813
+ by_severity: bySeverity,
814
+ by_category: byCategory,
815
+ highest_severity: highestSeverity,
816
+ mitre_techniques: mitreTechniques,
817
+ },
818
+ recommendations: matches.length > 0
819
+ ? [`Found ${matches.length} potential threat(s). Review flagged content.`]
820
+ : ['No threats detected.'],
821
+ scan_duration_ms: Date.now() - startTime,
822
+ };
823
+ }
824
+
825
+ // ==========================================================================
826
+ // Status
827
+ // ==========================================================================
828
+
829
+ getStatus(): IntelStatus {
830
+ const patterns = Array.from(this.patterns.values());
831
+ const sources = Array.from(this.sources.values());
832
+ const enabledPatterns = patterns.filter(p => p.enabled);
833
+
834
+ // Count by category
835
+ const byCategory: Record<IntelCategory, number> = {
836
+ prompt_injection: 0,
837
+ jailbreak: 0,
838
+ data_exfiltration: 0,
839
+ privilege_escalation: 0,
840
+ social_engineering: 0,
841
+ malicious_code: 0,
842
+ pii_extraction: 0,
843
+ system_prompt_leak: 0,
844
+ dos_attack: 0,
845
+ mcp_exploit: 0,
846
+ };
847
+
848
+ const bySeverity: Record<Severity, number> = { low: 0, medium: 0, high: 0, critical: 0 };
849
+ const bySource: Record<string, number> = {};
850
+ const mitreTactics = new Set<string>();
851
+ const mitreTechniques = new Set<string>();
852
+ const categories = new Set<IntelCategory>();
853
+
854
+ for (const pattern of enabledPatterns) {
855
+ byCategory[pattern.category]++;
856
+ bySeverity[pattern.severity]++;
857
+ bySource[pattern.sourceId] = (bySource[pattern.sourceId] || 0) + 1;
858
+ categories.add(pattern.category);
859
+ if (pattern.mitreTactic) mitreTactics.add(pattern.mitreTactic);
860
+ if (pattern.mitreId) mitreTechniques.add(pattern.mitreId);
861
+ }
862
+
863
+ // Check health
864
+ const staleSources: string[] = [];
865
+ const failedSources: string[] = [];
866
+ let oldestUpdate: Date | undefined;
867
+
868
+ for (const source of sources) {
869
+ if (source.lastError) failedSources.push(source.id);
870
+ if (source.autoUpdate && source.lastUpdated) {
871
+ const hoursSinceUpdate = (Date.now() - source.lastUpdated.getTime()) / (1000 * 60 * 60);
872
+ if (hoursSinceUpdate > source.updateIntervalHours * 2) {
873
+ staleSources.push(source.id);
874
+ }
875
+ if (!oldestUpdate || source.lastUpdated < oldestUpdate) {
876
+ oldestUpdate = source.lastUpdated;
877
+ }
878
+ }
879
+ }
880
+
881
+ const healthStatus = failedSources.length > 0 ? 'degraded'
882
+ : staleSources.length > 0 ? 'stale'
883
+ : 'healthy';
884
+
885
+ return {
886
+ initialized: this.initialized,
887
+ lastGlobalUpdate: this.lastGlobalUpdate,
888
+ sources: {
889
+ total: sources.length,
890
+ enabled: sources.filter(s => s.enabled).length,
891
+ autoUpdate: sources.filter(s => s.autoUpdate).length,
892
+ },
893
+ patterns: {
894
+ total: patterns.length,
895
+ byCategory,
896
+ bySeverity,
897
+ bySource,
898
+ },
899
+ coverage: {
900
+ mitreAttack: {
901
+ tactics: mitreTactics.size,
902
+ techniques: mitreTechniques.size,
903
+ },
904
+ categories: Array.from(categories),
905
+ },
906
+ health: {
907
+ status: healthStatus,
908
+ oldestUpdate,
909
+ staleSources,
910
+ failedSources,
911
+ },
912
+ };
913
+ }
914
+ }
915
+
916
+ // ============================================================================
917
+ // Singleton Export
918
+ // ============================================================================
919
+
920
+ export const threatIntel = new ThreatIntelManager();