cipher-security 5.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 (75) hide show
  1. package/bin/cipher.js +465 -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 +130 -0
  45. package/lib/commands.js +99 -0
  46. package/lib/complexity.js +377 -0
  47. package/lib/config.js +213 -0
  48. package/lib/gateway/client.js +309 -0
  49. package/lib/gateway/commands.js +830 -0
  50. package/lib/gateway/config-validate.js +109 -0
  51. package/lib/gateway/gateway.js +367 -0
  52. package/lib/gateway/index.js +62 -0
  53. package/lib/gateway/mode.js +309 -0
  54. package/lib/gateway/plugins.js +222 -0
  55. package/lib/gateway/prompt.js +214 -0
  56. package/lib/mcp/server.js +262 -0
  57. package/lib/memory/compressor.js +425 -0
  58. package/lib/memory/engine.js +763 -0
  59. package/lib/memory/evolution.js +668 -0
  60. package/lib/memory/index.js +58 -0
  61. package/lib/memory/orchestrator.js +506 -0
  62. package/lib/memory/retriever.js +515 -0
  63. package/lib/memory/synthesizer.js +333 -0
  64. package/lib/pipeline/async-scanner.js +510 -0
  65. package/lib/pipeline/binary-analysis.js +1043 -0
  66. package/lib/pipeline/dom-xss-scanner.js +435 -0
  67. package/lib/pipeline/github-actions.js +792 -0
  68. package/lib/pipeline/index.js +124 -0
  69. package/lib/pipeline/osint.js +498 -0
  70. package/lib/pipeline/sarif.js +373 -0
  71. package/lib/pipeline/scanner.js +880 -0
  72. package/lib/pipeline/template-manager.js +525 -0
  73. package/lib/pipeline/xss-scanner.js +353 -0
  74. package/lib/setup-wizard.js +229 -0
  75. package/package.json +30 -0
