cipher-security 2.1.0 → 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 (54) hide show
  1. package/bin/cipher.js +10 -0
  2. package/lib/analyze/consistency.js +566 -0
  3. package/lib/analyze/constitution.js +110 -0
  4. package/lib/analyze/sharding.js +251 -0
  5. package/lib/autonomous/agent-tool.js +165 -0
  6. package/lib/autonomous/framework.js +17 -0
  7. package/lib/autonomous/handoff.js +506 -0
  8. package/lib/autonomous/modes/blue.js +26 -0
  9. package/lib/autonomous/modes/red.js +28 -0
  10. package/lib/benchmark/agent.js +88 -26
  11. package/lib/benchmark/baselines.js +3 -0
  12. package/lib/benchmark/claude-code-solver.js +254 -0
  13. package/lib/benchmark/cognitive.js +283 -0
  14. package/lib/benchmark/index.js +12 -2
  15. package/lib/benchmark/knowledge.js +281 -0
  16. package/lib/benchmark/llm.js +156 -15
  17. package/lib/benchmark/models.js +5 -2
  18. package/lib/benchmark/nyu-ctf.js +192 -0
  19. package/lib/benchmark/overthewire.js +347 -0
  20. package/lib/benchmark/picoctf.js +281 -0
  21. package/lib/benchmark/prompts.js +280 -0
  22. package/lib/benchmark/registry.js +219 -0
  23. package/lib/benchmark/remote-solver.js +356 -0
  24. package/lib/benchmark/remote-target.js +263 -0
  25. package/lib/benchmark/reporter.js +35 -0
  26. package/lib/benchmark/runner.js +174 -10
  27. package/lib/benchmark/sandbox.js +35 -0
  28. package/lib/benchmark/scorer.js +22 -4
  29. package/lib/benchmark/solver.js +34 -1
  30. package/lib/benchmark/tools.js +262 -16
  31. package/lib/commands.js +9 -0
  32. package/lib/execution/council.js +434 -0
  33. package/lib/execution/parallel.js +292 -0
  34. package/lib/gates/circuit-breaker.js +135 -0
  35. package/lib/gates/confidence.js +302 -0
  36. package/lib/gates/corrections.js +219 -0
  37. package/lib/gates/self-check.js +245 -0
  38. package/lib/gateway/commands.js +727 -0
  39. package/lib/guardrails/engine.js +364 -0
  40. package/lib/mcp/server.js +349 -3
  41. package/lib/memory/compressor.js +94 -7
  42. package/lib/pipeline/hooks.js +288 -0
  43. package/lib/pipeline/index.js +11 -0
  44. package/lib/review/budget.js +210 -0
  45. package/lib/review/engine.js +526 -0
  46. package/lib/review/layers/acceptance-auditor.js +279 -0
  47. package/lib/review/layers/blind-hunter.js +500 -0
  48. package/lib/review/layers/defense-in-depth.js +209 -0
  49. package/lib/review/layers/edge-case-hunter.js +266 -0
  50. package/lib/review/panel.js +519 -0
  51. package/lib/review/two-stage.js +244 -0
  52. package/lib/session/cost-tracker.js +203 -0
  53. package/lib/session/logger.js +349 -0
  54. package/package.json +1 -1
