cipher-security 2.0.8 → 2.2.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 (70) hide show
  1. package/bin/cipher.js +11 -1
  2. package/lib/agent-runtime/handlers/architect.js +199 -0
  3. package/lib/agent-runtime/handlers/base.js +240 -0
  4. package/lib/agent-runtime/handlers/blue.js +220 -0
  5. package/lib/agent-runtime/handlers/incident.js +161 -0
  6. package/lib/agent-runtime/handlers/privacy.js +190 -0
  7. package/lib/agent-runtime/handlers/purple.js +209 -0
  8. package/lib/agent-runtime/handlers/recon.js +174 -0
  9. package/lib/agent-runtime/handlers/red.js +246 -0
  10. package/lib/agent-runtime/handlers/researcher.js +170 -0
  11. package/lib/agent-runtime/handlers.js +35 -0
  12. package/lib/agent-runtime/index.js +196 -0
  13. package/lib/agent-runtime/parser.js +316 -0
  14. package/lib/analyze/consistency.js +566 -0
  15. package/lib/analyze/constitution.js +110 -0
  16. package/lib/analyze/sharding.js +251 -0
  17. package/lib/autonomous/agent-tool.js +165 -0
  18. package/lib/autonomous/feedback-loop.js +13 -6
  19. package/lib/autonomous/framework.js +17 -0
  20. package/lib/autonomous/handoff.js +506 -0
  21. package/lib/autonomous/modes/blue.js +26 -0
  22. package/lib/autonomous/modes/red.js +585 -0
  23. package/lib/autonomous/modes/researcher.js +322 -0
  24. package/lib/autonomous/researcher.js +12 -45
  25. package/lib/autonomous/runner.js +9 -537
  26. package/lib/benchmark/agent.js +88 -26
  27. package/lib/benchmark/baselines.js +3 -0
  28. package/lib/benchmark/claude-code-solver.js +254 -0
  29. package/lib/benchmark/cognitive.js +283 -0
  30. package/lib/benchmark/index.js +12 -2
  31. package/lib/benchmark/knowledge.js +281 -0
  32. package/lib/benchmark/llm.js +156 -15
  33. package/lib/benchmark/models.js +5 -2
  34. package/lib/benchmark/nyu-ctf.js +192 -0
  35. package/lib/benchmark/overthewire.js +347 -0
  36. package/lib/benchmark/picoctf.js +281 -0
  37. package/lib/benchmark/prompts.js +280 -0
  38. package/lib/benchmark/registry.js +219 -0
  39. package/lib/benchmark/remote-solver.js +356 -0
  40. package/lib/benchmark/remote-target.js +263 -0
  41. package/lib/benchmark/reporter.js +35 -0
  42. package/lib/benchmark/runner.js +174 -10
  43. package/lib/benchmark/sandbox.js +35 -0
  44. package/lib/benchmark/scorer.js +22 -4
  45. package/lib/benchmark/solver.js +34 -1
  46. package/lib/benchmark/tools.js +262 -16
  47. package/lib/commands.js +9 -0
  48. package/lib/execution/council.js +434 -0
  49. package/lib/execution/parallel.js +292 -0
  50. package/lib/gates/circuit-breaker.js +135 -0
  51. package/lib/gates/confidence.js +302 -0
  52. package/lib/gates/corrections.js +219 -0
  53. package/lib/gates/self-check.js +245 -0
  54. package/lib/gateway/commands.js +727 -0
  55. package/lib/guardrails/engine.js +364 -0
  56. package/lib/mcp/server.js +349 -3
  57. package/lib/memory/compressor.js +94 -7
  58. package/lib/pipeline/hooks.js +288 -0
  59. package/lib/pipeline/index.js +11 -0
  60. package/lib/review/budget.js +210 -0
  61. package/lib/review/engine.js +526 -0
  62. package/lib/review/layers/acceptance-auditor.js +279 -0
  63. package/lib/review/layers/blind-hunter.js +500 -0
  64. package/lib/review/layers/defense-in-depth.js +209 -0
  65. package/lib/review/layers/edge-case-hunter.js +266 -0
  66. package/lib/review/panel.js +519 -0
  67. package/lib/review/two-stage.js +244 -0
  68. package/lib/session/cost-tracker.js +203 -0
  69. package/lib/session/logger.js +349 -0
  70. package/package.json +1 -1
