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,510 @@
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
+ * Async scanning pipeline for non-blocking, Promise-based scans.
7
+ *
8
+ * Provides AsyncNucleiRunner and AsyncKatanaRunner that wrap
9
+ * child_process.spawn in Promise APIs, plus AsyncScanManager with
10
+ * semaphore-based concurrency control.
11
+ *
12
+ * Ported from pipeline/async_scanner.py (331 LOC Python).
13
+ * Python used asyncio — Node.js uses Promises + spawn.
14
+ */
15
+
16
+ import { spawn } from 'node:child_process';
17
+ import { randomBytes } from 'node:crypto';
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Enums
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /** @enum {string} Status of an async scan job. */
24
+ const AsyncScanStatus = Object.freeze({
25
+ QUEUED: 'queued',
26
+ RUNNING: 'running',
27
+ COMPLETED: 'completed',
28
+ FAILED: 'failed',
29
+ CANCELLED: 'cancelled',
30
+ });
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Data classes
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /**
37
+ * A finding from an async scan.
38
+ */
39
+ class AsyncFinding {
40
+ constructor(opts = {}) {
41
+ this.id = opts.id ?? '';
42
+ this.name = opts.name ?? '';
43
+ this.severity = opts.severity ?? 'info';
44
+ this.url = opts.url ?? '';
45
+ this.description = opts.description ?? '';
46
+ this.matchedAt = opts.matchedAt ?? '';
47
+ this.template = opts.template ?? '';
48
+ this.timestamp = opts.timestamp ?? 0;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Result of an async scan job.
54
+ */
55
+ class AsyncScanResult {
56
+ constructor(opts = {}) {
57
+ this.scanId = opts.scanId ?? '';
58
+ this.target = opts.target ?? '';
59
+ this.status = opts.status ?? AsyncScanStatus.QUEUED;
60
+ this.findings = opts.findings ?? [];
61
+ this.startedAt = opts.startedAt ?? 0;
62
+ this.completedAt = opts.completedAt ?? 0;
63
+ this.error = opts.error ?? '';
64
+ this.profile = opts.profile ?? 'standard';
65
+ this.totalRequests = opts.totalRequests ?? 0;
66
+ }
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Semaphore
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /**
74
+ * Promise-based semaphore for concurrency control.
75
+ * acquire() returns a Promise that resolves when a slot is available.
76
+ * release() frees a slot and resolves the next queued waiter.
77
+ */
78
+ class Semaphore {
79
+ /**
80
+ * @param {number} max - Maximum concurrent slots
81
+ */
82
+ constructor(max) {
83
+ this._max = max;
84
+ this._count = 0;
85
+ this._queue = [];
86
+ }
87
+
88
+ /**
89
+ * Acquire a slot. Resolves immediately if available, otherwise queues.
90
+ * @returns {Promise<void>}
91
+ */
92
+ acquire() {
93
+ if (this._count < this._max) {
94
+ this._count++;
95
+ return Promise.resolve();
96
+ }
97
+ return new Promise((resolve) => {
98
+ this._queue.push(resolve);
99
+ });
100
+ }
101
+
102
+ /**
103
+ * Release a slot. If waiters are queued, the next one is resolved.
104
+ */
105
+ release() {
106
+ if (this._queue.length > 0) {
107
+ const next = this._queue.shift();
108
+ next();
109
+ } else {
110
+ this._count--;
111
+ }
112
+ }
113
+
114
+ /** Current number of active slots. */
115
+ get active() {
116
+ return this._count;
117
+ }
118
+
119
+ /** Number of waiters in the queue. */
120
+ get waiting() {
121
+ return this._queue.length;
122
+ }
123
+ }
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // whichSync (local utility)
127
+ // ---------------------------------------------------------------------------
128
+
129
+ import { execFileSync } from 'node:child_process';
130
+
131
+ function whichSync(binary) {
132
+ try {
133
+ return execFileSync('which', [binary], { encoding: 'utf8' }).trim() || null;
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // AsyncNucleiRunner
141
+ // ---------------------------------------------------------------------------
142
+
143
+ class AsyncNucleiRunner {
144
+ /**
145
+ * @param {object} opts
146
+ * @param {string} [opts.binary='nuclei']
147
+ * @param {number} [opts.timeout=300]
148
+ */
149
+ constructor(opts = {}) {
150
+ this.binary = opts.binary ?? 'nuclei';
151
+ this.timeout = opts.timeout ?? 300;
152
+ this._available = null;
153
+ }
154
+
155
+ /**
156
+ * Check if nuclei binary is in PATH.
157
+ * @returns {boolean}
158
+ */
159
+ isAvailable() {
160
+ if (this._available === null) {
161
+ this._available = whichSync(this.binary) !== null;
162
+ }
163
+ return this._available;
164
+ }
165
+
166
+ /**
167
+ * Run nuclei scan asynchronously.
168
+ * @param {string} target
169
+ * @param {object} opts
170
+ * @param {string} [opts.severity='medium,high,critical']
171
+ * @param {string} [opts.templates='']
172
+ * @param {number} [opts.rateLimit=150]
173
+ * @returns {Promise<AsyncFinding[]>}
174
+ */
175
+ scan(target, opts = {}) {
176
+ if (!this.isAvailable()) {
177
+ return Promise.reject(new Error('nuclei binary not found in PATH'));
178
+ }
179
+
180
+ const severity = opts.severity ?? 'medium,high,critical';
181
+ const rateLimit = opts.rateLimit ?? 150;
182
+
183
+ const cmd = [
184
+ this.binary, '-target', target,
185
+ '-severity', severity,
186
+ '-rate-limit', String(rateLimit),
187
+ '-jsonl', '-silent',
188
+ ];
189
+ if (opts.templates) cmd.push('-t', opts.templates);
190
+
191
+ return new Promise((resolve, reject) => {
192
+ const findings = [];
193
+ let proc;
194
+
195
+ try {
196
+ proc = spawn(cmd[0], cmd.slice(1), { stdio: ['pipe', 'pipe', 'pipe'] });
197
+ } catch (err) {
198
+ return reject(err);
199
+ }
200
+
201
+ proc.on('error', reject);
202
+
203
+ let stdoutBuf = '';
204
+ proc.stdout.on('data', (chunk) => {
205
+ stdoutBuf += chunk.toString();
206
+ let nl;
207
+ while ((nl = stdoutBuf.indexOf('\n')) !== -1) {
208
+ const line = stdoutBuf.slice(0, nl);
209
+ stdoutBuf = stdoutBuf.slice(nl + 1);
210
+ try {
211
+ const data = JSON.parse(line);
212
+ findings.push(new AsyncFinding({
213
+ id: data['template-id'] ?? '',
214
+ name: data.info?.name ?? '',
215
+ severity: data.info?.severity ?? 'info',
216
+ url: data['matched-at'] ?? target,
217
+ description: data.info?.description ?? '',
218
+ matchedAt: data['matched-at'] ?? '',
219
+ template: data.template ?? '',
220
+ timestamp: Date.now() / 1000,
221
+ }));
222
+ } catch { /* skip malformed lines */ }
223
+ }
224
+ });
225
+
226
+ const timer = setTimeout(() => {
227
+ proc.kill('SIGKILL');
228
+ }, this.timeout * 1000);
229
+
230
+ proc.on('close', () => {
231
+ clearTimeout(timer);
232
+ resolve(findings);
233
+ });
234
+ });
235
+ }
236
+ }
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // AsyncKatanaRunner
240
+ // ---------------------------------------------------------------------------
241
+
242
+ class AsyncKatanaRunner {
243
+ /**
244
+ * @param {object} opts
245
+ * @param {string} [opts.binary='katana']
246
+ * @param {number} [opts.timeout=120]
247
+ */
248
+ constructor(opts = {}) {
249
+ this.binary = opts.binary ?? 'katana';
250
+ this.timeout = opts.timeout ?? 120;
251
+ this._available = null;
252
+ }
253
+
254
+ isAvailable() {
255
+ if (this._available === null) {
256
+ this._available = whichSync(this.binary) !== null;
257
+ }
258
+ return this._available;
259
+ }
260
+
261
+ /**
262
+ * Run katana crawl asynchronously.
263
+ * @param {string} target
264
+ * @param {object} opts
265
+ * @param {number} [opts.depth=3]
266
+ * @param {number} [opts.concurrency=10]
267
+ * @returns {Promise<string[]>}
268
+ */
269
+ crawl(target, opts = {}) {
270
+ if (!this.isAvailable()) {
271
+ return Promise.reject(new Error('katana binary not found in PATH'));
272
+ }
273
+
274
+ const depth = opts.depth ?? 3;
275
+ const concurrency = opts.concurrency ?? 10;
276
+
277
+ const cmd = [
278
+ this.binary, '-u', target,
279
+ '-depth', String(depth),
280
+ '-concurrency', String(concurrency),
281
+ '-silent',
282
+ ];
283
+
284
+ return new Promise((resolve, reject) => {
285
+ const urls = [];
286
+ let proc;
287
+
288
+ try {
289
+ proc = spawn(cmd[0], cmd.slice(1), { stdio: ['pipe', 'pipe', 'pipe'] });
290
+ } catch (err) {
291
+ return reject(err);
292
+ }
293
+
294
+ proc.on('error', reject);
295
+
296
+ let stdoutBuf = '';
297
+ proc.stdout.on('data', (chunk) => {
298
+ stdoutBuf += chunk.toString();
299
+ let nl;
300
+ while ((nl = stdoutBuf.indexOf('\n')) !== -1) {
301
+ const line = stdoutBuf.slice(0, nl).trim();
302
+ stdoutBuf = stdoutBuf.slice(nl + 1);
303
+ if (line) urls.push(line);
304
+ }
305
+ });
306
+
307
+ const timer = setTimeout(() => {
308
+ proc.kill('SIGKILL');
309
+ }, this.timeout * 1000);
310
+
311
+ proc.on('close', () => {
312
+ clearTimeout(timer);
313
+ resolve(urls);
314
+ });
315
+ });
316
+ }
317
+ }
318
+
319
+ // ---------------------------------------------------------------------------
320
+ // AsyncScanManager
321
+ // ---------------------------------------------------------------------------
322
+
323
+ /**
324
+ * Map scan profile name → Nuclei severity filter string.
325
+ */
326
+ function severityForProfile(profile) {
327
+ const map = {
328
+ quick: 'critical,high',
329
+ standard: 'medium,high,critical',
330
+ deep: 'info,low,medium,high,critical',
331
+ pentest: 'info,low,medium,high,critical',
332
+ };
333
+ return map[profile] ?? 'medium,high,critical';
334
+ }
335
+
336
+ class AsyncScanManager {
337
+ /**
338
+ * @param {object} opts
339
+ * @param {number} [opts.maxConcurrent=3]
340
+ */
341
+ constructor(opts = {}) {
342
+ this.maxConcurrent = opts.maxConcurrent ?? 3;
343
+ this._jobs = new Map();
344
+ this._semaphore = new Semaphore(this.maxConcurrent);
345
+ this._nuclei = new AsyncNucleiRunner();
346
+ this._katana = new AsyncKatanaRunner();
347
+ }
348
+
349
+ /**
350
+ * Submit a scan job. Returns immediately with QUEUED status.
351
+ * @param {string} scanId
352
+ * @param {string} target
353
+ * @param {object} opts
354
+ * @param {string} [opts.profile='standard']
355
+ * @returns {AsyncScanResult}
356
+ */
357
+ submitScan(scanId, target, opts = {}) {
358
+ const result = new AsyncScanResult({
359
+ scanId,
360
+ target,
361
+ profile: opts.profile ?? 'standard',
362
+ });
363
+ this._jobs.set(scanId, result);
364
+ return result;
365
+ }
366
+
367
+ /**
368
+ * Execute a scan job asynchronously, respecting the semaphore.
369
+ * @param {string} scanId
370
+ * @returns {Promise<AsyncScanResult>}
371
+ */
372
+ async runScan(scanId) {
373
+ const result = this._jobs.get(scanId);
374
+ if (!result) throw new Error(`Scan ${scanId} not found`);
375
+
376
+ await this._semaphore.acquire();
377
+ try {
378
+ // Check if cancelled while waiting
379
+ if (result.status === AsyncScanStatus.CANCELLED) {
380
+ return result;
381
+ }
382
+ result.status = AsyncScanStatus.RUNNING;
383
+ result.startedAt = Date.now() / 1000;
384
+
385
+ try {
386
+ if (this._nuclei.isAvailable()) {
387
+ const findings = await this._nuclei.scan(result.target, {
388
+ severity: severityForProfile(result.profile),
389
+ });
390
+ result.findings = findings;
391
+ } else {
392
+ result.error = 'nuclei not available';
393
+ }
394
+ result.status = AsyncScanStatus.COMPLETED;
395
+ } catch (err) {
396
+ result.status = AsyncScanStatus.FAILED;
397
+ result.error = err.message;
398
+ }
399
+
400
+ result.completedAt = Date.now() / 1000;
401
+ } finally {
402
+ this._semaphore.release();
403
+ }
404
+
405
+ return result;
406
+ }
407
+
408
+ /**
409
+ * Get current status of a scan job.
410
+ * @param {string} scanId
411
+ * @returns {AsyncScanResult|null}
412
+ */
413
+ getStatus(scanId) {
414
+ return this._jobs.get(scanId) ?? null;
415
+ }
416
+
417
+ /**
418
+ * Get summary of all jobs.
419
+ * @returns {object}
420
+ */
421
+ getAllJobs() {
422
+ const out = {};
423
+ for (const [sid, r] of this._jobs) {
424
+ out[sid] = {
425
+ scanId: r.scanId,
426
+ target: r.target,
427
+ status: r.status,
428
+ findingsCount: r.findings.length,
429
+ error: r.error,
430
+ };
431
+ }
432
+ return out;
433
+ }
434
+
435
+ /**
436
+ * Cancel a queued scan.
437
+ * @param {string} scanId
438
+ * @returns {boolean}
439
+ */
440
+ cancelScan(scanId) {
441
+ const result = this._jobs.get(scanId);
442
+ if (result && result.status === AsyncScanStatus.QUEUED) {
443
+ result.status = AsyncScanStatus.CANCELLED;
444
+ return true;
445
+ }
446
+ return false;
447
+ }
448
+ }
449
+
450
+ // ---------------------------------------------------------------------------
451
+ // Convenience function
452
+ // ---------------------------------------------------------------------------
453
+
454
+ /**
455
+ * Run a one-shot async scan and return results.
456
+ * @param {string} target
457
+ * @param {object} opts
458
+ * @param {string} [opts.profile='standard']
459
+ * @param {string} [opts.scanId='']
460
+ * @returns {Promise<object>}
461
+ */
462
+ async function asyncScan(target, opts = {}) {
463
+ const scanId = opts.scanId || `scan-${randomBytes(4).toString('hex')}`;
464
+ const profile = opts.profile ?? 'standard';
465
+
466
+ const mgr = new AsyncScanManager();
467
+ mgr.submitScan(scanId, target, { profile });
468
+ const result = await mgr.runScan(scanId);
469
+
470
+ return {
471
+ scanId: result.scanId,
472
+ target: result.target,
473
+ status: result.status,
474
+ profile: result.profile,
475
+ findings: result.findings.map(f => ({
476
+ id: f.id,
477
+ name: f.name,
478
+ severity: f.severity,
479
+ url: f.url,
480
+ description: f.description,
481
+ })),
482
+ totalFindings: result.findings.length,
483
+ durationS: result.completedAt
484
+ ? Math.round((result.completedAt - result.startedAt) * 100) / 100
485
+ : 0,
486
+ error: result.error,
487
+ };
488
+ }
489
+
490
+ // ---------------------------------------------------------------------------
491
+ // Exports
492
+ // ---------------------------------------------------------------------------
493
+
494
+ export {
495
+ // Enums
496
+ AsyncScanStatus,
497
+ // Data classes
498
+ AsyncFinding,
499
+ AsyncScanResult,
500
+ // Runners
501
+ AsyncNucleiRunner,
502
+ AsyncKatanaRunner,
503
+ // Manager
504
+ AsyncScanManager,
505
+ Semaphore,
506
+ // Convenience
507
+ asyncScan,
508
+ // Utility
509
+ severityForProfile,
510
+ };