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,435 @@
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
+ * Runtime DOM XSS scanner using Playwright headless browser.
7
+ *
8
+ * Complements the static XSS scanner (xss-scanner.js) by executing JavaScript
9
+ * in a real browser context to detect XSS that only manifests at runtime.
10
+ *
11
+ * Playwright is an OPTIONAL dependency — scanner gracefully degrades when
12
+ * it's not installed. Do NOT add playwright to package.json.
13
+ *
14
+ * Ported from pipeline/dom_xss_scanner.py (390 LOC Python).
15
+ */
16
+
17
+ import { URL } from 'node:url';
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Payloads
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /** @type {Array<[string, string, string]>} [label, payload, detection] */
24
+ const XSS_PAYLOADS = Object.freeze([
25
+ ['script-alert', '<script>alert("CIPHER-XSS-1")</script>', 'dialog'],
26
+ ['img-onerror', '<img src=x onerror=alert("CIPHER-XSS-2")>', 'dialog'],
27
+ ['svg-onload', '<svg/onload=alert("CIPHER-XSS-3")>', 'dialog'],
28
+ ['body-onload', '<body onload=alert("CIPHER-XSS-4")>', 'dialog'],
29
+ ['event-handler', '" onfocus=alert("CIPHER-XSS-5") autofocus="', 'dialog'],
30
+ ['javascript-uri', 'javascript:alert("CIPHER-XSS-6")', 'dialog'],
31
+ ['template-literal', '${alert("CIPHER-XSS-7")}', 'dialog'],
32
+ ['iframe-srcdoc', '<iframe srcdoc="<script>alert(\'CIPHER-XSS-8\')</script>">', 'dialog'],
33
+ ]);
34
+
35
+ /** @type {Array<[string, string]>} [label, payload] */
36
+ const URL_PAYLOADS = Object.freeze([
37
+ ['reflected-script', '<script>alert("CIPHER-XSS-R1")</script>'],
38
+ ['reflected-img', '<img src=x onerror=alert("CIPHER-XSS-R2")>'],
39
+ ['reflected-svg', '<svg/onload=alert("CIPHER-XSS-R3")>'],
40
+ ['hash-injection', '#<img src=x onerror=alert("CIPHER-XSS-R4")>'],
41
+ ]);
42
+
43
+ // Common parameter names to test when URL has no existing params
44
+ const COMMON_PARAMS = ['q', 'search', 'query', 'id', 'name', 'page', 'url', 'redirect', 'next', 'callback'];
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Data classes
48
+ // ---------------------------------------------------------------------------
49
+
50
+ class DOMXSSFinding {
51
+ /**
52
+ * @param {object} opts
53
+ * @param {string} opts.url
54
+ * @param {string} opts.vector input | url-param | fragment | form
55
+ * @param {string} opts.inputName
56
+ * @param {string} opts.payload
57
+ * @param {string} opts.payloadLabel
58
+ * @param {boolean} [opts.confirmed=true]
59
+ * @param {string} [opts.reproduction='']
60
+ */
61
+ constructor(opts) {
62
+ this.url = opts.url;
63
+ this.vector = opts.vector;
64
+ this.inputName = opts.inputName;
65
+ this.payload = opts.payload;
66
+ this.payloadLabel = opts.payloadLabel;
67
+ this.confirmed = opts.confirmed !== false;
68
+ this.reproduction = opts.reproduction ?? '';
69
+ }
70
+
71
+ toDict() {
72
+ return {
73
+ url: this.url,
74
+ vector: this.vector,
75
+ input: this.inputName,
76
+ payload_label: this.payloadLabel,
77
+ confirmed: this.confirmed,
78
+ reproduction: this.reproduction,
79
+ };
80
+ }
81
+ }
82
+
83
+ class DOMXSSScanResult {
84
+ /**
85
+ * @param {object} opts
86
+ * @param {string} opts.target
87
+ * @param {number} [opts.pagesTested=0]
88
+ * @param {number} [opts.inputsTested=0]
89
+ * @param {number} [opts.paramsTested=0]
90
+ * @param {DOMXSSFinding[]} [opts.findings=[]]
91
+ * @param {string[]} [opts.errors=[]]
92
+ */
93
+ constructor(opts) {
94
+ this.target = opts.target;
95
+ this.pagesTested = opts.pagesTested ?? 0;
96
+ this.inputsTested = opts.inputsTested ?? 0;
97
+ this.paramsTested = opts.paramsTested ?? 0;
98
+ this.findings = opts.findings ?? [];
99
+ this.errors = opts.errors ?? [];
100
+ }
101
+
102
+ toDict() {
103
+ return {
104
+ target: this.target,
105
+ pages_tested: this.pagesTested,
106
+ inputs_tested: this.inputsTested,
107
+ params_tested: this.paramsTested,
108
+ findings: this.findings.map((f) => f.toDict()),
109
+ confirmed_xss: this.findings.length,
110
+ errors: this.errors,
111
+ };
112
+ }
113
+
114
+ summary() {
115
+ if (this.findings.length > 0) {
116
+ return (
117
+ `${this.findings.length} confirmed DOM XSS in ${this.target} ` +
118
+ `(${this.inputsTested} inputs, ${this.paramsTested} params tested)`
119
+ );
120
+ }
121
+ return `No DOM XSS found in ${this.target} (${this.inputsTested} inputs tested)`;
122
+ }
123
+ }
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // DOMXSSScanner
127
+ // ---------------------------------------------------------------------------
128
+
129
+ class DOMXSSScanner {
130
+ /**
131
+ * @param {object} [opts]
132
+ * @param {boolean} [opts.headless=true]
133
+ * @param {number} [opts.timeout=5000]
134
+ */
135
+ constructor(opts = {}) {
136
+ this._headless = opts.headless !== false;
137
+ this._timeout = opts.timeout ?? 5000;
138
+ }
139
+
140
+ /**
141
+ * Check if Playwright is available.
142
+ * @returns {Promise<boolean>}
143
+ */
144
+ async available() {
145
+ try {
146
+ await import('playwright');
147
+ return true;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Scan a URL for DOM XSS vulnerabilities.
155
+ * @param {string} url
156
+ * @param {object} [opts]
157
+ * @param {boolean} [opts.testParams=true]
158
+ * @param {boolean} [opts.testInputs=true]
159
+ * @param {boolean} [opts.testForms=true]
160
+ * @returns {Promise<DOMXSSScanResult>}
161
+ */
162
+ async scanUrl(url, opts = {}) {
163
+ const testParams = opts.testParams !== false;
164
+ const testInputs = opts.testInputs !== false;
165
+ const testForms = opts.testForms !== false;
166
+
167
+ let pw;
168
+ try {
169
+ pw = await import('playwright');
170
+ } catch {
171
+ return new DOMXSSScanResult({
172
+ target: url,
173
+ errors: ['Playwright not installed — run: npm install playwright && npx playwright install chromium'],
174
+ });
175
+ }
176
+
177
+ const result = new DOMXSSScanResult({ target: url });
178
+
179
+ try {
180
+ const browser = await pw.chromium.launch({ headless: this._headless });
181
+ const context = await browser.newContext({
182
+ ignoreHTTPSErrors: true,
183
+ javaScriptEnabled: true,
184
+ });
185
+
186
+ if (testParams) {
187
+ await this._testUrlParams(context, url, result);
188
+ }
189
+ if (testInputs) {
190
+ await this._testInputFields(context, url, result);
191
+ }
192
+ if (testForms) {
193
+ await this._testForms(context, url, result);
194
+ }
195
+
196
+ result.pagesTested++;
197
+ await context.close();
198
+ await browser.close();
199
+ } catch (err) {
200
+ result.errors.push(`Scanner error: ${err.message}`);
201
+ }
202
+
203
+ return result;
204
+ }
205
+
206
+ /**
207
+ * Inject payloads into URL query parameters.
208
+ * @private
209
+ */
210
+ async _testUrlParams(context, url, result) {
211
+ const parsed = new URL(url);
212
+ const params = parsed.searchParams;
213
+
214
+ if ([...params.keys()].length === 0) {
215
+ // Try common parameter names
216
+ for (const paramName of COMMON_PARAMS) {
217
+ for (const [label, payload] of URL_PAYLOADS) {
218
+ result.paramsTested++;
219
+ const injected = new URL(url);
220
+ injected.searchParams.set(paramName, payload);
221
+ const finding = await this._checkUrlXss(
222
+ context, injected.toString(), 'url-param', paramName, label, payload,
223
+ );
224
+ if (finding) {
225
+ result.findings.push(finding);
226
+ return; // One confirmed finding is enough
227
+ }
228
+ }
229
+ }
230
+ } else {
231
+ for (const paramName of params.keys()) {
232
+ for (const [label, payload] of URL_PAYLOADS) {
233
+ result.paramsTested++;
234
+ const injected = new URL(url);
235
+ injected.searchParams.set(paramName, payload);
236
+ const finding = await this._checkUrlXss(
237
+ context, injected.toString(), 'url-param', paramName, label, payload,
238
+ );
239
+ if (finding) {
240
+ result.findings.push(finding);
241
+ break; // Move to next param
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ // Test fragment injection
248
+ for (const [label, payload] of URL_PAYLOADS) {
249
+ if (label.startsWith('hash')) {
250
+ result.paramsTested++;
251
+ const injected = `${url}${payload}`;
252
+ const finding = await this._checkUrlXss(
253
+ context, injected, 'fragment', '#', label, payload,
254
+ );
255
+ if (finding) {
256
+ result.findings.push(finding);
257
+ }
258
+ }
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Inject payloads into input fields on the page.
264
+ * @private
265
+ */
266
+ async _testInputFields(context, url, result) {
267
+ const page = await context.newPage();
268
+ const dialogs = [];
269
+ page.on('dialog', async (d) => {
270
+ dialogs.push(d.message());
271
+ await d.accept();
272
+ });
273
+
274
+ try {
275
+ await page.goto(url, { waitUntil: 'networkidle', timeout: this._timeout * 2 });
276
+ } catch (err) {
277
+ result.errors.push(`Navigation failed: ${err.message}`);
278
+ await page.close();
279
+ return;
280
+ }
281
+
282
+ const inputs = await page.$$(
283
+ "input[type='text'], input[type='search'], input:not([type]), textarea, [contenteditable='true']",
284
+ );
285
+
286
+ for (const inp of inputs) {
287
+ const inputName =
288
+ (await inp.getAttribute('name')) ||
289
+ (await inp.getAttribute('id')) ||
290
+ (await inp.getAttribute('placeholder')) ||
291
+ 'unnamed';
292
+
293
+ for (const [label, payload] of XSS_PAYLOADS) {
294
+ result.inputsTested++;
295
+ dialogs.length = 0;
296
+ try {
297
+ await inp.fill('');
298
+ await inp.fill(payload);
299
+ await inp.dispatchEvent('change');
300
+ await inp.dispatchEvent('blur');
301
+ await page.waitForTimeout(500);
302
+
303
+ if (dialogs.some((d) => d.includes('CIPHER-XSS'))) {
304
+ result.findings.push(
305
+ new DOMXSSFinding({
306
+ url,
307
+ vector: 'input',
308
+ inputName,
309
+ payload,
310
+ payloadLabel: label,
311
+ reproduction: `Fill input '${inputName}' with: ${payload}`,
312
+ }),
313
+ );
314
+ break; // One payload per input
315
+ }
316
+ } catch {
317
+ continue;
318
+ }
319
+ }
320
+ }
321
+ await page.close();
322
+ }
323
+
324
+ /**
325
+ * Test form submissions with XSS payloads.
326
+ * @private
327
+ */
328
+ async _testForms(context, url, result) {
329
+ const page = await context.newPage();
330
+ const dialogs = [];
331
+ page.on('dialog', async (d) => {
332
+ dialogs.push(d.message());
333
+ await d.accept();
334
+ });
335
+
336
+ try {
337
+ await page.goto(url, { waitUntil: 'networkidle', timeout: this._timeout * 2 });
338
+ } catch {
339
+ await page.close();
340
+ return;
341
+ }
342
+
343
+ const forms = await page.$$('form');
344
+ for (const form of forms) {
345
+ const formInputs = await form.$$(
346
+ "input[type='text'], input:not([type]), textarea",
347
+ );
348
+ if (formInputs.length === 0) continue;
349
+
350
+ const payload = XSS_PAYLOADS[0][1]; // script-alert
351
+ dialogs.length = 0;
352
+ try {
353
+ for (const fi of formInputs) {
354
+ await fi.fill(payload);
355
+ }
356
+ const submitBtn = await form.$("button[type='submit'], input[type='submit']");
357
+ if (submitBtn) {
358
+ await submitBtn.click();
359
+ } else {
360
+ await form.dispatchEvent('submit');
361
+ }
362
+ await page.waitForTimeout(1000);
363
+
364
+ if (dialogs.some((d) => d.includes('CIPHER-XSS'))) {
365
+ const formId =
366
+ (await form.getAttribute('id')) ||
367
+ (await form.getAttribute('action')) ||
368
+ 'form';
369
+ result.findings.push(
370
+ new DOMXSSFinding({
371
+ url,
372
+ vector: 'form',
373
+ inputName: formId,
374
+ payload,
375
+ payloadLabel: 'form-submit',
376
+ reproduction: `Submit form '${formId}' with XSS in all text inputs`,
377
+ }),
378
+ );
379
+ }
380
+ } catch {
381
+ continue;
382
+ }
383
+ }
384
+ await page.close();
385
+ }
386
+
387
+ /**
388
+ * Navigate to an injected URL and check for XSS execution.
389
+ * @private
390
+ */
391
+ async _checkUrlXss(context, url, vector, paramName, label, payload) {
392
+ const page = await context.newPage();
393
+ const dialogs = [];
394
+ page.on('dialog', async (d) => {
395
+ dialogs.push(d.message());
396
+ await d.accept();
397
+ });
398
+
399
+ try {
400
+ await page.goto(url, { waitUntil: 'networkidle', timeout: this._timeout });
401
+ await page.waitForTimeout(500);
402
+ } catch {
403
+ // Navigation errors are expected for some payloads
404
+ } finally {
405
+ await page.close();
406
+ }
407
+
408
+ if (dialogs.some((d) => d.includes('CIPHER-XSS'))) {
409
+ return new DOMXSSFinding({
410
+ url,
411
+ vector,
412
+ inputName: paramName,
413
+ payload,
414
+ payloadLabel: label,
415
+ reproduction: `Navigate to: ${url}`,
416
+ });
417
+ }
418
+ return null;
419
+ }
420
+ }
421
+
422
+ // ---------------------------------------------------------------------------
423
+ // Exports
424
+ // ---------------------------------------------------------------------------
425
+
426
+ export {
427
+ // Payloads (for testing/inspection)
428
+ XSS_PAYLOADS,
429
+ URL_PAYLOADS,
430
+ // Data classes
431
+ DOMXSSFinding,
432
+ DOMXSSScanResult,
433
+ // Scanner
434
+ DOMXSSScanner,
435
+ };