@@ -0,0 +1,425 @@
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 1: Semantic Structured Compression
7
+ *
8
+ * Converts raw security engagement dialogues into atomic, self-contained
9
+ * memory entries through extraction:
10
+ *
11
+ * - Security-specific entity extraction (IPs, CVEs, MITRE ATT&CK, tools)
12
+ * - Information density gating (skip low-information exchanges)
13
+ * - Heuristic compression (always works, no LLM required)
14
+ * - LLM compression (stubbed — S03 provides the LLM client)
15
+ *
16
+ * Ported from Python memory/core/compressor.py.
17
+ */
18
+
19
+ import { randomUUID } from 'node:crypto';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Security-specific extraction patterns
23
+ // ---------------------------------------------------------------------------
24
+
25
+ const _IP_RE = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g;
26
+ const _CVE_RE = /CVE-\d{4}-\d{4,}/g;
27
+ const _MITRE_RE = /T\d{4}(?:\.\d{3})?/g;
28
+ const _PORT_RE = /\b(?:port\s+)?(\d{1,5})(?:\/(?:tcp|udp))?\b/gi;
29
+ const _DOMAIN_RE =
30
+ /\b(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}\b/g;
31
+ const _HASH_MD5_RE = /\b[a-fA-F0-9]{32}\b/g;
32
+ const _HASH_SHA256_RE = /\b[a-fA-F0-9]{64}\b/g;
33
+
34
+ // File extensions to filter from domain matches
35
+ const _FILE_EXTENSIONS = ['.py', '.js', '.md', '.yaml', '.json', '.txt'];
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Entity Extraction
39
+ // ---------------------------------------------------------------------------
40
+
41
+ /**
42
+ * Extract security-relevant entities from text using regex patterns.
43
+ * @param {string} text
44
+ * @returns {{ ips: string[], cves: string[], mitre: string[], domains: string[], hashes_sha256: string[], hashes_md5: string[] }}
45
+ */
46
+ function extractSecurityEntities(text) {
47
+ // Reset lastIndex on all regexes (they have /g flag)
48
+ const matchAll = (re, str) => [...str.matchAll(new RegExp(re.source, re.flags))].map((m) => m[0]);
49
+
50
+ const ips = [...new Set(matchAll(_IP_RE, text))];
51
+ const cves = [...new Set(matchAll(_CVE_RE, text))];
52
+ const mitre = [...new Set(matchAll(_MITRE_RE, text))];
53
+ const hashes_sha256 = [...new Set(matchAll(_HASH_SHA256_RE, text))];
54
+ const hashes_md5 = [...new Set(matchAll(_HASH_MD5_RE, text))];
55
+
56
+ // Filter hashes: SHA256 matches are 64 chars, MD5 are 32 chars.
57
+ // If a 64-char hash also matches the 32-char pattern, remove from MD5.
58
+ const sha256Set = new Set(hashes_sha256);
59
+ const filteredMd5 = hashes_md5.filter(
60
+ (h) => !hashes_sha256.some((s) => s.includes(h)),
61
+ );
62
+
63
+ const rawDomains = [...new Set(matchAll(_DOMAIN_RE, text))];
64
+ const domains = rawDomains.filter(
65
+ (d) => !_FILE_EXTENSIONS.some((ext) => d.endsWith(ext)),
66
+ );
67
+
68
+ return { ips, cves, mitre, domains, hashes_sha256, hashes_md5: filteredMd5 };
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // DialogueTurn
73
+ // ---------------------------------------------------------------------------
74
+
75
+ /**
76
+ * Single turn in a security engagement dialogue.
77
+ */
78
+ class DialogueTurn {
79
+ /**
80
+ * @param {{ turnId: number, role: string, content: string, timestamp?: string, toolName?: string, toolOutput?: string }} opts
81
+ */
82
+ constructor(opts = {}) {
83
+ this.turnId = opts.turnId ?? 0;
84
+ this.role = opts.role ?? 'user';
85
+ this.content = opts.content ?? '';
86
+ this.timestamp = opts.timestamp ?? '';
87
+ this.toolName = opts.toolName ?? '';
88
+ this.toolOutput = opts.toolOutput ?? '';
89
+ }
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // CompressedEntry
94
+ // ---------------------------------------------------------------------------
95
+
96
+ /**
97
+ * Atomic memory unit produced by Stage 1 compression.
98
+ */
99
+ class CompressedEntry {
100
+ constructor(opts = {}) {
101
+ this.entryId = opts.entryId ?? randomUUID();
102
+ this.losslessRestatement = opts.losslessRestatement ?? '';
103
+ this.memoryType = opts.memoryType ?? 'note';
104
+ this.confidence = opts.confidence ?? 'confirmed';
105
+
106
+ // Security-specific extracted metadata
107
+ this.targets = opts.targets ?? [];
108
+ this.cveIds = opts.cveIds ?? [];
109
+ this.mitreAttack = opts.mitreAttack ?? [];
110
+ this.severity = opts.severity ?? '';
111
+ this.toolsUsed = opts.toolsUsed ?? [];
112
+ this.hashes = opts.hashes ?? [];
113
+
114
+ // General metadata
115
+ this.keywords = opts.keywords ?? [];
116
+ this.persons = opts.persons ?? [];
117
+ this.entities = opts.entities ?? [];
118
+ this.timestamp = opts.timestamp ?? '';
119
+ this.topic = opts.topic ?? '';
120
+
121
+ // Source tracking
122
+ this.sourceTurns = opts.sourceTurns ?? [];
123
+ this.engagementId = opts.engagementId ?? '';
124
+ this.sourceSkill = opts.sourceSkill ?? '';
125
+
126
+ this.createdAt = opts.createdAt ?? new Date().toISOString();
127
+ }
128
+ }
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // DensityGate
132
+ // ---------------------------------------------------------------------------
133
+
134
+ /**
135
+ * Information density gate — filters out low-information dialogue turns.
136
+ *
137
+ * Scores based on: technical content, content length, lexical diversity.
138
+ */
139
+ class DensityGate {
140
+ static LOW_INFO_PATTERNS = [
141
+ /^(?:ok|okay|sure|yes|no|thanks|thank you|got it|understood)[.!?]?$/i,
142
+ /^(?:let me|i'll|i will|going to)\s/i,
143
+ /^(?:here (?:is|are)|sure,?\s+here)/i,
144
+ ];
145
+
146
+ /**
147
+ * @param {number} minScore — minimum density score to include a turn (default 0.3)
148
+ */
149
+ constructor(minScore = 0.3) {
150
+ this.minScore = minScore;
151
+ }
152
+
153
+ /**
154
+ * Score information density of a dialogue turn (0.0 - 1.0).
155
+ * @param {DialogueTurn} turn
156
+ * @returns {number}
157
+ */
158
+ score(turn) {
159
+ const text = (turn.content || '').trim();
160
+ if (!text) return 0.0;
161
+
162
+ let score = 0.0;
163
+
164
+ // Length score
165
+ const length = text.length;
166
+ if (length > 200) {
167
+ score += 0.3;
168
+ } else if (length > 50) {
169
+ score += 0.15;
170
+ }
171
+
172
+ // Technical content indicators
173
+ if (_IP_RE.test(text)) score += 0.15;
174
+ _IP_RE.lastIndex = 0;
175
+ if (_CVE_RE.test(text)) score += 0.2;
176
+ _CVE_RE.lastIndex = 0;
177
+ if (_MITRE_RE.test(text)) score += 0.15;
178
+ _MITRE_RE.lastIndex = 0;
179
+ if (text.includes('```')) score += 0.2;
180
+ if (_HASH_SHA256_RE.test(text) || _HASH_MD5_RE.test(text)) score += 0.1;
181
+ _HASH_SHA256_RE.lastIndex = 0;
182
+ _HASH_MD5_RE.lastIndex = 0;
183
+
184
+ // Unique term ratio (lexical diversity)
185
+ const words = text.toLowerCase().split(/\s+/);
186
+ if (words.length > 0) {
187
+ const uniqueRatio = new Set(words).size / words.length;
188
+ score += uniqueRatio * 0.2;
189
+ }
190
+
191
+ // Penalize boilerplate
192
+ for (const pat of DensityGate.LOW_INFO_PATTERNS) {
193
+ if (pat.test(text)) {
194
+ score *= 0.3;
195
+ break;
196
+ }
197
+ }
198
+
199
+ // Tool output is almost always high-info
200
+ if (turn.role === 'tool' && turn.toolOutput) {
201
+ score = Math.max(score, 0.5);
202
+ }
203
+
204
+ return Math.min(score, 1.0);
205
+ }
206
+
207
+ /**
208
+ * @param {DialogueTurn} turn
209
+ * @returns {boolean}
210
+ */
211
+ shouldInclude(turn) {
212
+ return this.score(turn) >= this.minScore;
213
+ }
214
+ }
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // SemanticCompressor
218
+ // ---------------------------------------------------------------------------
219
+
220
+ // Stop words for keyword extraction
221
+ const _STOP_WORDS = new Set([
222
+ 'the', 'and', 'for', 'that', 'this', 'with', 'from', 'have', 'has',
223
+ 'been', 'were', 'was', 'are', 'will', 'would', 'could', 'should',
224
+ 'into', 'about', 'which',
225
+ ]);
226
+
227
+ /**
228
+ * Stage 1: Semantic Structured Compression.
229
+ *
230
+ * Processes dialogue windows through:
231
+ * 1. Density gating — filter low-info turns
232
+ * 2. Entity extraction — security-specific pattern matching
233
+ * 3. LLM compression (stubbed) or heuristic extraction
234
+ * 4. Atomic entry creation — self-contained memory units
235
+ */
236
+ class SemanticCompressor {
237
+ /**
238
+ * @param {{ llmClient?: any, windowSize?: number, overlapSize?: number, densityThreshold?: number, model?: string }} opts
239
+ */
240
+ constructor(opts = {}) {
241
+ this.llmClient = opts.llmClient ?? null;
242
+ this.windowSize = opts.windowSize ?? 20;
243
+ this.overlapSize = opts.overlapSize ?? 2;
244
+ this.densityGate = new DensityGate(opts.densityThreshold ?? 0.3);
245
+ this.model = opts.model ?? 'claude-sonnet-4-20250514';
246
+ this._previousContext = '';
247
+ }
248
+
249
+ /**
250
+ * Compress dialogue turns into atomic memory entries.
251
+ * @param {DialogueTurn[]} turns
252
+ * @param {string} engagementId
253
+ * @param {string} sourceSkill
254
+ * @returns {Promise<CompressedEntry[]>}
255
+ */
256
+ async compress(turns, engagementId = '', sourceSkill = '') {
257
+ // Step 1: Density gating
258
+ const filtered = turns.filter((t) => this.densityGate.shouldInclude(t));
259
+ if (filtered.length === 0) return [];
260
+
261
+ // Step 2: Window processing
262
+ const allEntries = [];
263
+ const step = this.windowSize - this.overlapSize;
264
+ for (let i = 0; i < filtered.length; i += step) {
265
+ const window = filtered.slice(i, i + this.windowSize);
266
+ const entries = await this._processWindow(window, engagementId, sourceSkill);
267
+ allEntries.push(...entries);
268
+ }
269
+
270
+ return allEntries;
271
+ }
272
+
273
+ /**
274
+ * Process a single window of dialogue turns.
275
+ * @private
276
+ */
277
+ async _processWindow(window, engagementId, sourceSkill) {
278
+ const combinedText = window.map((t) => t.content).join(' ');
279
+ const entities = extractSecurityEntities(combinedText);
280
+
281
+ let entries;
282
+ if (this.llmClient !== null) {
283
+ entries = await this._llmCompress(window, entities);
284
+ } else {
285
+ entries = this._heuristicCompress(window, entities);
286
+ }
287
+
288
+ // Annotate all entries with engagement context
289
+ for (const entry of entries) {
290
+ entry.engagementId = engagementId;
291
+ entry.sourceSkill = sourceSkill;
292
+ entry.targets = entities.ips.concat(entities.domains);
293
+ entry.cveIds = entities.cves;
294
+ entry.mitreAttack = entities.mitre;
295
+ entry.hashes = entities.hashes_sha256.concat(entities.hashes_md5);
296
+ }
297
+
298
+ this._previousContext = this._buildContextSummary(entries);
299
+ return entries;
300
+ }
301
+
302
+ /**
303
+ * LLM compression — stubbed for S03 integration.
304
+ * Falls back to heuristic compression.
305
+ * @private
306
+ */
307
+ async _llmCompress(window, entities) {
308
+ // TODO: S03 LLM integration — call this.llmClient with extraction prompt
309
+ // For now, fall back to heuristic compression
310
+ return this._heuristicCompress(window, entities);
311
+ }
312
+
313
+ /**
314
+ * Fallback heuristic compression when no LLM is available.
315
+ * @private
316
+ */
317
+ _heuristicCompress(window, entities) {
318
+ const entries = [];
319
+
320
+ for (const turn of window) {
321
+ const sentences = turn.content.split(/[.!?\n]+/);
322
+ for (let sent of sentences) {
323
+ sent = sent.trim();
324
+ if (sent.length < 20) continue;
325
+
326
+ const sentEntities = extractSecurityEntities(sent);
327
+ const hasSecurityContent = Object.values(sentEntities).some(
328
+ (arr) => arr.length > 0,
329
+ );
330
+
331
+ if (
332
+ hasSecurityContent ||
333
+ this.densityGate.score(new DialogueTurn({ turnId: 0, role: turn.role, content: sent })) > 0.5
334
+ ) {
335
+ // Determine memory type from content
336
+ let memType = 'note';
337
+ if (sentEntities.cves.length > 0) {
338
+ memType = 'finding';
339
+ } else if (sentEntities.ips.length > 0 || sentEntities.domains.length > 0) {
340
+ memType = 'ioc';
341
+ } else if (sentEntities.mitre.length > 0) {
342
+ memType = 'ttp';
343
+ }
344
+
345
+ // Extract keywords (content words > 4 chars)
346
+ const words = sent.toLowerCase().split(/\s+/);
347
+ const keywords = words
348
+ .map((w) => w.replace(/[.,;:!?()[\]{}"']/g, ''))
349
+ .filter((w) => w.length > 4 && !_STOP_WORDS.has(w))
350
+ .slice(0, 10);
351
+
352
+ entries.push(
353
+ new CompressedEntry({
354
+ losslessRestatement: sent,
355
+ memoryType: memType,
356
+ keywords,
357
+ sourceTurns: [turn.turnId],
358
+ timestamp: turn.timestamp || new Date().toISOString(),
359
+ }),
360
+ );
361
+ }
362
+ }
363
+ }
364
+
365
+ return entries;
366
+ }
367
+
368
+ /**
369
+ * Format a window of turns into human-readable text.
370
+ * @private
371
+ */
372
+ _formatWindow(window) {
373
+ const lines = [];
374
+ for (const turn of window) {
375
+ const ts = turn.timestamp ? ` [${turn.timestamp}]` : '';
376
+ let prefix = turn.role.toUpperCase();
377
+ if (turn.toolName) prefix = `TOOL(${turn.toolName})`;
378
+ lines.push(`${prefix}${ts}: ${turn.content}`);
379
+ if (turn.toolOutput) {
380
+ lines.push(` OUTPUT: ${turn.toolOutput.slice(0, 500)}`);
381
+ }
382
+ }
383
+ return lines.join('\n');
384
+ }
385
+
386
+ /**
387
+ * Build extraction prompt for LLM compression.
388
+ * @private
389
+ */
390
+ _buildExtractionPrompt(dialogueText, entities) {
391
+ let contextSection = '';
392
+ if (this._previousContext) {
393
+ contextSection = `## Previous Context (avoid duplication):\n${this._previousContext}\n\n---\n`;
394
+ }
395
+
396
+ let entityHints = '';
397
+ if (Object.values(entities).some((arr) => arr.length > 0)) {
398
+ const parts = [];
399
+ if (entities.ips.length > 0) parts.push(`IPs: ${entities.ips.join(', ')}`);
400
+ if (entities.cves.length > 0) parts.push(`CVEs: ${entities.cves.join(', ')}`);
401
+ if (entities.mitre.length > 0) parts.push(`MITRE: ${entities.mitre.join(', ')}`);
402
+ if (entities.domains.length > 0) parts.push(`Domains: ${entities.domains.slice(0, 10).join(', ')}`);
403
+ entityHints = `\n## Detected Entities:\n${parts.join('\n')}\n`;
404
+ }
405
+
406
+ return `${contextSection}${entityHints}\n## Engagement Dialogue:\n${dialogueText}`;
407
+ }
408
+
409
+ /**
410
+ * Build summary of recently compressed entries for context tracking.
411
+ * @private
412
+ */
413
+ _buildContextSummary(entries) {
414
+ if (entries.length === 0) return '';
415
+ return entries.slice(-5).map((e) => `- ${e.losslessRestatement}`).join('\n');
416
+ }
417
+ }
418
+
419
+ export {
420
+ extractSecurityEntities,
421
+ DialogueTurn,
422
+ CompressedEntry,
423
+ DensityGate,
424
+ SemanticCompressor,
425
+ };