@@ -0,0 +1,302 @@
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
+ * Security Confidence Checker — Pre-response confidence assessment.
7
+ *
8
+ * Prevents wrong-direction responses by scoring confidence across
9
+ * 5 dimensions before generating security advice. Inspired by
10
+ * SuperClaude's ConfidenceChecker pattern, adapted for security domain.
11
+ *
12
+ * Confidence levels:
13
+ * HIGH (≥0.90): Proceed with full response
14
+ * MEDIUM (0.70–0.89): Respond with explicit caveats
15
+ * LOW (<0.70): Flag uncertainty, recommend verification
16
+ *
17
+ * @module gates/confidence
18
+ */
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Hedging language detection
22
+ // ---------------------------------------------------------------------------
23
+
24
+ const HEDGING_PATTERNS = [
25
+ /\bshould\s+(?:work|fix|resolve|detect|prevent|mitigate)\b/i,
26
+ /\bprobably\s+(?:works?|fixes?|covers?|detects?)\b/i,
27
+ /\blikely\s+sufficient\b/i,
28
+ /\bseems?\s+to\s+(?:work|fix|detect)\b/i,
29
+ /\bmight\s+(?:work|help|detect|prevent)\b/i,
30
+ /\bI\s+(?:think|believe|assume)\s+(?:this|it|that)\b/i,
31
+ /\bjust\s+this\s+once\b/i,
32
+ /\bshould\s+be\s+(?:fine|enough|sufficient|okay)\b/i,
33
+ /\bI'm\s+(?:fairly|pretty|quite)\s+(?:sure|confident)\b/i,
34
+ ];
35
+
36
+ const RATIONALIZATION_PATTERNS = [
37
+ { pattern: /\bshould\s+work\s+now\b/i, category: 'unverified-claim' },
38
+ { pattern: /\bI'm\s+confident\b/i, category: 'false-confidence' },
39
+ { pattern: /\bjust\s+this\s+once\b/i, category: 'exception-seeking' },
40
+ { pattern: /\bpartial\s+check\s+is\s+enough\b/i, category: 'incomplete-verification' },
41
+ { pattern: /\bno\s+need\s+to\s+(?:test|verify|check)\b/i, category: 'verification-avoidance' },
42
+ { pattern: /\bobviously\s+(?:works?|correct|right)\b/i, category: 'assumed-correctness' },
43
+ ];
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // SecurityConfidenceChecker
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /**
50
+ * @typedef {Object} AssessmentContext
51
+ * @property {string} query — The user's query
52
+ * @property {string} mode — CIPHER mode (RED, BLUE, etc.)
53
+ * @property {number} knowledgeHits — Number of knowledge base matches
54
+ * @property {number} knowledgeRelevance — Relevance score of top match (0–1)
55
+ * @property {'current'|'recent'|'outdated'|'unknown'} topicRecency
56
+ * @property {boolean} hasConflictingGuidance — Whether sources conflict
57
+ * @property {boolean} authoritativeSourceFound — Whether authoritative source exists
58
+ */
59
+
60
+ /**
61
+ * @typedef {Object} ConfidenceResult
62
+ * @property {number} score — Overall confidence (0–1)
63
+ * @property {'HIGH'|'MEDIUM'|'LOW'} level — Confidence level
64
+ * @property {boolean} shouldProceed — Whether to generate full response
65
+ * @property {string[]} checks — Individual check results
66
+ * @property {string[]} concerns — Identified concerns
67
+ * @property {string} recommendation — Action recommendation
68
+ */
69
+
70
+ export class SecurityConfidenceChecker {
71
+ /**
72
+ * Assess confidence before generating a security response.
73
+ * @param {AssessmentContext} context
74
+ * @returns {ConfidenceResult}
75
+ */
76
+ assess(context) {
77
+ let score = 0;
78
+ const checks = [];
79
+ const concerns = [];
80
+
81
+ // Check 1: Knowledge base has relevant content (30%)
82
+ if (context.knowledgeHits > 0 && context.knowledgeRelevance > 0.7) {
83
+ score += 0.30;
84
+ checks.push('✅ Knowledge base has relevant content');
85
+ } else if (context.knowledgeHits > 0) {
86
+ score += 0.15;
87
+ checks.push('⚠️ Knowledge base has partial matches');
88
+ concerns.push('Knowledge relevance below threshold');
89
+ } else {
90
+ checks.push('❌ No knowledge base matches');
91
+ concerns.push('No authoritative knowledge for this topic');
92
+ }
93
+
94
+ // Check 2: Information recency (25%)
95
+ if (context.topicRecency === 'current' || context.topicRecency === 'recent') {
96
+ score += 0.25;
97
+ checks.push('✅ Information is current');
98
+ } else if (context.topicRecency === 'outdated') {
99
+ score += 0.10;
100
+ checks.push('⚠️ Information may be outdated');
101
+ concerns.push('Knowledge may not reflect latest threats/mitigations');
102
+ } else {
103
+ checks.push('❌ Information recency unknown');
104
+ concerns.push('Cannot verify information currency');
105
+ }
106
+
107
+ // Check 3: Mode-appropriate response (20%)
108
+ if (this._isModeAligned(context)) {
109
+ score += 0.20;
110
+ checks.push('✅ Query aligns with active mode');
111
+ } else {
112
+ score += 0.10;
113
+ checks.push('⚠️ Query may not align with active mode');
114
+ concerns.push('Consider switching mode for better results');
115
+ }
116
+
117
+ // Check 4: No conflicting guidance (15%)
118
+ if (!context.hasConflictingGuidance) {
119
+ score += 0.15;
120
+ checks.push('✅ No conflicting guidance detected');
121
+ } else {
122
+ checks.push('❌ Conflicting guidance exists');
123
+ concerns.push('Multiple sources provide contradictory advice');
124
+ }
125
+
126
+ // Check 5: Authoritative source verified (10%)
127
+ if (context.authoritativeSourceFound) {
128
+ score += 0.10;
129
+ checks.push('✅ Authoritative source verified');
130
+ } else {
131
+ checks.push('⚠️ No authoritative source found');
132
+ concerns.push('Response based on general knowledge, not verified source');
133
+ }
134
+
135
+ const level = score >= 0.90 ? 'HIGH' : score >= 0.70 ? 'MEDIUM' : 'LOW';
136
+ const shouldProceed = score >= 0.70;
137
+
138
+ let recommendation;
139
+ if (level === 'HIGH') {
140
+ recommendation = 'Proceed with full response — high confidence';
141
+ } else if (level === 'MEDIUM') {
142
+ recommendation = 'Respond with explicit caveats — flag areas of uncertainty';
143
+ } else {
144
+ recommendation = 'Flag low confidence — recommend verification before acting on advice';
145
+ }
146
+
147
+ return { score, level, shouldProceed, checks, concerns, recommendation };
148
+ }
149
+
150
+ /**
151
+ * Check if query aligns with the active CIPHER mode.
152
+ * @private
153
+ */
154
+ _isModeAligned(context) {
155
+ const modeTopics = {
156
+ RED: ['exploit', 'payload', 'attack', 'bypass', 'lateral', 'privesc', 'c2'],
157
+ BLUE: ['detect', 'detection', 'sigma', 'siem', 'hunting', 'hardening', 'edr', 'log'],
158
+ PURPLE: ['coverage', 'emulation', 'gap', 'detection engineering'],
159
+ PRIVACY: ['gdpr', 'ccpa', 'hipaa', 'dpia', 'anonymization', 'data flow'],
160
+ RECON: ['osint', 'reconnaissance', 'subdomain', 'footprinting'],
161
+ INCIDENT: ['triage', 'forensics', 'containment', 'eradication', 'timeline'],
162
+ ARCHITECT: ['design', 'architecture', 'threat model', 'zero trust'],
163
+ RESEARCHER: ['research', 'analysis', 'technique', 'methodology'],
164
+ };
165
+
166
+ const topics = modeTopics[context.mode] || [];
167
+ if (topics.length === 0) return true; // Unknown mode — don't penalize
168
+
169
+ const queryLower = context.query.toLowerCase();
170
+ return topics.some(topic => queryLower.includes(topic));
171
+ }
172
+ }
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // Hedging & Rationalization Detection
176
+ // ---------------------------------------------------------------------------
177
+
178
+ /**
179
+ * Detect hedging language in security output.
180
+ * @param {string} text — Response text to check
181
+ * @returns {{ hedgingFound: boolean, matches: string[], count: number }}
182
+ */
183
+ export function detectHedging(text) {
184
+ const matches = [];
185
+ for (const pattern of HEDGING_PATTERNS) {
186
+ const match = text.match(pattern);
187
+ if (match) {
188
+ matches.push(match[0]);
189
+ }
190
+ }
191
+ return { hedgingFound: matches.length > 0, matches, count: matches.length };
192
+ }
193
+
194
+ /**
195
+ * Detect rationalization patterns in security output.
196
+ * @param {string} text
197
+ * @returns {{ found: boolean, rationalizations: Array<{ text: string, category: string }> }}
198
+ */
199
+ export function detectRationalizations(text) {
200
+ const rationalizations = [];
201
+ for (const { pattern, category } of RATIONALIZATION_PATTERNS) {
202
+ const match = text.match(pattern);
203
+ if (match) {
204
+ rationalizations.push({ text: match[0], category });
205
+ }
206
+ }
207
+ return { found: rationalizations.length > 0, rationalizations };
208
+ }
209
+
210
+ // ---------------------------------------------------------------------------
211
+ // Verification Gate
212
+ // ---------------------------------------------------------------------------
213
+
214
+ /**
215
+ * Evidence types that satisfy the verification gate.
216
+ */
217
+ const EVIDENCE_TYPES = {
218
+ sigma_rule: {
219
+ validators: [
220
+ (text) => /^title:\s*.+/m.test(text),
221
+ (text) => /^logsource:/m.test(text),
222
+ (text) => /^detection:/m.test(text),
223
+ ],
224
+ description: 'Sigma rule must have title, logsource, and detection fields',
225
+ },
226
+ kql_query: {
227
+ validators: [
228
+ (text) => /\b(?:where|project|summarize|extend|join|let)\b/i.test(text),
229
+ ],
230
+ description: 'KQL query must contain valid operators',
231
+ },
232
+ spl_query: {
233
+ validators: [
234
+ (text) => /\b(?:index=|sourcetype=|search|stats|eval|table)\b/i.test(text),
235
+ ],
236
+ description: 'SPL query must contain valid Splunk operators',
237
+ },
238
+ cve_reference: {
239
+ validators: [
240
+ (text) => /CVE-\d{4}-\d{4,}/.test(text),
241
+ ],
242
+ description: 'CVE reference must be a valid CVE ID',
243
+ },
244
+ mitre_reference: {
245
+ validators: [
246
+ (text) => /T\d{4}(?:\.\d{3})?/.test(text),
247
+ ],
248
+ description: 'MITRE reference must be a valid technique ID',
249
+ },
250
+ command_output: {
251
+ validators: [
252
+ (text) => text.includes('$') || text.includes('#') || text.includes('>>>'),
253
+ ],
254
+ description: 'Command output must include shell prompt evidence',
255
+ },
256
+ };
257
+
258
+ /**
259
+ * Verification gate — checks that security claims have supporting evidence.
260
+ *
261
+ * @param {string} claim — The security claim being made
262
+ * @param {string} evidence — Supporting evidence
263
+ * @param {string} evidenceType — Type of evidence expected
264
+ * @returns {{ verified: boolean, reason: string, evidenceType: string }}
265
+ */
266
+ export function verifyEvidence(claim, evidence, evidenceType) {
267
+ if (!evidence || evidence.trim().length === 0) {
268
+ return { verified: false, reason: 'No evidence provided', evidenceType };
269
+ }
270
+
271
+ const typeSpec = EVIDENCE_TYPES[evidenceType];
272
+ if (!typeSpec) {
273
+ // Unknown evidence type — accept if non-empty
274
+ return { verified: true, reason: 'Evidence provided (untyped)', evidenceType };
275
+ }
276
+
277
+ const failedValidators = typeSpec.validators.filter(v => !v(evidence));
278
+ if (failedValidators.length > 0) {
279
+ return {
280
+ verified: false,
281
+ reason: `Evidence does not satisfy ${evidenceType} requirements: ${typeSpec.description}`,
282
+ evidenceType,
283
+ };
284
+ }
285
+
286
+ return { verified: true, reason: `Evidence verified as valid ${evidenceType}`, evidenceType };
287
+ }
288
+
289
+ /**
290
+ * Batch verify multiple claims against their evidence.
291
+ * @param {Array<{ claim: string, evidence: string, evidenceType: string }>} items
292
+ * @returns {{ allVerified: boolean, results: Array, unverifiedCount: number }}
293
+ */
294
+ export function verifyBatch(items) {
295
+ const results = items.map(item => ({
296
+ claim: item.claim,
297
+ ...verifyEvidence(item.claim, item.evidence, item.evidenceType),
298
+ }));
299
+
300
+ const unverifiedCount = results.filter(r => !r.verified).length;
301
+ return { allVerified: unverifiedCount === 0, results, unverifiedCount };
302
+ }
@@ -0,0 +1,219 @@
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
+ * Reflexion Error Learning — corrections log for pattern-based error prevention.
7
+ *
8
+ * Stores error patterns and corrections in a JSONL file. Before generating
9
+ * structured output (Sigma rules, KQL queries, etc.), checks the corrections
10
+ * log and applies known fixes automatically.
11
+ *
12
+ * Inspired by SuperClaude's ReflectionEngine pattern.
13
+ *
14
+ * @module gates/corrections
15
+ */
16
+
17
+ import { readFileSync, appendFileSync, existsSync, mkdirSync } from 'node:fs';
18
+ import { join, dirname } from 'node:path';
19
+ import { homedir } from 'node:os';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Correction entry
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /**
26
+ * @typedef {Object} CorrectionEntry
27
+ * @property {string} id — Unique correction ID
28
+ * @property {string} category — Error category (sigma, kql, spl, hardening, general)
29
+ * @property {string} pattern — Regex pattern string that matches the error
30
+ * @property {string} replacement — Replacement string or fix description
31
+ * @property {string} description — Human-readable description of the error
32
+ * @property {string} timestamp — ISO timestamp
33
+ * @property {number} applyCount — Times this correction has been applied
34
+ */
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // CorrectionsLog
38
+ // ---------------------------------------------------------------------------
39
+
40
+ const DEFAULT_DIR = join(homedir(), '.cipher', 'data');
41
+ const MAX_ENTRIES = 500;
42
+
43
+ export class CorrectionsLog {
44
+ /**
45
+ * @param {string} [logPath] — Path to corrections.jsonl file
46
+ */
47
+ constructor(logPath) {
48
+ this._path = logPath || join(DEFAULT_DIR, 'corrections.jsonl');
49
+ this._entries = null; // Lazy load
50
+ }
51
+
52
+ /**
53
+ * Load entries from disk.
54
+ * @returns {CorrectionEntry[]}
55
+ */
56
+ _load() {
57
+ if (this._entries !== null) return this._entries;
58
+
59
+ this._entries = [];
60
+ if (!existsSync(this._path)) return this._entries;
61
+
62
+ const lines = readFileSync(this._path, 'utf8').split('\n').filter(l => l.trim());
63
+ for (const line of lines) {
64
+ try {
65
+ this._entries.push(JSON.parse(line));
66
+ } catch { /* skip malformed lines */ }
67
+ }
68
+
69
+ return this._entries;
70
+ }
71
+
72
+ /**
73
+ * Record a new correction.
74
+ * @param {{ category: string, pattern: string, replacement: string, description: string }} opts
75
+ * @returns {CorrectionEntry}
76
+ */
77
+ record(opts) {
78
+ const entry = {
79
+ id: `COR-${Date.now().toString(36)}`,
80
+ category: opts.category || 'general',
81
+ pattern: opts.pattern,
82
+ replacement: opts.replacement || '',
83
+ description: opts.description || '',
84
+ timestamp: new Date().toISOString(),
85
+ applyCount: 0,
86
+ };
87
+
88
+ mkdirSync(dirname(this._path), { recursive: true });
89
+ appendFileSync(this._path, JSON.stringify(entry) + '\n');
90
+
91
+ // Invalidate cache
92
+ this._entries = null;
93
+
94
+ return entry;
95
+ }
96
+
97
+ /**
98
+ * Get all corrections, optionally filtered by category.
99
+ * @param {string} [category]
100
+ * @returns {CorrectionEntry[]}
101
+ */
102
+ getAll(category) {
103
+ const entries = this._load();
104
+ if (category) {
105
+ return entries.filter(e => e.category === category);
106
+ }
107
+ return entries;
108
+ }
109
+
110
+ /**
111
+ * Apply known corrections to text.
112
+ * @param {string} text — Input text to correct
113
+ * @param {string} category — Category to filter corrections by
114
+ * @returns {{ corrected: string, applied: string[], count: number }}
115
+ */
116
+ apply(text, category) {
117
+ const corrections = this.getAll(category);
118
+ let corrected = text;
119
+ const applied = [];
120
+
121
+ for (const entry of corrections) {
122
+ try {
123
+ const regex = new RegExp(entry.pattern, 'g');
124
+ if (regex.test(corrected)) {
125
+ corrected = corrected.replace(regex, entry.replacement);
126
+ applied.push(entry.id);
127
+ entry.applyCount++;
128
+ }
129
+ } catch {
130
+ // Invalid regex — skip
131
+ }
132
+ }
133
+
134
+ return { corrected, applied, count: applied.length };
135
+ }
136
+
137
+ /**
138
+ * Get correction statistics.
139
+ * @returns {{ total: number, byCategory: Record<string, number>, topApplied: CorrectionEntry[] }}
140
+ */
141
+ stats() {
142
+ const entries = this._load();
143
+ const byCategory = {};
144
+ for (const e of entries) {
145
+ byCategory[e.category] = (byCategory[e.category] || 0) + 1;
146
+ }
147
+
148
+ const topApplied = [...entries]
149
+ .sort((a, b) => (b.applyCount || 0) - (a.applyCount || 0))
150
+ .slice(0, 10);
151
+
152
+ return { total: entries.length, byCategory, topApplied };
153
+ }
154
+
155
+ /**
156
+ * Prune old entries, keeping only the most recent per category.
157
+ * @param {number} [maxPerCategory=100]
158
+ * @returns {number} — Number of entries pruned
159
+ */
160
+ prune(maxPerCategory = 100) {
161
+ const entries = this._load();
162
+ if (entries.length <= maxPerCategory) return 0;
163
+
164
+ const byCategory = {};
165
+ for (const e of entries) {
166
+ if (!byCategory[e.category]) byCategory[e.category] = [];
167
+ byCategory[e.category].push(e);
168
+ }
169
+
170
+ const kept = [];
171
+ for (const [, catEntries] of Object.entries(byCategory)) {
172
+ catEntries.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
173
+ kept.push(...catEntries.slice(0, maxPerCategory));
174
+ }
175
+
176
+ const pruned = entries.length - kept.length;
177
+ if (pruned > 0) {
178
+ mkdirSync(dirname(this._path), { recursive: true });
179
+ const content = kept.map(e => JSON.stringify(e)).join('\n') + '\n';
180
+ // Atomic write
181
+ const { writeFileSync: wfs } = require('node:fs');
182
+ wfs(this._path, content);
183
+ this._entries = null;
184
+ }
185
+
186
+ return pruned;
187
+ }
188
+
189
+ /** Number of entries */
190
+ get size() {
191
+ return this._load().length;
192
+ }
193
+ }
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Pre-built Sigma corrections
197
+ // ---------------------------------------------------------------------------
198
+
199
+ /** Common Sigma rule errors that LLMs make */
200
+ export const SIGMA_CORRECTIONS = [
201
+ {
202
+ category: 'sigma',
203
+ pattern: 'level:\\s*(?:critical|high|medium|low)\\s*\\n(?!tags:)',
204
+ replacement: '',
205
+ description: 'Sigma level must be one of: critical, high, medium, low, informational',
206
+ },
207
+ {
208
+ category: 'sigma',
209
+ pattern: 'status:\\s*(?:new|draft)\\b',
210
+ replacement: 'status: experimental',
211
+ description: 'Sigma status "new" or "draft" should be "experimental"',
212
+ },
213
+ {
214
+ category: 'sigma',
215
+ pattern: 'logsource:\\s*\\n\\s*service:\\s*',
216
+ replacement: 'logsource:\n category: ',
217
+ description: 'Sigma logsource should use "category" not "service" as primary field',
218
+ },
219
+ ];