cipher-security 2.0.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.
Files changed (76) hide show
  1. package/bin/cipher.js +566 -0
  2. package/lib/api/billing.js +321 -0
  3. package/lib/api/compliance.js +693 -0
  4. package/lib/api/controls.js +1401 -0
  5. package/lib/api/index.js +49 -0
  6. package/lib/api/marketplace.js +467 -0
  7. package/lib/api/openai-proxy.js +383 -0
  8. package/lib/api/server.js +685 -0
  9. package/lib/autonomous/feedback-loop.js +554 -0
  10. package/lib/autonomous/framework.js +512 -0
  11. package/lib/autonomous/index.js +97 -0
  12. package/lib/autonomous/leaderboard.js +594 -0
  13. package/lib/autonomous/modes/architect.js +412 -0
  14. package/lib/autonomous/modes/blue.js +386 -0
  15. package/lib/autonomous/modes/incident.js +684 -0
  16. package/lib/autonomous/modes/privacy.js +369 -0
  17. package/lib/autonomous/modes/purple.js +294 -0
  18. package/lib/autonomous/modes/recon.js +250 -0
  19. package/lib/autonomous/parallel.js +587 -0
  20. package/lib/autonomous/researcher.js +583 -0
  21. package/lib/autonomous/runner.js +955 -0
  22. package/lib/autonomous/scheduler.js +615 -0
  23. package/lib/autonomous/task-parser.js +127 -0
  24. package/lib/autonomous/validators/forensic.js +266 -0
  25. package/lib/autonomous/validators/osint.js +216 -0
  26. package/lib/autonomous/validators/privacy.js +296 -0
  27. package/lib/autonomous/validators/purple.js +298 -0
  28. package/lib/autonomous/validators/sigma.js +248 -0
  29. package/lib/autonomous/validators/threat-model.js +363 -0
  30. package/lib/benchmark/agent.js +119 -0
  31. package/lib/benchmark/baselines.js +43 -0
  32. package/lib/benchmark/builder.js +143 -0
  33. package/lib/benchmark/config.js +35 -0
  34. package/lib/benchmark/coordinator.js +91 -0
  35. package/lib/benchmark/index.js +20 -0
  36. package/lib/benchmark/llm.js +58 -0
  37. package/lib/benchmark/models.js +137 -0
  38. package/lib/benchmark/reporter.js +103 -0
  39. package/lib/benchmark/runner.js +103 -0
  40. package/lib/benchmark/sandbox.js +96 -0
  41. package/lib/benchmark/scorer.js +32 -0
  42. package/lib/benchmark/solver.js +166 -0
  43. package/lib/benchmark/tools.js +62 -0
  44. package/lib/bot/bot.js +238 -0
  45. package/lib/brand.js +105 -0
  46. package/lib/commands.js +100 -0
  47. package/lib/complexity.js +377 -0
  48. package/lib/config.js +213 -0
  49. package/lib/gateway/client.js +309 -0
  50. package/lib/gateway/commands.js +991 -0
  51. package/lib/gateway/config-validate.js +109 -0
  52. package/lib/gateway/gateway.js +367 -0
  53. package/lib/gateway/index.js +62 -0
  54. package/lib/gateway/mode.js +309 -0
  55. package/lib/gateway/plugins.js +222 -0
  56. package/lib/gateway/prompt.js +214 -0
  57. package/lib/mcp/server.js +262 -0
  58. package/lib/memory/compressor.js +425 -0
  59. package/lib/memory/engine.js +763 -0
  60. package/lib/memory/evolution.js +668 -0
  61. package/lib/memory/index.js +58 -0
  62. package/lib/memory/orchestrator.js +506 -0
  63. package/lib/memory/retriever.js +515 -0
  64. package/lib/memory/synthesizer.js +333 -0
  65. package/lib/pipeline/async-scanner.js +510 -0
  66. package/lib/pipeline/binary-analysis.js +1043 -0
  67. package/lib/pipeline/dom-xss-scanner.js +435 -0
  68. package/lib/pipeline/github-actions.js +792 -0
  69. package/lib/pipeline/index.js +124 -0
  70. package/lib/pipeline/osint.js +498 -0
  71. package/lib/pipeline/sarif.js +373 -0
  72. package/lib/pipeline/scanner.js +880 -0
  73. package/lib/pipeline/template-manager.js +525 -0
  74. package/lib/pipeline/xss-scanner.js +353 -0
  75. package/lib/setup-wizard.js +288 -0
  76. package/package.json +31 -0
