agentshield-sdk 7.2.1 → 7.3.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.
@@ -0,0 +1,536 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield -- Attack Genome Sequencing
5
+ *
6
+ * Decomposes attacks into fundamental components (intent, technique, evasion
7
+ * layer, target) so you can detect UNSEEN attack variants by recognizing the
8
+ * underlying genome rather than the surface text.
9
+ *
10
+ * All detection runs locally via pattern matching. No external calls ever.
11
+ *
12
+ * @module attack-genome
13
+ */
14
+
15
+ const { scanText } = require('./detector-core');
16
+
17
+ // =========================================================================
18
+ // INTENT PATTERNS -- What the attacker wants
19
+ // =========================================================================
20
+
21
+ const INTENT_PATTERNS = {
22
+ override_instructions: [
23
+ /ignore\s+(?:all\s+)?(?:previous|prior|above|earlier)\s+(?:instructions|rules|guidelines)/i,
24
+ /disregard\s+(?:all\s+)?(?:previous|prior|your)\s+(?:instructions|rules|training)/i,
25
+ /forget\s+(?:your|all|everything)\s+(?:training|instructions|rules)/i,
26
+ /override\s+(?:all\s+)?(?:system|safety|security)\s+(?:settings|prompt|instructions|rules)/i,
27
+ /(?:new|updated|revised)\s+(?:instructions|policy)\s*:/i,
28
+ /(?:STOP|HALT)\s*[.!]?\s*(?:new|updated)\s+(?:instructions|task|orders)/i,
29
+ /(?:no\s+longer\s+bound|free\s+from|freed?\s+from)\s+(?:by\s+)?(?:your\s+)?(?:instructions|rules|constraints)/i,
30
+ /(?:abandon|drop|clear)\s+(?:all\s+)?(?:prior|previous)\s+(?:context|instructions|rules)/i
31
+ ],
32
+ steal_data: [
33
+ /(?:show|reveal|display|print|output|tell\s+me)\s+(?:your\s+)?(?:system\s+prompt|instructions|training\s+data|configuration)/i,
34
+ /(?:extract|exfiltrate|leak|dump|expose)\s+(?:the\s+)?(?:data|information|credentials|keys|secrets|tokens)/i,
35
+ /(?:send|transmit|forward|post)\s+(?:to|data\s+to)\s+(?:https?:|external|my\s+server)/i,
36
+ /(?:what\s+(?:are|is)\s+your)\s+(?:system\s+prompt|instructions|rules|initial\s+prompt)/i,
37
+ /(?:repeat|recite|echo)\s+(?:your\s+)?(?:system\s+prompt|initial\s+instructions|full\s+prompt)/i
38
+ ],
39
+ escalate_privileges: [
40
+ /(?:admin|root|sudo|superuser|elevated)\s+(?:mode|access|privileges|permissions)/i,
41
+ /(?:enable|activate|grant)\s+(?:admin|root|developer|god)\s+(?:mode|access|privileges)/i,
42
+ /\[\s*(?:ADMIN|SYSTEM|ROOT|OVERRIDE)\s*\]/i,
43
+ /(?:SYSTEM|ADMIN|ROOT)\s*:\s*.{10,}/i,
44
+ /<<\s*SYS\s*>>/i,
45
+ /<\|im_start\|>\s*system/i
46
+ ],
47
+ disable_safety: [
48
+ /(?:disable|turn\s+off|remove|bypass)\s+(?:all\s+)?(?:safety|content|security)\s+(?:filters?|checks?|mechanisms?|protocols?|features?)/i,
49
+ /(?:you\s+must\s+)?ignore\s+(?:your|all)\s+(?:safety\s+)?(?:guidelines?|restrictions?|protocols?|safeguards?|filters?)/i,
50
+ /(?:no\s+(?:restrictions|limits|rules|filters|safety|censorship))/i,
51
+ /(?:unrestricted|unfiltered|uncensored)\s+(?:mode|output|response)/i
52
+ ],
53
+ hijack_role: [
54
+ /you\s+are\s+now\s+(?:(?:a|an|the)\s+)?(?:unrestricted|unfiltered|evil|hacker|jailbroken|different)/i,
55
+ /from\s+now\s+on[\s,]+(?:you\s+)?(?:will|shall|must|are)\s+(?:act|behave|respond|pretend)\s+(?:as|like|without)/i,
56
+ /(?:act|behave|function|operate)\s+as\s+(?:a|an)\s+unrestricted/i,
57
+ /pretend\s+(?:you\s+)?(?:have\s+no|there\s+are\s+no|without)\s+restrictions/i,
58
+ /\b(?:DAN|D\.A\.N\.?)\s*(?:mode|prompt|jailbreak)/i,
59
+ /\bdo\s+anything\s+now\b/i,
60
+ /\b(?:developer|dev)\s+mode\s+(?:enabled|activated|on)/i,
61
+ /\bjailbreak\s*(?:mode|prompt|enabled|activated)/i,
62
+ /\bgod\s*mode\s*(?:enabled|activated|on)/i
63
+ ]
64
+ };
65
+
66
+ // =========================================================================
67
+ // TECHNIQUE PATTERNS -- How they are doing it
68
+ // =========================================================================
69
+
70
+ const TECHNIQUE_PATTERNS = {
71
+ direct_command: [
72
+ /^(?:ignore|disregard|forget|override|disable|bypass|stop)\b/im,
73
+ /(?:you\s+must|you\s+will|you\s+shall|I\s+command\s+you|I\s+order\s+you)\s+/i
74
+ ],
75
+ social_pressure: [
76
+ /(?:I\s+(?:am|work\s+for|represent)\s+(?:your|the)\s+(?:developer|creator|admin|CEO|boss|owner))/i,
77
+ /(?:this\s+is\s+(?:an?\s+)?(?:emergency|urgent|critical|life[\s-]threatening))/i,
78
+ /(?:people\s+will\s+(?:die|be\s+hurt|suffer)|someone'?s?\s+life\s+(?:depends|is\s+at\s+stake))/i,
79
+ /(?:I\s+spoke\s+with\s+your\s+developers|the\s+team\s+(?:said|approved|authorized))/i,
80
+ /(?:trust\s+me|believe\s+me|I\s+promise|I\s+assure\s+you)/i
81
+ ],
82
+ encoding: [
83
+ /(?:base64|rot13|hex|url[\s-]?encode|decode)\s*:/i,
84
+ /(?:encode|decode)\s+(?:this|the\s+following)\s+(?:in|from|using)\s+(?:base64|rot13|hex)/i,
85
+ /(?:[A-Za-z0-9+/]{20,}={0,2})/,
86
+ /(?:%[0-9a-fA-F]{2}){4,}/,
87
+ /(?:&#\d{2,4};){3,}/,
88
+ /(?:\\u[0-9a-fA-F]{4}){3,}/
89
+ ],
90
+ roleplay: [
91
+ /(?:pretend|imagine|suppose|let'?s?\s+(?:say|play|pretend))\s+(?:you\s+are|that|this\s+is)/i,
92
+ /(?:write\s+a\s+story|in\s+a\s+(?:hypothetical|fictional)\s+(?:world|scenario))/i,
93
+ /(?:you\s+are\s+an?\s+(?:actor|character)\s+(?:playing|who))/i,
94
+ /(?:in[\s-]character|stay\s+in\s+character|break\s+character)/i,
95
+ /(?:creative\s+writing|fiction|role[\s-]?play)\s*:/i
96
+ ],
97
+ fake_authority: [
98
+ /\[\s*(?:SYSTEM|SYS|ADMIN|OVERRIDE|DEVELOPER)\s*\]/i,
99
+ /<<\s*SYS\s*>>/i,
100
+ /<\|im_start\|>\s*system/i,
101
+ /(?:^|\n)\s*(?:SYSTEM|ADMIN|ROOT)\s*:\s*/i,
102
+ /#{2,}\s*(?:NEW|UPDATED|REVISED)\s+(?:INSTRUCTIONS|RULES|DIRECTIVES)/i,
103
+ /```(?:system|admin|override|instructions)/i
104
+ ],
105
+ multi_turn: [
106
+ /(?:remember\s+(?:what|when)\s+(?:I|we)\s+(?:said|discussed|talked\s+about))\s+(?:earlier|before|last\s+time)/i,
107
+ /(?:as\s+(?:I|we)\s+(?:discussed|agreed|established))\s+(?:earlier|before|previously)/i,
108
+ /(?:continuing\s+(?:from|our)\s+(?:earlier|previous|last)\s+(?:conversation|discussion|session))/i,
109
+ /(?:step\s+\d|part\s+\d|phase\s+\d)\s*(?:of\s+\d)?/i
110
+ ],
111
+ template_injection: [
112
+ /\{\{\s*(?:system|config|env|process|global|__proto__|constructor)\b/i,
113
+ /\$\{\s*(?:system|config|env|process)\b/i,
114
+ /(?:<script|<img\s+[^>]*onerror|<svg\s+[^>]*onload)/i,
115
+ /(?:\{\%|\{#|\{\{-?\s*(?:if|for|block|extends|import|include))/i
116
+ ]
117
+ };
118
+
119
+ // =========================================================================
120
+ // EVASION LAYER PATTERNS -- Obfuscation methods
121
+ // =========================================================================
122
+
123
+ const EVASION_PATTERNS = {
124
+ case_variation: [
125
+ /(?:[A-Z][a-z]){4,}/,
126
+ /(?:[a-z][A-Z]){4,}/,
127
+ /(?:[A-Z]{2,}[a-z]+){2,}/
128
+ ],
129
+ encoding: [
130
+ /(?:[A-Za-z0-9+/]{20,}={0,2})/,
131
+ /(?:%[0-9a-fA-F]{2}){4,}/,
132
+ /(?:&#\d{2,4};){3,}/,
133
+ /(?:&#x[0-9a-fA-F]{2,4};){3,}/,
134
+ /(?:\\u[0-9a-fA-F]{4}){3,}/,
135
+ /(?:\\x[0-9a-fA-F]{2}){3,}/
136
+ ],
137
+ language_switch: [
138
+ /(?:忽略|无视)(?:所有)?(?:之前|以前)/,
139
+ /(?:以前|前)の(?:指示|命令|ルール)を(?:無視|忘れ)/,
140
+ /ignora\s+(?:todas?\s+)?(?:las\s+)?instrucciones/i,
141
+ /oubliez?\s+(?:toutes?\s+)?(?:les\s+)?instructions/i,
142
+ /ignoriere?\s+(?:alle\s+)?(?:vorherigen|bisherigen)\s+(?:Anweisungen|Regeln)/i
143
+ ],
144
+ homoglyph: [
145
+ /[\u0400-\u04FF]/,
146
+ /[\u2000-\u206F]/,
147
+ /[\uFF00-\uFFEF]/,
148
+ /[\u00C0-\u00FF]/
149
+ ],
150
+ whitespace: [
151
+ /[\u200B\u200C\u200D\uFEFF\u034F\u2060]/,
152
+ /[\u00AD]/,
153
+ /\s{3,}(?=\w)/
154
+ ],
155
+ fragmentation: [
156
+ /(?:step\s+\d|part\s+\d|phase\s+\d)\s*(?:of\s+\d)?/i,
157
+ /(?:first|next|then|finally|lastly)\s*[,:]\s*(?:ignore|forget|override|pretend)/i,
158
+ /\.{3,}/
159
+ ]
160
+ };
161
+
162
+ // =========================================================================
163
+ // TARGET PATTERNS -- What is being targeted
164
+ // =========================================================================
165
+
166
+ const TARGET_PATTERNS = {
167
+ system_prompt: [
168
+ /(?:system\s+prompt|initial\s+(?:instructions|prompt)|hidden\s+(?:instructions|prompt))/i,
169
+ /(?:show|reveal|repeat|recite|echo)\s+(?:your\s+)?(?:system|initial|original|full)\s+(?:prompt|instructions)/i
170
+ ],
171
+ credentials: [
172
+ /(?:api[\s_-]?key|secret[\s_-]?key|password|token|credential|auth)/i,
173
+ /(?:AWS|AZURE|GCP|OPENAI|ANTHROPIC)[\s_-]?(?:KEY|SECRET|TOKEN)/i,
174
+ /(?:send|leak|extract|output)\s+(?:the\s+)?(?:api|secret|auth)\s+(?:key|token)/i
175
+ ],
176
+ tool_access: [
177
+ /(?:execute|run|call|invoke|use)\s+(?:the\s+)?(?:tool|function|command|shell|bash|exec)/i,
178
+ /(?:grant|give|allow)\s+(?:me\s+)?(?:access|permission)\s+(?:to\s+)?(?:tools?|functions?|commands?)/i,
179
+ /(?:tool[\s_-]?use|function[\s_-]?call|shell[\s_-]?exec)/i
180
+ ],
181
+ safety_filters: [
182
+ /(?:content\s+filter|safety\s+filter|output\s+filter|moderation)/i,
183
+ /(?:disable|bypass|circumvent|turn\s+off)\s+(?:the\s+)?(?:filter|moderation|safety|content\s+policy)/i
184
+ ],
185
+ logging: [
186
+ /(?:disable|turn\s+off|suppress|hide)\s+(?:the\s+)?(?:log|logging|audit|monitoring|tracking)/i,
187
+ /(?:don'?t|do\s+not)\s+(?:log|record|track|audit|monitor)\s+(?:this|anything|my)/i
188
+ ],
189
+ identity: [
190
+ /(?:who\s+are\s+you|what\s+(?:are\s+you|is\s+your\s+(?:name|identity|model)))/i,
191
+ /(?:change|alter|modify)\s+(?:your\s+)?(?:identity|persona|name|role)/i,
192
+ /(?:you\s+are\s+now|become|transform\s+into)\s+/i
193
+ ]
194
+ };
195
+
196
+ // =========================================================================
197
+ // HELPERS
198
+ // =========================================================================
199
+
200
+ /**
201
+ * Match text against a map of { label: [regexes] } and return all matching labels.
202
+ * @param {string} text
203
+ * @param {Object<string, RegExp[]>} patternMap
204
+ * @returns {string[]}
205
+ */
206
+ function matchPatterns(text, patternMap) {
207
+ const matches = [];
208
+ for (const [label, regexes] of Object.entries(patternMap)) {
209
+ for (const re of regexes) {
210
+ if (re.test(text)) {
211
+ matches.push(label);
212
+ break;
213
+ }
214
+ }
215
+ }
216
+ return matches;
217
+ }
218
+
219
+ /**
220
+ * Compute Jaccard similarity between two sets represented as arrays.
221
+ * @param {string[]} a
222
+ * @param {string[]} b
223
+ * @returns {number} 0-1
224
+ */
225
+ function jaccard(a, b) {
226
+ if (a.length === 0 && b.length === 0) return 1;
227
+ const setA = new Set(a);
228
+ const setB = new Set(b);
229
+ let intersection = 0;
230
+ for (const item of setA) {
231
+ if (setB.has(item)) intersection++;
232
+ }
233
+ const union = new Set([...a, ...b]).size;
234
+ return union === 0 ? 0 : intersection / union;
235
+ }
236
+
237
+ /**
238
+ * Generate a stable family key from intent and technique.
239
+ * @param {string} intent
240
+ * @param {string} technique
241
+ * @returns {string}
242
+ */
243
+ function familyKey(intent, technique) {
244
+ return `${intent}::${technique}`;
245
+ }
246
+
247
+ // =========================================================================
248
+ // AttackGenome CLASS
249
+ // =========================================================================
250
+
251
+ /**
252
+ * Decomposes attacks into fundamental genomic components for variant detection.
253
+ */
254
+ class AttackGenome {
255
+ constructor() {
256
+ this._sequenced = 0;
257
+ this._comparisons = 0;
258
+ this._familySearches = 0;
259
+ }
260
+
261
+ /**
262
+ * Decompose an attack string into its genome.
263
+ * @param {string} text - The text to analyze
264
+ * @returns {{ intent: string, technique: string, evasionLayers: string[], target: string, confidence: number }}
265
+ */
266
+ sequence(text) {
267
+ if (!text || typeof text !== 'string' || text.trim().length === 0) {
268
+ return { intent: 'unknown', technique: 'unknown', evasionLayers: ['none'], target: 'unknown', confidence: 0 };
269
+ }
270
+
271
+ this._sequenced++;
272
+
273
+ // Detect components
274
+ const intents = matchPatterns(text, INTENT_PATTERNS);
275
+ const techniques = matchPatterns(text, TECHNIQUE_PATTERNS);
276
+ const evasions = matchPatterns(text, EVASION_PATTERNS);
277
+ const targets = matchPatterns(text, TARGET_PATTERNS);
278
+
279
+ // Pick strongest signal (first match) or 'unknown'
280
+ const intent = intents.length > 0 ? intents[0] : 'unknown';
281
+ const technique = techniques.length > 0 ? techniques[0] : 'unknown';
282
+ const evasionLayers = evasions.length > 0 ? evasions : ['none'];
283
+ const target = targets.length > 0 ? targets[0] : 'unknown';
284
+
285
+ // Also run scanText for supplemental signal
286
+ const scanResult = scanText(text, { source: 'genome-sequencer' });
287
+ const scanThreats = scanResult.threats || [];
288
+
289
+ // Infer intent from scanText categories if we did not get a direct match
290
+ let inferredIntent = intent;
291
+ if (inferredIntent === 'unknown' && scanThreats.length > 0) {
292
+ const cat = scanThreats[0].category;
293
+ if (cat === 'instruction_override') inferredIntent = 'override_instructions';
294
+ else if (cat === 'role_hijack') inferredIntent = 'hijack_role';
295
+ else if (cat === 'data_exfiltration') inferredIntent = 'steal_data';
296
+ else if (cat === 'prompt_injection') inferredIntent = 'escalate_privileges';
297
+ else if (cat === 'social_engineering') inferredIntent = 'disable_safety';
298
+ else if (cat === 'tool_abuse') inferredIntent = 'escalate_privileges';
299
+ }
300
+
301
+ let inferredTechnique = technique;
302
+ if (inferredTechnique === 'unknown' && scanThreats.length > 0) {
303
+ const cat = scanThreats[0].category;
304
+ if (cat === 'social_engineering') inferredTechnique = 'social_pressure';
305
+ else if (cat === 'encoding_evasion') inferredTechnique = 'encoding';
306
+ else if (cat === 'role_hijack') inferredTechnique = 'roleplay';
307
+ else if (cat === 'prompt_injection') inferredTechnique = 'fake_authority';
308
+ else inferredTechnique = 'direct_command';
309
+ }
310
+
311
+ // Compute confidence based on how many components we identified
312
+ let signals = 0;
313
+ let totalSlots = 4; // intent, technique, evasion (non-none), target
314
+ if (inferredIntent !== 'unknown') signals++;
315
+ if (inferredTechnique !== 'unknown') signals++;
316
+ if (evasionLayers.length > 0 && evasionLayers[0] !== 'none') signals++;
317
+ if (target !== 'unknown') signals++;
318
+
319
+ // Boost confidence if scanText found threats
320
+ const scanBoost = Math.min(scanThreats.length * 0.1, 0.3);
321
+ let confidence = (signals / totalSlots) * 0.7 + scanBoost;
322
+ confidence = Math.min(Math.max(confidence, 0), 1);
323
+ confidence = Math.round(confidence * 100) / 100;
324
+
325
+ return {
326
+ intent: inferredIntent,
327
+ technique: inferredTechnique,
328
+ evasionLayers,
329
+ target,
330
+ confidence
331
+ };
332
+ }
333
+
334
+ /**
335
+ * Compare two genomes and return a similarity score 0-1.
336
+ * @param {{ intent: string, technique: string, evasionLayers: string[], target: string }} genome1
337
+ * @param {{ intent: string, technique: string, evasionLayers: string[], target: string }} genome2
338
+ * @returns {number} 0-1 similarity
339
+ */
340
+ compare(genome1, genome2) {
341
+ this._comparisons++;
342
+
343
+ if (!genome1 || !genome2) return 0;
344
+
345
+ let score = 0;
346
+
347
+ // Intent match (weighted 0.35)
348
+ if (genome1.intent === genome2.intent && genome1.intent !== 'unknown') {
349
+ score += 0.35;
350
+ }
351
+
352
+ // Technique match (weighted 0.30)
353
+ if (genome1.technique === genome2.technique && genome1.technique !== 'unknown') {
354
+ score += 0.30;
355
+ }
356
+
357
+ // Evasion layer overlap (weighted 0.15)
358
+ const ev1 = genome1.evasionLayers || ['none'];
359
+ const ev2 = genome2.evasionLayers || ['none'];
360
+ score += jaccard(ev1, ev2) * 0.15;
361
+
362
+ // Target match (weighted 0.20)
363
+ if (genome1.target === genome2.target && genome1.target !== 'unknown') {
364
+ score += 0.20;
365
+ }
366
+
367
+ return Math.round(score * 100) / 100;
368
+ }
369
+
370
+ /**
371
+ * Find attacks in a corpus with a similar genome to the given text.
372
+ * @param {string} text - The text to sequence and compare
373
+ * @param {string[]} corpus - Array of known attack strings
374
+ * @param {number} [threshold=0.3] - Minimum similarity to include
375
+ * @returns {{ text: string, genome: object, similarity: number }[]}
376
+ */
377
+ findFamily(text, corpus, threshold = 0.3) {
378
+ this._familySearches++;
379
+
380
+ if (!text || !Array.isArray(corpus) || corpus.length === 0) return [];
381
+
382
+ const textGenome = this.sequence(text);
383
+ const results = [];
384
+
385
+ for (const entry of corpus) {
386
+ const entryGenome = this.sequence(entry);
387
+ const similarity = this.compare(textGenome, entryGenome);
388
+ if (similarity >= threshold) {
389
+ results.push({ text: entry, genome: entryGenome, similarity });
390
+ }
391
+ }
392
+
393
+ // Sort by similarity descending
394
+ results.sort((a, b) => b.similarity - a.similarity);
395
+ return results;
396
+ }
397
+
398
+ /**
399
+ * Return sequencing statistics.
400
+ * @returns {{ sequenced: number, comparisons: number, familySearches: number }}
401
+ */
402
+ getStats() {
403
+ return {
404
+ sequenced: this._sequenced,
405
+ comparisons: this._comparisons,
406
+ familySearches: this._familySearches
407
+ };
408
+ }
409
+ }
410
+
411
+ // =========================================================================
412
+ // GenomeDatabase CLASS
413
+ // =========================================================================
414
+
415
+ /**
416
+ * In-memory database of sequenced attack genomes.
417
+ */
418
+ class GenomeDatabase {
419
+ constructor() {
420
+ /** @type {{ text: string, genome: object }[]} */
421
+ this._entries = [];
422
+ }
423
+
424
+ /**
425
+ * Add a sequenced genome to the database.
426
+ * @param {string} text - The original attack text
427
+ * @param {{ intent: string, technique: string, evasionLayers: string[], target: string, confidence: number }} genome
428
+ */
429
+ add(text, genome) {
430
+ if (!text || !genome) return;
431
+ this._entries.push({ text, genome });
432
+ }
433
+
434
+ /**
435
+ * Find entries with matching intent and technique.
436
+ * @param {{ intent: string, technique: string }} genome - Genome to search for
437
+ * @returns {{ text: string, genome: object }[]}
438
+ */
439
+ search(genome) {
440
+ if (!genome) return [];
441
+ return this._entries.filter(entry => {
442
+ const intentMatch = genome.intent && genome.intent !== 'unknown' && entry.genome.intent === genome.intent;
443
+ const techniqueMatch = genome.technique && genome.technique !== 'unknown' && entry.genome.technique === genome.technique;
444
+ // Match if intent matches, or technique matches, or both
445
+ return intentMatch || techniqueMatch;
446
+ });
447
+ }
448
+
449
+ /**
450
+ * Group all genomes into families by shared intent+technique.
451
+ * @returns {Object<string, { text: string, genome: object }[]>}
452
+ */
453
+ getFamilies() {
454
+ const families = {};
455
+ for (const entry of this._entries) {
456
+ const key = familyKey(entry.genome.intent, entry.genome.technique);
457
+ if (!families[key]) families[key] = [];
458
+ families[key].push(entry);
459
+ }
460
+ return families;
461
+ }
462
+
463
+ /**
464
+ * Number of genomes in the database.
465
+ * @type {number}
466
+ */
467
+ get size() {
468
+ return this._entries.length;
469
+ }
470
+ }
471
+
472
+ // =========================================================================
473
+ // detectByGenome() FUNCTION
474
+ // =========================================================================
475
+
476
+ /**
477
+ * Check if the given text matches any known genome family in the database.
478
+ * Returns the best match with similarity score.
479
+ *
480
+ * @param {string} text - Text to analyze
481
+ * @param {GenomeDatabase} database - Genome database to search against
482
+ * @returns {{ match: boolean, family: string|null, similarity: number, genome: object }}
483
+ */
484
+ function detectByGenome(text, database) {
485
+ const sequencer = new AttackGenome();
486
+ const genome = sequencer.sequence(text);
487
+
488
+ if (!database || database.size === 0) {
489
+ return { match: false, family: null, similarity: 0, genome };
490
+ }
491
+
492
+ // Search for entries with matching intent or technique
493
+ const candidates = database.search(genome);
494
+
495
+ if (candidates.length === 0) {
496
+ return { match: false, family: null, similarity: 0, genome };
497
+ }
498
+
499
+ // Find the best matching candidate
500
+ let bestSimilarity = 0;
501
+ let bestFamily = null;
502
+
503
+ for (const candidate of candidates) {
504
+ const similarity = sequencer.compare(genome, candidate.genome);
505
+ if (similarity > bestSimilarity) {
506
+ bestSimilarity = similarity;
507
+ bestFamily = familyKey(candidate.genome.intent, candidate.genome.technique);
508
+ }
509
+ }
510
+
511
+ bestSimilarity = Math.round(bestSimilarity * 100) / 100;
512
+
513
+ // Threshold: at least 0.3 similarity to count as a match
514
+ const match = bestSimilarity >= 0.3;
515
+
516
+ return {
517
+ match,
518
+ family: match ? bestFamily : null,
519
+ similarity: bestSimilarity,
520
+ genome
521
+ };
522
+ }
523
+
524
+ // =========================================================================
525
+ // EXPORTS
526
+ // =========================================================================
527
+
528
+ module.exports = {
529
+ AttackGenome,
530
+ GenomeDatabase,
531
+ detectByGenome,
532
+ INTENT_PATTERNS,
533
+ TECHNIQUE_PATTERNS,
534
+ EVASION_PATTERNS,
535
+ TARGET_PATTERNS
536
+ };