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,525 @@
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 Nuclei Template Manager — pull, version, and manage nuclei templates.
7
+ *
8
+ * Provides template management beyond basic execution:
9
+ * - Pull/update templates from nuclei-templates repository
10
+ * - Custom template generation from scan findings
11
+ * - Template categorization and search
12
+ * - Workflow chain composition
13
+ */
14
+
15
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
16
+ import { join, relative } from 'node:path';
17
+ import { execFileSync } from 'node:child_process';
18
+ import { homedir } from 'node:os';
19
+
20
+ // Default template directory
21
+ const DEFAULT_TEMPLATE_DIR =
22
+ process.env.CIPHER_TEMPLATE_DIR ||
23
+ join(homedir(), '.config', 'cipher', 'nuclei-templates');
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Data classes
27
+ // ---------------------------------------------------------------------------
28
+
29
+ class TemplateInfo {
30
+ /**
31
+ * @param {object} opts
32
+ * @param {string} opts.id
33
+ * @param {string} opts.name
34
+ * @param {string} [opts.severity]
35
+ * @param {string[]} [opts.tags]
36
+ * @param {string} [opts.author]
37
+ * @param {string} [opts.description]
38
+ * @param {string[]} [opts.reference]
39
+ * @param {string} [opts.filePath]
40
+ * @param {string} [opts.protocol]
41
+ * @param {string} [opts.category]
42
+ */
43
+ constructor(opts = {}) {
44
+ this.id = opts.id || '';
45
+ this.name = opts.name || '';
46
+ this.severity = opts.severity || 'info';
47
+ this.tags = opts.tags || [];
48
+ this.author = opts.author || '';
49
+ this.description = opts.description || '';
50
+ this.reference = opts.reference || [];
51
+ this.filePath = opts.filePath || '';
52
+ this.protocol = opts.protocol || 'http';
53
+ this.category = opts.category || '';
54
+ }
55
+
56
+ toDict() {
57
+ return {
58
+ id: this.id,
59
+ name: this.name,
60
+ severity: this.severity,
61
+ tags: this.tags,
62
+ author: this.author,
63
+ description: this.description,
64
+ reference: this.reference,
65
+ file_path: this.filePath,
66
+ protocol: this.protocol,
67
+ category: this.category,
68
+ };
69
+ }
70
+ }
71
+
72
+ class TemplateCollection {
73
+ /**
74
+ * @param {object} opts
75
+ * @param {string} [opts.name]
76
+ * @param {string} [opts.version]
77
+ * @param {TemplateInfo[]} [opts.templates]
78
+ * @param {string} [opts.updatedAt]
79
+ * @param {string} [opts.source]
80
+ */
81
+ constructor(opts = {}) {
82
+ this.name = opts.name || '';
83
+ this.version = opts.version || '';
84
+ this.templates = opts.templates || [];
85
+ this.updatedAt = opts.updatedAt || new Date().toISOString();
86
+ this.source = opts.source || '';
87
+ }
88
+
89
+ get count() {
90
+ return this.templates.length;
91
+ }
92
+
93
+ bySeverity(severity) {
94
+ return this.templates.filter((t) => t.severity === severity);
95
+ }
96
+
97
+ byTag(tag) {
98
+ return this.templates.filter((t) => t.tags.includes(tag));
99
+ }
100
+
101
+ byProtocol(protocol) {
102
+ return this.templates.filter((t) => t.protocol === protocol);
103
+ }
104
+
105
+ search(query) {
106
+ const q = query.toLowerCase();
107
+ return this.templates.filter(
108
+ (t) =>
109
+ t.id.toLowerCase().includes(q) ||
110
+ t.name.toLowerCase().includes(q) ||
111
+ t.description.toLowerCase().includes(q) ||
112
+ t.tags.some((tag) => tag.toLowerCase().includes(q)),
113
+ );
114
+ }
115
+
116
+ toDict() {
117
+ return {
118
+ name: this.name,
119
+ version: this.version,
120
+ count: this.count,
121
+ updated_at: this.updatedAt,
122
+ source: this.source,
123
+ severity_breakdown: {
124
+ critical: this.bySeverity('critical').length,
125
+ high: this.bySeverity('high').length,
126
+ medium: this.bySeverity('medium').length,
127
+ low: this.bySeverity('low').length,
128
+ info: this.bySeverity('info').length,
129
+ },
130
+ };
131
+ }
132
+ }
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // NucleiTemplateManager
136
+ // ---------------------------------------------------------------------------
137
+
138
+ /**
139
+ * Resolve the nuclei binary path.
140
+ * @returns {string|null}
141
+ */
142
+ function _whichNuclei() {
143
+ try {
144
+ const result = execFileSync('which', ['nuclei'], {
145
+ encoding: 'utf8',
146
+ timeout: 5000,
147
+ });
148
+ return result.trim() || null;
149
+ } catch {
150
+ return null;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Recursively list all .yaml files under a directory.
156
+ * @param {string} dir
157
+ * @returns {string[]}
158
+ */
159
+ function _walkYaml(dir) {
160
+ const results = [];
161
+ if (!existsSync(dir)) return results;
162
+
163
+ let entries;
164
+ try {
165
+ entries = readdirSync(dir, { withFileTypes: true });
166
+ } catch {
167
+ return results;
168
+ }
169
+
170
+ for (const entry of entries) {
171
+ const fullPath = join(dir, entry.name);
172
+ if (entry.isDirectory()) {
173
+ results.push(..._walkYaml(fullPath));
174
+ } else if (entry.isFile() && entry.name.endsWith('.yaml')) {
175
+ results.push(fullPath);
176
+ }
177
+ }
178
+ return results;
179
+ }
180
+
181
+ class NucleiTemplateManager {
182
+ /**
183
+ * @param {object} [opts]
184
+ * @param {string} [opts.templateDir]
185
+ */
186
+ constructor(opts = {}) {
187
+ this.templateDir = opts.templateDir || DEFAULT_TEMPLATE_DIR;
188
+ this._nucleiPath = _whichNuclei();
189
+ /** @type {TemplateCollection|null} */
190
+ this._collection = null;
191
+ }
192
+
193
+ /** @returns {boolean} */
194
+ get available() {
195
+ return this._nucleiPath !== null;
196
+ }
197
+
198
+ /** @returns {boolean} */
199
+ get templatesExist() {
200
+ if (!existsSync(this.templateDir)) return false;
201
+ // Check for at least one .yaml file (shallow check)
202
+ return _walkYaml(this.templateDir).length > 0;
203
+ }
204
+
205
+ /**
206
+ * Pull or update nuclei templates from the official repository.
207
+ * @param {boolean} [force=false]
208
+ * @returns {object}
209
+ */
210
+ pullTemplates(force = false) {
211
+ if (this.templatesExist && !force) {
212
+ return {
213
+ status: 'already_exists',
214
+ path: this.templateDir,
215
+ message: 'Templates already present. Use force=true to update.',
216
+ };
217
+ }
218
+
219
+ // Try nuclei -update-templates first
220
+ if (this._nucleiPath) {
221
+ try {
222
+ execFileSync(this._nucleiPath, ['-update-templates', '-ud', this.templateDir], {
223
+ encoding: 'utf8',
224
+ timeout: 300000,
225
+ stdio: ['pipe', 'pipe', 'pipe'],
226
+ });
227
+ const count = _walkYaml(this.templateDir).length;
228
+ return {
229
+ status: 'updated',
230
+ path: this.templateDir,
231
+ template_count: count,
232
+ method: 'nuclei-update',
233
+ };
234
+ } catch (e) {
235
+ // Fall through to git
236
+ }
237
+ }
238
+
239
+ // Fallback: git clone
240
+ let gitPath;
241
+ try {
242
+ gitPath = execFileSync('which', ['git'], { encoding: 'utf8', timeout: 5000 }).trim();
243
+ } catch {
244
+ gitPath = null;
245
+ }
246
+
247
+ if (gitPath) {
248
+ try {
249
+ if (existsSync(join(this.templateDir, '.git'))) {
250
+ execFileSync(gitPath, ['-C', this.templateDir, 'pull', '--depth=1'], {
251
+ encoding: 'utf8',
252
+ timeout: 300000,
253
+ stdio: ['pipe', 'pipe', 'pipe'],
254
+ });
255
+ } else {
256
+ execFileSync(
257
+ gitPath,
258
+ [
259
+ 'clone',
260
+ '--depth=1',
261
+ 'https://github.com/projectdiscovery/nuclei-templates.git',
262
+ this.templateDir,
263
+ ],
264
+ { encoding: 'utf8', timeout: 300000, stdio: ['pipe', 'pipe', 'pipe'] },
265
+ );
266
+ }
267
+ const count = _walkYaml(this.templateDir).length;
268
+ return {
269
+ status: 'cloned',
270
+ path: this.templateDir,
271
+ template_count: count,
272
+ method: 'git-clone',
273
+ };
274
+ } catch {
275
+ // Fall through
276
+ }
277
+ }
278
+
279
+ return {
280
+ status: 'error',
281
+ message: 'Neither nuclei nor git available for template pull',
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Scan template directory and build an indexed collection.
287
+ * @returns {TemplateCollection}
288
+ */
289
+ indexTemplates() {
290
+ if (!this.templatesExist) {
291
+ return new TemplateCollection({ name: 'empty', version: '0.0.0' });
292
+ }
293
+
294
+ const templates = [];
295
+ const yamlFiles = _walkYaml(this.templateDir).sort();
296
+ for (const yamlFile of yamlFiles) {
297
+ const info = this._parseTemplateFile(yamlFile);
298
+ if (info) templates.push(info);
299
+ }
300
+
301
+ this._collection = new TemplateCollection({
302
+ name: 'nuclei-templates',
303
+ version: this._detectVersion(),
304
+ templates,
305
+ source: this.templateDir,
306
+ });
307
+ return this._collection;
308
+ }
309
+
310
+ /**
311
+ * Search templates by ID, name, description, or tags.
312
+ * @param {string} query
313
+ * @returns {object[]}
314
+ */
315
+ searchTemplates(query) {
316
+ if (!this._collection) this.indexTemplates();
317
+ if (!this._collection) return [];
318
+ return this._collection.search(query).slice(0, 50).map((t) => t.toDict());
319
+ }
320
+
321
+ /**
322
+ * Get template paths suitable for a scan type.
323
+ * @param {string} [scanType='pentest']
324
+ * @param {string} [severity]
325
+ * @param {string[]} [tags]
326
+ * @returns {string[]}
327
+ */
328
+ getTemplatesForScan(scanType = 'pentest', severity, tags) {
329
+ const SCAN_TYPE_TAGS = {
330
+ pentest: ['cve', 'sqli', 'xss', 'rce', 'lfi', 'ssrf', 'injection'],
331
+ recon: ['tech', 'detect', 'fingerprint', 'exposure', 'panel'],
332
+ compliance: ['misconfig', 'default-login', 'exposure', 'unauth'],
333
+ fuzzing: ['fuzz', 'brute', 'spray'],
334
+ cve: ['cve'],
335
+ exposure: ['exposure', 'misconfig', 'default-login', 'unauth', 'panel'],
336
+ };
337
+
338
+ const targetTags = [...(SCAN_TYPE_TAGS[scanType] || ['cve'])];
339
+ if (tags) targetTags.push(...tags);
340
+
341
+ if (!this._collection) this.indexTemplates();
342
+ if (!this._collection) return [];
343
+
344
+ const matched = [];
345
+ for (const t of this._collection.templates) {
346
+ if (severity && t.severity !== severity) continue;
347
+ if (t.tags.some((tag) => targetTags.includes(tag))) {
348
+ if (t.filePath) matched.push(t.filePath);
349
+ }
350
+ }
351
+ return matched.slice(0, 500);
352
+ }
353
+
354
+ /**
355
+ * Generate a custom nuclei template from a security finding.
356
+ * @param {object} finding
357
+ * @returns {string}
358
+ */
359
+ generateTemplate(finding) {
360
+ const title = (finding.title || 'custom-check').toLowerCase().replace(/\s+/g, '-');
361
+ const desc = finding.description || 'Custom security check';
362
+ const severity = finding.severity || 'medium';
363
+ const method = (finding.method || 'GET').toUpperCase();
364
+ const path = finding.path || '/';
365
+ const matchers = finding.matchers || [];
366
+
367
+ let matcherYaml;
368
+ if (matchers.length) {
369
+ matcherYaml = matchers
370
+ .map((m) => ` - type: word\n words:\n - "${m}"`)
371
+ .join('\n');
372
+ } else {
373
+ matcherYaml = ' - type: status\n status:\n - 200';
374
+ }
375
+
376
+ return `id: cipher-${title}
377
+
378
+ info:
379
+ name: ${finding.title || 'Custom Check'}
380
+ author: cipher-auto
381
+ severity: ${severity}
382
+ description: |
383
+ ${desc}
384
+ tags: custom,cipher-generated
385
+ metadata:
386
+ generated-by: cipher-template-manager
387
+ generated-at: ${new Date().toISOString()}
388
+
389
+ http:
390
+ - method: ${method}
391
+ path:
392
+ - "{{BaseURL}}${path}"
393
+ matchers:
394
+ ${matcherYaml}
395
+ `;
396
+ }
397
+
398
+ /**
399
+ * Generate a nuclei workflow YAML that chains multiple templates.
400
+ * @param {string} name
401
+ * @param {string[]} templateIds
402
+ * @returns {string}
403
+ */
404
+ generateWorkflow(name, templateIds) {
405
+ const subtemplates = templateIds
406
+ .map((tid) => ` - template: ${tid}`)
407
+ .join('\n');
408
+ return `id: cipher-workflow-${name}
409
+
410
+ info:
411
+ name: CIPHER Workflow - ${name}
412
+ author: cipher
413
+ severity: info
414
+
415
+ workflows:
416
+ - template: ${templateIds[0] || 'missing'}
417
+ subtemplates:
418
+ ${subtemplates}
419
+ `;
420
+ }
421
+
422
+ /**
423
+ * Return template collection statistics.
424
+ * @returns {object}
425
+ */
426
+ stats() {
427
+ if (!this._collection) this.indexTemplates();
428
+ if (!this._collection) {
429
+ return { status: 'no_templates', path: this.templateDir };
430
+ }
431
+ const protocols = {};
432
+ for (const p of [
433
+ 'http', 'dns', 'network', 'file', 'headless', 'javascript', 'code', 'ssl',
434
+ ]) {
435
+ protocols[p] = this._collection.byProtocol(p).length;
436
+ }
437
+ return {
438
+ ...this._collection.toDict(),
439
+ protocols,
440
+ };
441
+ }
442
+
443
+ // -- private helpers --
444
+
445
+ /**
446
+ * Parse a nuclei template YAML file for metadata (fast string splitting).
447
+ * @param {string} filePath
448
+ * @returns {TemplateInfo|null}
449
+ */
450
+ _parseTemplateFile(filePath) {
451
+ try {
452
+ const content = readFileSync(filePath, 'utf8');
453
+ const info = new TemplateInfo({ filePath });
454
+
455
+ const lines = content.split('\n').slice(0, 50);
456
+ for (const rawLine of lines) {
457
+ const line = rawLine.trim();
458
+ if (line.startsWith('id:')) {
459
+ info.id = line.split(':', 2)[1].trim();
460
+ } else if (line.startsWith('name:')) {
461
+ info.name = line.split(':', 2)[1].trim();
462
+ } else if (line.startsWith('severity:')) {
463
+ info.severity = line.split(':', 2)[1].trim();
464
+ } else if (line.startsWith('author:')) {
465
+ info.author = line.split(':', 2)[1].trim();
466
+ } else if (line.startsWith('description:')) {
467
+ info.description = line.split(':', 2)[1].trim();
468
+ } else if (line.startsWith('tags:')) {
469
+ info.tags = line
470
+ .split(':', 2)[1]
471
+ .split(',')
472
+ .map((t) => t.trim())
473
+ .filter(Boolean);
474
+ }
475
+ }
476
+
477
+ // Detect protocol and category from path relative to template dir
478
+ try {
479
+ const rel = relative(this.templateDir, filePath);
480
+ const parts = rel.split('/');
481
+ if (parts.length > 0) {
482
+ info.category = parts[0];
483
+ const knownProtocols = [
484
+ 'http', 'dns', 'network', 'file', 'headless',
485
+ 'javascript', 'code', 'ssl', 'cloud',
486
+ ];
487
+ info.protocol = knownProtocols.includes(parts[0]) ? parts[0] : 'http';
488
+ }
489
+ } catch {
490
+ // relative path may fail if paths are on different roots — ignore
491
+ }
492
+
493
+ if (!info.id) return null;
494
+ return info;
495
+ } catch {
496
+ return null;
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Detect template version from checksum or date.
502
+ * @returns {string}
503
+ */
504
+ _detectVersion() {
505
+ const checksumFile = join(this.templateDir, 'cves.json-checksum.txt');
506
+ try {
507
+ if (existsSync(checksumFile)) {
508
+ return readFileSync(checksumFile, 'utf8').trim().slice(0, 12);
509
+ }
510
+ } catch {
511
+ // ignore
512
+ }
513
+ return new Date().toISOString().slice(0, 10).replace(/-/g, '');
514
+ }
515
+ }
516
+
517
+ export {
518
+ // Constants
519
+ DEFAULT_TEMPLATE_DIR,
520
+ // Data classes
521
+ TemplateInfo,
522
+ TemplateCollection,
523
+ // Manager
524
+ NucleiTemplateManager,
525
+ };