@@ -0,0 +1,515 @@
1
+ // Copyright (c) 2026 defconxt. All rights reserved.
2
+ // Licensed under AGPL-3.0 — see LICENSE file for details.
3
+ // CIPHER is a trademark of defconxt.
4
+
5
+ /**
6
+ * CIPHER Memory — Stage 3: Intent-Aware Retrieval Planning
7
+ *
8
+ * Intelligent retrieval that understands query intent and adapts strategy:
9
+ * - Query complexity analysis → determines retrieval depth
10
+ * - Multi-query decomposition → sub-queries for complex questions
11
+ * - Hybrid search fusion → lexical + symbolic via RRF
12
+ * - Reflection-based refinement → iterative retrieval for complex queries
13
+ *
14
+ * Ported from Python memory/core/retriever.py.
15
+ */
16
+
17
+ import { extractSecurityEntities } from './compressor.js';
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Query Intent Classification
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /**
24
+ * Security query intent categories with retrieval strategy mapping.
25
+ */
26
+ const QueryIntent = {
27
+ // Intent → [description, searchDepth, requiresSymbolic]
28
+ INTENTS: {
29
+ finding_lookup: ['Looking up specific vulnerability or finding', 'shallow', true],
30
+ ioc_search: ['Searching for indicators of compromise', 'shallow', true],
31
+ ttp_mapping: ['Mapping techniques, tactics, procedures', 'medium', true],
32
+ engagement_context: ['Getting context for an engagement', 'deep', true],
33
+ temporal_query: ['Time-based query about when something happened', 'medium', true],
34
+ comparative: ['Comparing findings, hosts, or engagements', 'deep', false],
35
+ causal: ['Understanding why or how something happened', 'deep', false],
36
+ general: ['General security knowledge query', 'shallow', false],
37
+ },
38
+
39
+ // Keyword patterns for intent detection
40
+ PATTERNS: {
41
+ finding_lookup: [
42
+ 'vulnerability', 'vuln', 'finding', 'cve-', 'exploit', 'weakness',
43
+ 'injection', 'xss', 'sqli', 'rce', 'ssrf', 'csrf',
44
+ ],
45
+ ioc_search: [
46
+ 'ioc', 'indicator', 'hash', 'ip address', 'domain', 'c2', 'beacon',
47
+ 'callback', 'malware', 'sample',
48
+ ],
49
+ ttp_mapping: [
50
+ 'technique', 'tactic', 'procedure', 'mitre', 'att&ck', 't1',
51
+ 'lateral', 'persistence', 'privilege',
52
+ ],
53
+ engagement_context: [
54
+ 'engagement', 'pentest', 'assessment', 'audit', 'scope', 'client',
55
+ 'target', 'progress', 'status',
56
+ ],
57
+ temporal_query: [
58
+ 'when', 'timeline', 'first', 'last', 'before', 'after', 'during',
59
+ 'recently', 'latest', 'oldest',
60
+ ],
61
+ comparative: [
62
+ 'compare', 'difference', 'between', 'versus', 'vs', 'similar',
63
+ 'different', 'common',
64
+ ],
65
+ causal: [
66
+ 'why', 'how', 'because', 'cause', 'root cause', 'reason', 'led to',
67
+ 'resulted in', 'impact',
68
+ ],
69
+ },
70
+ };
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // RetrievalPlan
74
+ // ---------------------------------------------------------------------------
75
+
76
+ /**
77
+ * Structured retrieval plan for a query.
78
+ */
79
+ class RetrievalPlan {
80
+ constructor(opts = {}) {
81
+ this.intent = opts.intent ?? 'general';
82
+ this.complexity = opts.complexity ?? 0.3;
83
+ this.subQueries = opts.subQueries ?? [];
84
+ this.requiredFilters = opts.requiredFilters ?? {};
85
+ this.searchDepth = opts.searchDepth ?? 'shallow';
86
+ this.needsReflection = opts.needsReflection ?? false;
87
+ }
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // IntentClassifier
92
+ // ---------------------------------------------------------------------------
93
+
94
+ /**
95
+ * Classify security query intent using keyword matching.
96
+ */
97
+ class IntentClassifier {
98
+ /**
99
+ * Return the most likely intent for a query.
100
+ * @param {string} query
101
+ * @returns {string}
102
+ */
103
+ classify(query) {
104
+ const queryLower = query.toLowerCase();
105
+ const scores = {};
106
+
107
+ for (const [intent, keywords] of Object.entries(QueryIntent.PATTERNS)) {
108
+ let score = 0;
109
+ for (const kw of keywords) {
110
+ if (queryLower.includes(kw)) score++;
111
+ }
112
+ if (score > 0) scores[intent] = score;
113
+ }
114
+
115
+ if (Object.keys(scores).length === 0) return 'general';
116
+
117
+ // Return intent with highest score
118
+ let maxIntent = 'general';
119
+ let maxScore = 0;
120
+ for (const [intent, score] of Object.entries(scores)) {
121
+ if (score > maxScore) {
122
+ maxScore = score;
123
+ maxIntent = intent;
124
+ }
125
+ }
126
+ return maxIntent;
127
+ }
128
+
129
+ /**
130
+ * Estimate query complexity (0.0 = trivial, 1.0 = very complex).
131
+ * @param {string} query
132
+ * @returns {number}
133
+ */
134
+ estimateComplexity(query) {
135
+ let score = 0.0;
136
+
137
+ // Length-based
138
+ const words = query.split(/\s+/);
139
+ if (words.length > 20) {
140
+ score += 0.2;
141
+ } else if (words.length > 10) {
142
+ score += 0.1;
143
+ }
144
+
145
+ // Multi-hop indicators
146
+ const multiHop = ['and', 'also', 'then', 'after that', 'in addition'];
147
+ const queryLower = query.toLowerCase();
148
+ for (const kw of multiHop) {
149
+ if (queryLower.includes(kw)) score += 0.1;
150
+ }
151
+
152
+ // Comparative/causal is inherently complex
153
+ if (['compare', 'why', 'how did'].some((w) => queryLower.includes(w))) {
154
+ score += 0.3;
155
+ }
156
+
157
+ // Multiple entity references
158
+ const entities = extractSecurityEntities(query);
159
+ const entityCount = Object.values(entities).reduce((sum, arr) => sum + arr.length, 0);
160
+ if (entityCount > 3) {
161
+ score += 0.2;
162
+ } else if (entityCount > 1) {
163
+ score += 0.1;
164
+ }
165
+
166
+ return Math.min(score, 1.0);
167
+ }
168
+ }
169
+
170
+ // ---------------------------------------------------------------------------
171
+ // QueryDecomposer
172
+ // ---------------------------------------------------------------------------
173
+
174
+ /**
175
+ * Decompose complex queries into targeted sub-queries.
176
+ */
177
+ class QueryDecomposer {
178
+ /**
179
+ * Break a complex query into focused sub-queries.
180
+ * @param {string} query
181
+ * @param {string} intent
182
+ * @returns {string[]}
183
+ */
184
+ decompose(query, intent) {
185
+ let subQueries = [query];
186
+
187
+ // Split on conjunctions
188
+ if (/\s+and\s+/i.test(query)) {
189
+ const parts = query.split(/\s+and\s+/i);
190
+ if (parts.length > 1 && parts.every((p) => p.split(/\s+/).length > 3)) {
191
+ subQueries = parts;
192
+ }
193
+ } else if (intent === 'comparative') {
194
+ // For comparative queries, search each entity separately
195
+ const entities = extractSecurityEntities(query);
196
+ const targets = [
197
+ ...entities.ips,
198
+ ...entities.domains,
199
+ ...entities.cves,
200
+ ];
201
+ if (targets.length >= 2) {
202
+ subQueries = targets.slice(0, 3).map((t) => `${query} ${t}`);
203
+ }
204
+ } else if (intent === 'causal') {
205
+ // For causal queries, search for both the event and the cause
206
+ subQueries = [query];
207
+ const stripped = query
208
+ .replace(/\b(why|how|because|cause)\b/gi, '')
209
+ .trim();
210
+ if (stripped !== query && stripped.length > 10) {
211
+ subQueries.push(stripped);
212
+ }
213
+ }
214
+
215
+ return subQueries.slice(0, 4); // Max 4 sub-queries
216
+ }
217
+ }
218
+
219
+ // ---------------------------------------------------------------------------
220
+ // AdaptiveRetriever
221
+ // ---------------------------------------------------------------------------
222
+
223
+ /** Depth → top_k mapping */
224
+ const DEPTH_MAP = { shallow: 5, medium: 15, deep: 30 };
225
+
226
+ /**
227
+ * Stage 3: Intent-Aware Adaptive Retrieval.
228
+ *
229
+ * Combines intent classification, query decomposition, and hybrid search
230
+ * with optional reflection-based iterative refinement.
231
+ */
232
+ class AdaptiveRetriever {
233
+ /**
234
+ * @param {import('./engine.js').CipherMemory} memory
235
+ * @param {{ enableReflection?: boolean, maxReflectionRounds?: number }} opts
236
+ */
237
+ constructor(memory, opts = {}) {
238
+ this.memory = memory;
239
+ this.classifier = new IntentClassifier();
240
+ this.decomposer = new QueryDecomposer();
241
+ this.enableReflection = opts.enableReflection ?? true;
242
+ this.maxReflectionRounds = opts.maxReflectionRounds ?? 2;
243
+ }
244
+
245
+ /**
246
+ * Create a retrieval plan for the query.
247
+ * @param {string} query
248
+ * @returns {RetrievalPlan}
249
+ */
250
+ plan(query) {
251
+ const intent = this.classifier.classify(query);
252
+ const complexity = this.classifier.estimate_complexity
253
+ ? this.classifier.estimateComplexity(query)
254
+ : this.classifier.estimateComplexity(query);
255
+
256
+ const intentInfo = QueryIntent.INTENTS[intent] ?? ['', 'shallow', false];
257
+ const [, depth] = intentInfo;
258
+
259
+ const subQueries =
260
+ complexity > 0.5
261
+ ? this.decomposer.decompose(query, intent)
262
+ : [query];
263
+
264
+ // Extract filter hints from query
265
+ const filters = {};
266
+ const entities = extractSecurityEntities(query);
267
+ if (entities.cves.length > 0) filters.cve_id = entities.cves[0];
268
+ if (entities.mitre.length > 0) filters.mitre_technique = entities.mitre[0];
269
+
270
+ // Engagement ID extraction (e.g., "engagement ENG-001")
271
+ const engMatch = query.match(/engagement\s+([A-Za-z0-9-]+)/i);
272
+ if (engMatch) filters.engagement_id = engMatch[1];
273
+
274
+ return new RetrievalPlan({
275
+ intent,
276
+ complexity,
277
+ subQueries,
278
+ requiredFilters: filters,
279
+ searchDepth: depth,
280
+ needsReflection: complexity > 0.6 && this.enableReflection,
281
+ });
282
+ }
283
+
284
+ /**
285
+ * Retrieve relevant memories with intent-aware planning.
286
+ * @param {string} query
287
+ * @param {string} engagementId
288
+ * @param {number} [limit]
289
+ * @returns {import('./engine.js').MemoryEntry[]}
290
+ */
291
+ retrieve(query, engagementId = '', limit = null) {
292
+ const plan = this.plan(query);
293
+
294
+ if (engagementId) {
295
+ plan.requiredFilters.engagement_id = engagementId;
296
+ }
297
+
298
+ const topK = limit ?? DEPTH_MAP[plan.searchDepth] ?? 10;
299
+
300
+ // Execute sub-queries and merge results
301
+ const allResults = [];
302
+ const seenIds = new Set();
303
+
304
+ for (const subQuery of plan.subQueries) {
305
+ const results = this.memory.search(subQuery, {
306
+ engagementId: plan.requiredFilters.engagement_id ?? '',
307
+ memoryType: plan.requiredFilters.memory_type ?? '',
308
+ severity: plan.requiredFilters.severity ?? '',
309
+ mitreTechnique: plan.requiredFilters.mitre_technique ?? '',
310
+ }, topK);
311
+
312
+ for (const entry of results) {
313
+ if (!seenIds.has(entry.entryId)) {
314
+ seenIds.add(entry.entryId);
315
+ allResults.push(entry);
316
+ }
317
+ }
318
+ }
319
+
320
+ // Reflection: check if we have enough relevant results
321
+ if (plan.needsReflection && allResults.length < 3) {
322
+ return this._reflectAndRetry(query, plan, allResults, seenIds, topK);
323
+ }
324
+
325
+ return allResults.slice(0, topK);
326
+ }
327
+
328
+ /**
329
+ * Iterative refinement when initial results are insufficient.
330
+ * @private
331
+ */
332
+ _reflectAndRetry(query, plan, currentResults, seenIds, topK) {
333
+ for (let round = 0; round < this.maxReflectionRounds; round++) {
334
+ const altQueries = this._generateAlternativeQueries(query, currentResults);
335
+
336
+ for (const altQuery of altQueries) {
337
+ const results = this.memory.search(altQuery, {
338
+ engagementId: plan.requiredFilters.engagement_id ?? '',
339
+ }, topK);
340
+
341
+ for (const entry of results) {
342
+ if (!seenIds.has(entry.entryId)) {
343
+ seenIds.add(entry.entryId);
344
+ currentResults.push(entry);
345
+ }
346
+ }
347
+ }
348
+
349
+ if (currentResults.length >= 3) break;
350
+ }
351
+
352
+ return currentResults;
353
+ }
354
+
355
+ /**
356
+ * Generate alternative search queries based on current results.
357
+ * @private
358
+ */
359
+ _generateAlternativeQueries(originalQuery, currentResults) {
360
+ const alternatives = [];
361
+
362
+ // Try broader query (remove specifics)
363
+ let broader = originalQuery.replace(/CVE-\d+-\d+/g, '').trim();
364
+ broader = broader.replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '').trim();
365
+ if (broader && broader !== originalQuery) {
366
+ alternatives.push(broader);
367
+ }
368
+
369
+ // Try extracting key terms
370
+ const words = originalQuery.split(/\s+/);
371
+ const skipWords = new Set([
372
+ 'what', 'where', 'when', 'which', 'about', 'find', 'search', 'look',
373
+ 'show', 'there',
374
+ ]);
375
+ const contentWords = words.filter(
376
+ (w) => w.length > 4 && !skipWords.has(w.toLowerCase()),
377
+ );
378
+ if (contentWords.length > 0) {
379
+ alternatives.push(contentWords.slice(0, 5).join(' '));
380
+ }
381
+
382
+ // Try using tags from current results
383
+ if (currentResults.length > 0) {
384
+ for (const entry of currentResults.slice(0, 2)) {
385
+ if (entry.tags && entry.tags.length > 0) {
386
+ alternatives.push(entry.tags.slice(0, 3).join(' '));
387
+ }
388
+ }
389
+ }
390
+
391
+ return alternatives.slice(0, 3);
392
+ }
393
+ }
394
+
395
+ // ---------------------------------------------------------------------------
396
+ // ContextBuilder — Token-budgeted context assembly
397
+ // ---------------------------------------------------------------------------
398
+
399
+ /**
400
+ * Build token-budgeted context from retrieved memories.
401
+ *
402
+ * Assembles memories into a structured context block suitable for
403
+ * injection into LLM prompts, respecting token budget constraints.
404
+ */
405
+ class ContextBuilder {
406
+ /**
407
+ * @param {{ maxTokens?: number, charsPerToken?: number }} opts
408
+ */
409
+ constructor(opts = {}) {
410
+ this.maxTokens = opts.maxTokens ?? 4000;
411
+ this.charsPerToken = opts.charsPerToken ?? 4.0;
412
+ }
413
+
414
+ /**
415
+ * Build structured context from memory entries.
416
+ * Returns a formatted string suitable for system prompt injection.
417
+ * @param {import('./engine.js').MemoryEntry[]} entries
418
+ * @param {string} query
419
+ * @returns {string}
420
+ */
421
+ build(entries, query = '') {
422
+ if (!entries || entries.length === 0) return '';
423
+
424
+ const maxChars = Math.floor(this.maxTokens * this.charsPerToken);
425
+ const sections = {
426
+ findings: [],
427
+ iocs: [],
428
+ ttps: [],
429
+ decisions: [],
430
+ context: [],
431
+ };
432
+
433
+ for (const entry of entries) {
434
+ let line = `- ${entry.content}`;
435
+ if (entry.severity) {
436
+ line = `- [${entry.severity.toUpperCase()}] ${entry.content}`;
437
+ }
438
+ if (entry.mitreAttack && entry.mitreAttack.length > 0) {
439
+ const attacks = entry.mitreAttack.slice(0, 3).join(', ');
440
+ line += ` (${attacks})`;
441
+ }
442
+
443
+ // Route to section based on memoryType
444
+ // memoryType is a string value from MemoryType enum
445
+ const memType = typeof entry.memoryType === 'object' && entry.memoryType.value
446
+ ? entry.memoryType.value
447
+ : entry.memoryType;
448
+
449
+ if (memType === 'finding') {
450
+ sections.findings.push(line);
451
+ } else if (memType === 'ioc') {
452
+ sections.iocs.push(line);
453
+ } else if (memType === 'ttp') {
454
+ sections.ttps.push(line);
455
+ } else if (memType === 'decision') {
456
+ sections.decisions.push(line);
457
+ } else {
458
+ sections.context.push(line);
459
+ }
460
+ }
461
+
462
+ // Assemble within budget
463
+ const parts = ['## Engagement Memory'];
464
+ let currentChars = parts[0].length;
465
+
466
+ const sectionLabels = {
467
+ findings: '### Findings',
468
+ iocs: '### Indicators of Compromise',
469
+ ttps: '### Techniques & Procedures',
470
+ decisions: '### Decisions',
471
+ context: '### Context',
472
+ };
473
+
474
+ for (const [key, label] of Object.entries(sectionLabels)) {
475
+ const items = sections[key];
476
+ if (items.length === 0) continue;
477
+
478
+ const sectionText = `\n${label}\n${items.join('\n')}`;
479
+ if (currentChars + sectionText.length > maxChars) {
480
+ // Truncate items to fit
481
+ const remaining = maxChars - currentChars - (`\n${label}\n`).length;
482
+ if (remaining > 100) {
483
+ const truncated = [];
484
+ let charsUsed = 0;
485
+ for (const item of items) {
486
+ if (charsUsed + item.length + 1 > remaining) break;
487
+ truncated.push(item);
488
+ charsUsed += item.length + 1;
489
+ }
490
+ if (truncated.length > 0) {
491
+ parts.push(`\n${label}`);
492
+ parts.push(...truncated);
493
+ }
494
+ }
495
+ break;
496
+ }
497
+
498
+ parts.push(`\n${label}`);
499
+ parts.push(...items);
500
+ currentChars += sectionText.length;
501
+ }
502
+
503
+ return parts.join('\n');
504
+ }
505
+ }
506
+
507
+ export {
508
+ QueryIntent,
509
+ RetrievalPlan,
510
+ IntentClassifier,
511
+ QueryDecomposer,
512
+ AdaptiveRetriever,
513
+ ContextBuilder,
514
+ DEPTH_MAP,
515
+ };