@@ -0,0 +1,292 @@
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
+ * Parallel Assessment Engine — wave-based concurrent security assessment.
7
+ *
8
+ * Dispatches independent domain assessments in parallel (wave 1),
9
+ * then cross-references results in a synthesis phase (wave 2).
10
+ *
11
+ * Pattern: Superpowers subagent dispatch + SuperClaude parallel execution.
12
+ *
13
+ * @module execution/parallel
14
+ */
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Assessment result types
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /**
21
+ * @typedef {Object} DomainAssessment
22
+ * @property {string} domain — Assessment domain
23
+ * @property {string} status — 'completed' | 'failed' | 'skipped'
24
+ * @property {Object} findings — Domain-specific findings
25
+ * @property {number} durationMs — Execution time
26
+ * @property {string|null} error — Error message if failed
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} BriefingResult
31
+ * @property {DomainAssessment[]} assessments — Individual domain results
32
+ * @property {Object} synthesis — Cross-domain synthesis
33
+ * @property {number} totalDurationMs — Total wall-clock time
34
+ * @property {{ completed: number, failed: number, skipped: number }} summary
35
+ */
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // ParallelAssessor
39
+ // ---------------------------------------------------------------------------
40
+
41
+ export class ParallelAssessor {
42
+ /**
43
+ * @param {{ assessors?: Record<string, Function>, timeoutMs?: number }} opts
44
+ */
45
+ constructor(opts = {}) {
46
+ this._assessors = opts.assessors || {};
47
+ this._timeoutMs = opts.timeoutMs || 30000;
48
+ }
49
+
50
+ /**
51
+ * Register a domain assessor function.
52
+ * @param {string} domain — Domain name (e.g., 'attack_surface', 'threat_profile')
53
+ * @param {Function} fn — Async function(target, context) → findings object
54
+ */
55
+ register(domain, fn) {
56
+ this._assessors[domain] = fn;
57
+ }
58
+
59
+ /**
60
+ * Run all registered assessors in parallel (Wave 1), then synthesize (Wave 2).
61
+ * @param {string} target — Assessment target
62
+ * @param {Object} [context] — Additional context
63
+ * @returns {Promise<BriefingResult>}
64
+ */
65
+ async assess(target, context = {}) {
66
+ const startTime = Date.now();
67
+
68
+ // Wave 1: Parallel domain assessments
69
+ const domains = Object.keys(this._assessors);
70
+ const wave1 = await Promise.allSettled(
71
+ domains.map(async (domain) => {
72
+ const domainStart = Date.now();
73
+ try {
74
+ const timeoutPromise = new Promise((_, reject) =>
75
+ setTimeout(() => reject(new Error(`Timeout after ${this._timeoutMs}ms`)), this._timeoutMs)
76
+ );
77
+ const findings = await Promise.race([
78
+ this._assessors[domain](target, context),
79
+ timeoutPromise,
80
+ ]);
81
+ return {
82
+ domain,
83
+ status: 'completed',
84
+ findings: findings || {},
85
+ durationMs: Date.now() - domainStart,
86
+ error: null,
87
+ };
88
+ } catch (err) {
89
+ return {
90
+ domain,
91
+ status: 'failed',
92
+ findings: {},
93
+ durationMs: Date.now() - domainStart,
94
+ error: err.message,
95
+ };
96
+ }
97
+ })
98
+ );
99
+
100
+ const assessments = wave1.map(r =>
101
+ r.status === 'fulfilled' ? r.value : {
102
+ domain: 'unknown',
103
+ status: 'failed',
104
+ findings: {},
105
+ durationMs: 0,
106
+ error: r.reason?.message || 'Unknown error',
107
+ }
108
+ );
109
+
110
+ // Wave 2: Cross-domain synthesis
111
+ const synthesis = this._synthesize(assessments, target);
112
+
113
+ const totalDurationMs = Date.now() - startTime;
114
+ const completed = assessments.filter(a => a.status === 'completed').length;
115
+ const failed = assessments.filter(a => a.status === 'failed').length;
116
+ const skipped = assessments.filter(a => a.status === 'skipped').length;
117
+
118
+ return {
119
+ assessments,
120
+ synthesis,
121
+ totalDurationMs,
122
+ summary: { completed, failed, skipped },
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Cross-reference domain assessments and produce synthesis.
128
+ * @private
129
+ */
130
+ _synthesize(assessments, target) {
131
+ const completed = assessments.filter(a => a.status === 'completed');
132
+ if (completed.length === 0) {
133
+ return { target, gaps: [], priorities: [], crossReferences: [], overallRisk: 'unknown' };
134
+ }
135
+
136
+ // Cross-reference: find gaps between attack surface and detection coverage
137
+ const attackFindings = completed.find(a => a.domain === 'attack_surface')?.findings || {};
138
+ const detectionFindings = completed.find(a => a.domain === 'detection_coverage')?.findings || {};
139
+
140
+ const gaps = [];
141
+ if (attackFindings.techniques && detectionFindings.coveredTechniques) {
142
+ const uncovered = (attackFindings.techniques || []).filter(
143
+ t => !(detectionFindings.coveredTechniques || []).includes(t)
144
+ );
145
+ for (const t of uncovered) {
146
+ gaps.push({ technique: t, type: 'detection_gap', severity: 'high' });
147
+ }
148
+ }
149
+
150
+ // Prioritize by cross-domain risk
151
+ const priorities = completed
152
+ .flatMap(a => Object.entries(a.findings).map(([k, v]) => ({ domain: a.domain, finding: k, value: v })))
153
+ .slice(0, 20);
154
+
155
+ return {
156
+ target,
157
+ gaps,
158
+ priorities,
159
+ crossReferences: completed.map(a => a.domain),
160
+ overallRisk: gaps.length > 5 ? 'critical' : gaps.length > 2 ? 'high' : gaps.length > 0 ? 'medium' : 'low',
161
+ };
162
+ }
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // Engagement Constitution
167
+ // ---------------------------------------------------------------------------
168
+
169
+ /**
170
+ * @typedef {Object} Constitution
171
+ * @property {string} engagementId
172
+ * @property {string} target
173
+ * @property {Object} scope — { inScope: string[], outOfScope: string[] }
174
+ * @property {Object} rulesOfEngagement — { passiveOnly: boolean, activeScanAllowed: boolean, exploitAllowed: boolean, socialEngAllowed: boolean }
175
+ * @property {string[]} complianceFrameworks — Applicable frameworks
176
+ * @property {Object} severityClassification — Custom severity criteria
177
+ * @property {string} reportingFormat — 'json' | 'markdown' | 'sarif'
178
+ * @property {string} createdAt
179
+ */
180
+
181
+ export class EngagementConstitution {
182
+ /**
183
+ * @param {Partial<Constitution>} opts
184
+ */
185
+ constructor(opts = {}) {
186
+ this.engagementId = opts.engagementId || `ENG-${Date.now().toString(36)}`;
187
+ this.target = opts.target || '';
188
+ this.scope = opts.scope || { inScope: [], outOfScope: [] };
189
+ this.rulesOfEngagement = opts.rulesOfEngagement || {
190
+ passiveOnly: false,
191
+ activeScanAllowed: true,
192
+ exploitAllowed: false,
193
+ socialEngAllowed: false,
194
+ };
195
+ this.complianceFrameworks = opts.complianceFrameworks || [];
196
+ this.severityClassification = opts.severityClassification || {
197
+ critical: 'RCE, auth bypass, data exfiltration',
198
+ high: 'Privilege escalation, sensitive data exposure',
199
+ medium: 'Information disclosure, CSRF',
200
+ low: 'Verbose errors, missing headers',
201
+ info: 'Informational findings, best practice deviations',
202
+ };
203
+ this.reportingFormat = opts.reportingFormat || 'markdown';
204
+ this.createdAt = opts.createdAt || new Date().toISOString();
205
+ }
206
+
207
+ /**
208
+ * Validate an output against the constitution.
209
+ * @param {Object} output — Security assessment output to validate
210
+ * @returns {{ valid: boolean, violations: string[] }}
211
+ */
212
+ validate(output) {
213
+ const violations = [];
214
+
215
+ // Check scope compliance
216
+ if (output.target && this.scope.outOfScope.length > 0) {
217
+ for (const excluded of this.scope.outOfScope) {
218
+ if (output.target.includes(excluded)) {
219
+ violations.push(`Target "${output.target}" is out of scope (excluded: ${excluded})`);
220
+ }
221
+ }
222
+ }
223
+
224
+ // Check RoE compliance
225
+ if (output.technique) {
226
+ if (this.rulesOfEngagement.passiveOnly && output.technique.includes('exploit')) {
227
+ violations.push(`Exploitation techniques not allowed (passive-only engagement)`);
228
+ }
229
+ if (!this.rulesOfEngagement.exploitAllowed && output.technique.includes('exploit')) {
230
+ violations.push(`Exploitation not authorized in rules of engagement`);
231
+ }
232
+ }
233
+
234
+ return { valid: violations.length === 0, violations };
235
+ }
236
+
237
+ /**
238
+ * Serialize to markdown.
239
+ * @returns {string}
240
+ */
241
+ toMarkdown() {
242
+ const lines = [
243
+ `# Engagement Constitution: ${this.engagementId}`,
244
+ '',
245
+ `**Target:** ${this.target}`,
246
+ `**Created:** ${this.createdAt}`,
247
+ `**Reporting Format:** ${this.reportingFormat}`,
248
+ '',
249
+ '## Scope',
250
+ '',
251
+ '### In Scope',
252
+ ...this.scope.inScope.map(s => `- ${s}`),
253
+ '',
254
+ '### Out of Scope',
255
+ ...this.scope.outOfScope.map(s => `- ${s}`),
256
+ '',
257
+ '## Rules of Engagement',
258
+ '',
259
+ `- Passive only: ${this.rulesOfEngagement.passiveOnly ? 'Yes' : 'No'}`,
260
+ `- Active scanning: ${this.rulesOfEngagement.activeScanAllowed ? 'Allowed' : 'Not allowed'}`,
261
+ `- Exploitation: ${this.rulesOfEngagement.exploitAllowed ? 'Allowed' : 'Not allowed'}`,
262
+ `- Social engineering: ${this.rulesOfEngagement.socialEngAllowed ? 'Allowed' : 'Not allowed'}`,
263
+ '',
264
+ '## Compliance Frameworks',
265
+ '',
266
+ ...(this.complianceFrameworks.length > 0 ? this.complianceFrameworks.map(f => `- ${f}`) : ['- None specified']),
267
+ '',
268
+ '## Severity Classification',
269
+ '',
270
+ ...Object.entries(this.severityClassification).map(([level, desc]) => `- **${level}:** ${desc}`),
271
+ '',
272
+ ];
273
+ return lines.join('\n');
274
+ }
275
+
276
+ /**
277
+ * Serialize to JSON.
278
+ * @returns {Object}
279
+ */
280
+ toDict() {
281
+ return {
282
+ engagementId: this.engagementId,
283
+ target: this.target,
284
+ scope: this.scope,
285
+ rulesOfEngagement: this.rulesOfEngagement,
286
+ complianceFrameworks: this.complianceFrameworks,
287
+ severityClassification: this.severityClassification,
288
+ reportingFormat: this.reportingFormat,
289
+ createdAt: this.createdAt,
290
+ };
291
+ }
292
+ }
@@ -0,0 +1,135 @@
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
+ * Circuit Breaker — 3-strike architecture rule.
7
+ *
8
+ * Tracks consecutive failures per issue ID. After 3 failed attempts
9
+ * on the same issue, halts execution and surfaces an architecture
10
+ * review question instead of attempting Fix #4.
11
+ *
12
+ * Source: Superpowers systematic-debugging "Three-Fix Architecture Questioning Rule"
13
+ *
14
+ * @module gates/circuit-breaker
15
+ */
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // ThreeStrikeBreaker
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /**
22
+ * @typedef {Object} IssueState
23
+ * @property {number} consecutiveFailures — Current streak of failures
24
+ * @property {string[]} failureDescriptions — Description of each failure
25
+ * @property {string} lastAttempt — ISO timestamp of last attempt
26
+ * @property {number} totalAttempts — Total attempts (including successes)
27
+ */
28
+
29
+ export class ThreeStrikeBreaker {
30
+ /**
31
+ * @param {{ threshold?: number }} [opts]
32
+ */
33
+ constructor(opts = {}) {
34
+ /** @type {Map<string, IssueState>} */
35
+ this._issues = new Map();
36
+ this._threshold = opts.threshold ?? 3;
37
+ }
38
+
39
+ /**
40
+ * Record an attempt on an issue.
41
+ * @param {string} issueId — Unique identifier for the issue
42
+ * @param {boolean} success — Whether the attempt succeeded
43
+ * @param {string} [description] — Description of what was tried
44
+ */
45
+ recordAttempt(issueId, success, description = '') {
46
+ let state = this._issues.get(issueId);
47
+ if (!state) {
48
+ state = { consecutiveFailures: 0, failureDescriptions: [], lastAttempt: '', totalAttempts: 0 };
49
+ this._issues.set(issueId, state);
50
+ }
51
+
52
+ state.totalAttempts++;
53
+ state.lastAttempt = new Date().toISOString();
54
+
55
+ if (success) {
56
+ state.consecutiveFailures = 0;
57
+ state.failureDescriptions = [];
58
+ } else {
59
+ state.consecutiveFailures++;
60
+ if (description) {
61
+ state.failureDescriptions.push(description);
62
+ }
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Check if the circuit breaker should halt for this issue.
68
+ * @param {string} issueId
69
+ * @returns {boolean}
70
+ */
71
+ shouldHalt(issueId) {
72
+ const state = this._issues.get(issueId);
73
+ if (!state) return false;
74
+ return state.consecutiveFailures >= this._threshold;
75
+ }
76
+
77
+ /**
78
+ * Get the architecture review question for a halted issue.
79
+ * @param {string} issueId
80
+ * @returns {{ halted: boolean, question: string, failures: string[], totalAttempts: number }}
81
+ */
82
+ getArchitectureQuestion(issueId) {
83
+ const state = this._issues.get(issueId);
84
+ if (!state || state.consecutiveFailures < this._threshold) {
85
+ return { halted: false, question: '', failures: [], totalAttempts: 0 };
86
+ }
87
+
88
+ const failureList = state.failureDescriptions
89
+ .map((d, i) => ` ${i + 1}. ${d}`)
90
+ .join('\n');
91
+
92
+ const question = [
93
+ `CIRCUIT BREAKER: ${this._threshold} consecutive failures on "${issueId}".`,
94
+ '',
95
+ 'Failed attempts:',
96
+ failureList || ' (no descriptions recorded)',
97
+ '',
98
+ 'Before attempting another fix, answer these questions:',
99
+ ' 1. Is this pattern fundamentally sound?',
100
+ ' 2. Are we sticking with it through sheer inertia?',
101
+ ' 3. Should we refactor architecture vs. continue fixing symptoms?',
102
+ '',
103
+ 'Discuss with human before attempting more fixes.',
104
+ ].join('\n');
105
+
106
+ return {
107
+ halted: true,
108
+ question,
109
+ failures: state.failureDescriptions,
110
+ totalAttempts: state.totalAttempts,
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Reset tracking for an issue.
116
+ * @param {string} issueId
117
+ */
118
+ reset(issueId) {
119
+ this._issues.delete(issueId);
120
+ }
121
+
122
+ /**
123
+ * Get current state for an issue.
124
+ * @param {string} issueId
125
+ * @returns {IssueState|null}
126
+ */
127
+ getState(issueId) {
128
+ return this._issues.get(issueId) || null;
129
+ }
130
+
131
+ /** Number of tracked issues */
132
+ get size() {
133
+ return this._issues.size;
134
+ }
135
+ }