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
package/bin/cipher.js CHANGED
@@ -51,6 +51,11 @@ ${B}Usage:${R} cipher [command] [options]
51
51
  ${B}Commands:${R}
52
52
  ${GRN}query${R} Run a security query
53
53
  ${GRN}scan${R} Run a security scan
54
+ ${GRN}review${R} Multi-layer code review (3 parallel analyzers)
55
+ ${GRN}panel${R} Expert panel security assessment (3 personas)
56
+ ${GRN}analyze${R} Cross-artifact consistency analysis
57
+ ${GRN}shard${R} Split large docs into semantic chunks
58
+ ${GRN}guardrail${R} Test input/output guardrails (tripwire detection)
54
59
  ${GRN}compliance${R} Run compliance checks (39 frameworks)
55
60
  ${GRN}search${R} Search security data
56
61
  ${GRN}osint${R} OSINT intelligence tools
@@ -73,6 +78,10 @@ ${B}Commands:${R}
73
78
  ${GRN}api${R} Start REST API server
74
79
  ${GRN}mcp${R} Start MCP server (stdio)
75
80
  ${GRN}bot${R} Start Signal bot (background)
81
+ ${GRN}chain${R} Run multi-mode agent chain (e.g. red,purple,blue)
82
+ ${GRN}council${R} Multi-model consensus evaluation
83
+ ${GRN}resume${R} Resume interrupted autonomous sessions
84
+ ${GRN}benchmark${R} Run XBOW benchmark suite
76
85
 
77
86
  ${GRN}workflow${R} Generate CI/CD security workflow
78
87
  ${GRN}memory-export${R} Export memory to JSON
@@ -144,7 +153,7 @@ let queryNoStream = false;
144
153
  let autonomousMode = null;
145
154
 
146
155
  /** Set of valid autonomous mode names (lowercased). */
147
- const MODE_NAMES = new Set(['red', 'blue', 'incident', 'purple', 'recon', 'privacy', 'architect']);
156
+ const MODE_NAMES = new Set(['red', 'blue', 'incident', 'purple', 'recon', 'privacy', 'architect', 'researcher']);
148
157
 
149
158
  /** Clean args with --backend, --no-stream, and --autonomous extracted */
150
159
  const cleanedArgs = [];
@@ -197,6 +206,7 @@ const knownCommands = new Set([
197
206
  'scan', 'search', 'store', 'diff', 'workflow', 'stats', 'domains',
198
207
  'skills', 'score', 'marketplace', 'compliance', 'leaderboard',
199
208
  'feedback', 'memory-export', 'memory-import', 'sarif', 'osint', 'update',
209
+ 'review', 'analyze', 'panel', 'shard', 'guardrail',
200
210
  // Services
201
211
  'bot', 'mcp', 'api',
202
212
  ]);
@@ -0,0 +1,199 @@
1
+ // Copyright (c) 2026 defconxt. All rights reserved.
2
+ // Licensed under AGPL-3.0 — see LICENSE file for details.
3
+
4
+ import { BaseHandler } from './base.js';
5
+
6
+ /**
7
+ * ARCHITECT mode handler — security architecture & design.
8
+ * Adds: threat-model, controls, design
9
+ */
10
+ export class ArchitectHandler extends BaseHandler {
11
+ constructor() { super('architect'); }
12
+
13
+ domainActions() {
14
+ return ['threat-model', 'controls', 'design'];
15
+ }
16
+
17
+ describeDomain(action) {
18
+ return {
19
+ 'threat-model': 'Build STRIDE/DREAD threat model from technique patterns',
20
+ 'controls': 'Map security controls (CIS, NIST, ISO) to technique',
21
+ 'design': 'Extract architecture design patterns and recommendations',
22
+ }[action];
23
+ }
24
+
25
+ async executeDomain(command, ctx) {
26
+ switch (command) {
27
+ case 'threat-model': return this.threatModel(ctx);
28
+ case 'controls': return this.controls(ctx);
29
+ case 'design': return this.design(ctx);
30
+ default: throw new Error(`Unknown ARCHITECT command: ${command}`);
31
+ }
32
+ }
33
+
34
+ /** Build threat model structure. */
35
+ threatModel(ctx) {
36
+ const { skill, technique } = ctx;
37
+ if (!skill) throw new Error('No SKILL.md found');
38
+
39
+ const scope = ctx.flags.scope || 'unspecified';
40
+
41
+ // Extract STRIDE elements from content
42
+ const stride = {
43
+ spoofing: [],
44
+ tampering: [],
45
+ repudiation: [],
46
+ informationDisclosure: [],
47
+ dos: [],
48
+ elevationOfPrivilege: [],
49
+ };
50
+
51
+ const stridePatterns = {
52
+ spoofing: /spoof|impersonat|forge|fake identity/i,
53
+ tampering: /tamper|modif|alter|inject|manipulat/i,
54
+ repudiation: /repudiat|non-repudiat|audit|log/i,
55
+ informationDisclosure: /disclos|leak|expos|exfiltrat|data breach/i,
56
+ dos: /denial.of.service|dos|ddos|availability|exhaust/i,
57
+ elevationOfPrivilege: /privilege.escalat|elevat|root|admin|bypass auth/i,
58
+ };
59
+
60
+ for (const section of skill.sections) {
61
+ for (const [category, pattern] of Object.entries(stridePatterns)) {
62
+ if (pattern.test(section.title) || pattern.test(section.content.slice(0, 200))) {
63
+ stride[category].push({
64
+ source: section.title,
65
+ summary: section.content.slice(0, 200),
66
+ });
67
+ }
68
+ }
69
+ }
70
+
71
+ // Extract trust boundaries from diagrams
72
+ const diagrams = skill.codeBlocks
73
+ .filter(b => b.lang === 'mermaid' || /diagram|flow|architect/i.test(b.context))
74
+ .map(b => ({ context: b.context, code: b.code }));
75
+
76
+ return {
77
+ action: 'threat-model',
78
+ technique,
79
+ mode: 'ARCHITECT',
80
+ scope,
81
+ attackIds: skill.attackIds,
82
+ stride,
83
+ diagrams: diagrams.slice(0, 5),
84
+ mitigations: this.extractRecommendations(skill),
85
+ status: 'threat_model_complete',
86
+ };
87
+ }
88
+
89
+ /** Map security controls. */
90
+ controls(ctx) {
91
+ const { skill, technique } = ctx;
92
+ if (!skill) throw new Error('No SKILL.md found');
93
+
94
+ // Extract control references
95
+ const controlRefs = new Set();
96
+ const patterns = [
97
+ /CIS\s*(?:Control\s*)?\d+(?:\.\d+)*/gi,
98
+ /NIST\s*(?:SP\s*)?(?:800-\d+(?:[A-Z])?\s*)?(?:[A-Z]{2}-\d+)?/gi,
99
+ /ISO\s*27\d{3}(?:[-:]\d+(?:\.\d+)*)?/gi,
100
+ /SOC\s*[12](?:\s*Type\s*[12I]+)?/gi,
101
+ /PCI[\s-]DSS\s*(?:v?\d+(?:\.\d+)?\s*)?(?:Req(?:uirement)?\s*\d+(?:\.\d+)*)?/gi,
102
+ /OWASP\s*(?:Top\s*10)?\s*(?:A\d{2}(?::\d{4})?)?/gi,
103
+ /MITRE\s*(?:ATT&CK|D3FEND)/gi,
104
+ ];
105
+
106
+ for (const re of patterns) {
107
+ let m;
108
+ while ((m = re.exec(skill.raw))) {
109
+ controlRefs.add(m[0].trim());
110
+ }
111
+ }
112
+
113
+ // Extract from tables with control columns
114
+ const controlTables = [];
115
+ for (const table of skill.tables) {
116
+ if (table.headers.some(h => /control|requirement|standard|framework|compliance/i.test(h))) {
117
+ controlTables.push({
118
+ headers: table.headers,
119
+ rows: table.rows.slice(0, 15),
120
+ });
121
+ }
122
+ }
123
+
124
+ const controlsList = [...controlRefs];
125
+
126
+ return {
127
+ action: 'controls',
128
+ technique,
129
+ mode: 'ARCHITECT',
130
+ attackIds: skill.attackIds,
131
+ controls: controlsList,
132
+ controlValidation: this.validateControlRefs(controlsList),
133
+ controlTables: controlTables.slice(0, 5),
134
+ status: 'controls_complete',
135
+ };
136
+ }
137
+
138
+ /** Validate control reference strings against known framework formats. */
139
+ validateControlRefs(controls) {
140
+ if (!Array.isArray(controls)) return { valid: [], invalid: [], total: 0 };
141
+ const patterns = [
142
+ /^CIS\s*(?:Control\s*)?\d+(?:\.\d+)*/i,
143
+ /^NIST\s*(?:SP\s*)?800-\d+/i,
144
+ /^(?:AC|AT|AU|CA|CM|CP|IA|IR|MA|MP|PE|PL|PM|PS|RA|SA|SC|SI|SR)-\d+/i,
145
+ /^ISO\s*27\d{3}/i,
146
+ /^OWASP/i,
147
+ /^PCI[\s-]DSS/i,
148
+ /^SOC\s*[12]/i,
149
+ /^MITRE\s*(?:ATT&CK|D3FEND)/i,
150
+ ];
151
+ const valid = [];
152
+ const invalid = [];
153
+ for (const ref of controls) {
154
+ const s = String(ref).trim();
155
+ if (patterns.some(p => p.test(s))) { valid.push(s); } else { invalid.push(s); }
156
+ }
157
+ return { valid, invalid, total: controls.length };
158
+ }
159
+
160
+ /** Extract architecture design patterns. */
161
+ design(ctx) {
162
+ const { skill, technique } = ctx;
163
+ if (!skill) throw new Error('No SKILL.md found');
164
+
165
+ // Extract design/architecture sections
166
+ const patterns = skill.sections
167
+ .filter(s => /design|architect|pattern|principle|best.practice|implement/i.test(s.title))
168
+ .map(s => ({
169
+ area: s.title,
170
+ content: s.content.slice(0, 400),
171
+ }));
172
+
173
+ // Extract configuration examples
174
+ const configs = skill.codeBlocks
175
+ .filter(b => ['yaml', 'json', 'toml', 'hcl', 'tf', 'xml'].includes(b.lang))
176
+ .map(b => ({
177
+ lang: b.lang,
178
+ context: b.context,
179
+ config: b.code.slice(0, 500),
180
+ }));
181
+
182
+ // Extract diagrams
183
+ const diagrams = skill.codeBlocks
184
+ .filter(b => b.lang === 'mermaid')
185
+ .map(b => ({ context: b.context, code: b.code }));
186
+
187
+ return {
188
+ action: 'design',
189
+ technique,
190
+ mode: 'ARCHITECT',
191
+ tools: skill.tools,
192
+ patterns: patterns.slice(0, 10),
193
+ configs: configs.slice(0, 10),
194
+ diagrams: diagrams.slice(0, 5),
195
+ recommendations: this.extractRecommendations(skill),
196
+ status: 'design_complete',
197
+ };
198
+ }
199
+ }
@@ -0,0 +1,240 @@
1
+ // Copyright (c) 2026 defconxt. All rights reserved.
2
+ // Licensed under AGPL-3.0 — see LICENSE file for details.
3
+
4
+ /**
5
+ * Base handler — shared actions every domain handler inherits.
6
+ *
7
+ * Subclasses override domainActions() and executeDomain() to add
8
+ * domain-specific commands on top of the common set.
9
+ */
10
+
11
+ export class BaseHandler {
12
+ constructor(mode) {
13
+ this.mode = mode;
14
+ }
15
+
16
+ /** All available actions (common + domain-specific). */
17
+ actions() {
18
+ return [...this.commonActions(), ...this.domainActions()];
19
+ }
20
+
21
+ /** Actions shared across all modes. */
22
+ commonActions() {
23
+ return ['analyze', 'enumerate', 'assess', 'report'];
24
+ }
25
+
26
+ /** Override in subclass — domain-specific actions. */
27
+ domainActions() {
28
+ return [];
29
+ }
30
+
31
+ /** Description for help text. Override per-action in subclass. */
32
+ describe(action) {
33
+ const common = {
34
+ analyze: 'Extract knowledge, tables, ATT&CK mappings from SKILL.md',
35
+ enumerate: 'List commands and tools from api-reference.md',
36
+ assess: 'Evaluate against technique checklist',
37
+ report: 'Generate structured findings report',
38
+ };
39
+ return common[action] || this.describeDomain(action) || 'No description';
40
+ }
41
+
42
+ /** Override in subclass for domain-specific descriptions. */
43
+ describeDomain(action) { return null; }
44
+
45
+ /** Dispatch to the right action. */
46
+ async execute(command, ctx) {
47
+ switch (command) {
48
+ case 'analyze': return this.analyze(ctx);
49
+ case 'enumerate': return this.enumerate(ctx);
50
+ case 'assess': return this.assess(ctx);
51
+ case 'report': return this.report(ctx);
52
+ default: return this.executeDomain(command, ctx);
53
+ }
54
+ }
55
+
56
+ /** Override in subclass for domain-specific execution. */
57
+ async executeDomain(command, ctx) {
58
+ throw new Error(`Unknown command: ${command}`);
59
+ }
60
+
61
+ // ── Common actions ────────────────────────────────────────────────
62
+
63
+ /** Analyze SKILL.md — extract all structured data. */
64
+ analyze(ctx) {
65
+ const { skill, technique, domain, mode } = ctx;
66
+ if (!skill) throw new Error('No SKILL.md found');
67
+
68
+ return {
69
+ action: 'analyze',
70
+ technique,
71
+ domain,
72
+ mode: mode.toUpperCase(),
73
+ name: skill.frontmatter?.name || technique,
74
+ description: skill.frontmatter?.description || '',
75
+ tags: skill.frontmatter?.tags || [],
76
+ attackIds: skill.attackIds,
77
+ tools: skill.tools,
78
+ sections: skill.sections.map(s => ({
79
+ level: s.level,
80
+ title: s.title,
81
+ // Truncate content to avoid dumping entire SKILL.md
82
+ summary: s.content.slice(0, 200) + (s.content.length > 200 ? '...' : ''),
83
+ })),
84
+ tables: skill.tables.map(t => ({
85
+ headers: t.headers,
86
+ rowCount: t.rows.length,
87
+ sample: t.rows.slice(0, 3),
88
+ })),
89
+ codeBlockCount: skill.codeBlocks.length,
90
+ status: 'analyze_complete',
91
+ };
92
+ }
93
+
94
+ /** Enumerate commands/tools from api-reference.md. */
95
+ enumerate(ctx) {
96
+ const source = ctx.apiRef || ctx.skill;
97
+ if (!source) throw new Error('No api-reference.md or SKILL.md found');
98
+
99
+ const commands = [];
100
+
101
+ // Extract from code blocks
102
+ for (const block of source.codeBlocks) {
103
+ for (const line of block.code.split('\n')) {
104
+ const trimmed = line.trim();
105
+ // Skip comments and empty lines
106
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//') || trimmed.startsWith('*')) continue;
107
+ // Skip pure Python/JS function defs — only collect executable commands
108
+ if (trimmed.startsWith('def ') || trimmed.startsWith('function ') || trimmed.startsWith('import ')) continue;
109
+ if (trimmed.startsWith('class ') || trimmed.startsWith('from ')) continue;
110
+ commands.push({
111
+ command: trimmed,
112
+ lang: block.lang,
113
+ context: block.context,
114
+ });
115
+ }
116
+ }
117
+
118
+ // Extract from tables (command tables)
119
+ for (const table of source.tables) {
120
+ const cmdHeader = table.headers.find(h =>
121
+ /command|syntax|usage/i.test(h)
122
+ );
123
+ if (cmdHeader) {
124
+ for (const row of table.rows) {
125
+ commands.push({
126
+ command: row[cmdHeader],
127
+ description: row[table.headers.find(h => /description|purpose/i.test(h))] || '',
128
+ context: 'table',
129
+ });
130
+ }
131
+ }
132
+ }
133
+
134
+ return {
135
+ action: 'enumerate',
136
+ technique: ctx.technique,
137
+ domain: ctx.domain,
138
+ mode: ctx.mode.toUpperCase(),
139
+ tools: source.tools,
140
+ commandCount: commands.length,
141
+ commands: commands.slice(0, 50), // Cap output size
142
+ status: 'enumerate_complete',
143
+ };
144
+ }
145
+
146
+ /** Assess against technique checklist. */
147
+ assess(ctx) {
148
+ const { skill, technique, domain, mode, flags } = ctx;
149
+ if (!skill) throw new Error('No SKILL.md found');
150
+
151
+ const target = flags.target || flags.scope || 'unspecified';
152
+
153
+ // Build checklist from tables and sections
154
+ const checklist = [];
155
+
156
+ // Look for prerequisite tables
157
+ for (const table of skill.tables) {
158
+ if (table.headers.some(h => /requirement|prerequisite|control|check/i.test(h))) {
159
+ for (const row of table.rows) {
160
+ const item = row[table.headers[0]] || Object.values(row)[0];
161
+ checklist.push({
162
+ item,
163
+ category: table.headers[0],
164
+ status: 'needs_review',
165
+ });
166
+ }
167
+ }
168
+ }
169
+
170
+ // Look for checklist patterns in content ([ ] and [x])
171
+ const checklistRe = /^\s*[-*] \[([x ])\] (.+)$/gm;
172
+ let m;
173
+ while ((m = checklistRe.exec(skill.raw))) {
174
+ checklist.push({
175
+ item: m[2].trim(),
176
+ category: 'checklist',
177
+ status: m[1] === 'x' ? 'implemented' : 'needs_review',
178
+ });
179
+ }
180
+
181
+ return {
182
+ action: 'assess',
183
+ technique,
184
+ domain,
185
+ mode: mode.toUpperCase(),
186
+ target,
187
+ attackIds: skill.attackIds,
188
+ checklistItems: checklist.length,
189
+ checklist: checklist.slice(0, 50),
190
+ coverage: checklist.length > 0
191
+ ? `${checklist.filter(c => c.status === 'implemented').length}/${checklist.length}`
192
+ : 'no checklist found',
193
+ status: 'assess_complete',
194
+ };
195
+ }
196
+
197
+ /** Generate structured report. */
198
+ report(ctx) {
199
+ const { skill, technique, domain, mode, flags } = ctx;
200
+ if (!skill) throw new Error('No SKILL.md found');
201
+
202
+ const format = flags.format || 'json';
203
+ const title = skill.frontmatter?.name || technique;
204
+
205
+ return {
206
+ action: 'report',
207
+ technique,
208
+ domain,
209
+ mode: mode.toUpperCase(),
210
+ report: {
211
+ title,
212
+ description: skill.frontmatter?.description || '',
213
+ attackTechniques: skill.attackIds,
214
+ tools: skill.tools,
215
+ sections: skill.sections.map(s => s.title),
216
+ tableCount: skill.tables.length,
217
+ codeBlockCount: skill.codeBlocks.length,
218
+ recommendations: this.extractRecommendations(skill),
219
+ },
220
+ format,
221
+ status: 'report_complete',
222
+ };
223
+ }
224
+
225
+ /** Extract recommendations from sections named remediation, mitigation, etc. */
226
+ extractRecommendations(skill) {
227
+ const recs = [];
228
+ const recSections = skill.sections.filter(s =>
229
+ /remediat|mitigat|recommend|harden|defense|protect|prevent/i.test(s.title)
230
+ );
231
+ for (const s of recSections) {
232
+ // Extract list items
233
+ const items = s.content.match(/^[-*] .+$/gm);
234
+ if (items) {
235
+ recs.push(...items.map(i => i.replace(/^[-*] /, '').trim()));
236
+ }
237
+ }
238
+ return recs.slice(0, 20);
239
+ }
240
+ }
@@ -0,0 +1,220 @@
1
+ // Copyright (c) 2026 defconxt. All rights reserved.
2
+ // Licensed under AGPL-3.0 — see LICENSE file for details.
3
+
4
+ import { BaseHandler } from './base.js';
5
+
6
+ /**
7
+ * BLUE mode handler — defensive security.
8
+ * Adds: detect (Sigma rules, log queries), hunt, harden
9
+ */
10
+ export class BlueHandler extends BaseHandler {
11
+ constructor() { super('blue'); }
12
+
13
+ domainActions() {
14
+ return ['detect', 'hunt', 'harden'];
15
+ }
16
+
17
+ describeDomain(action) {
18
+ return {
19
+ 'detect': 'Generate detection rules (Sigma, KQL, SPL) from technique patterns',
20
+ 'hunt': 'Build threat hunting queries from technique indicators',
21
+ 'harden': 'Extract hardening recommendations and CIS controls',
22
+ }[action];
23
+ }
24
+
25
+ async executeDomain(command, ctx) {
26
+ switch (command) {
27
+ case 'detect': return this.detect(ctx);
28
+ case 'hunt': return this.hunt(ctx);
29
+ case 'harden': return this.harden(ctx);
30
+ default: throw new Error(`Unknown BLUE command: ${command}`);
31
+ }
32
+ }
33
+
34
+ /** Generate detection rules from SKILL.md patterns. */
35
+ detect(ctx) {
36
+ const { skill, technique } = ctx;
37
+ if (!skill) throw new Error('No SKILL.md found');
38
+
39
+ const format = ctx.flags.format || 'sigma';
40
+ const rules = [];
41
+
42
+ // Extract Sigma rules from code blocks
43
+ for (const block of skill.codeBlocks) {
44
+ if (block.lang === 'yaml' || block.lang === 'sigma') {
45
+ if (block.code.includes('logsource') || block.code.includes('detection')) {
46
+ rules.push({
47
+ type: 'sigma',
48
+ context: block.context,
49
+ rule: block.code,
50
+ });
51
+ }
52
+ }
53
+ }
54
+
55
+ // Extract KQL/SPL queries
56
+ for (const block of skill.codeBlocks) {
57
+ if (block.lang === 'kql' || block.lang === 'kusto') {
58
+ rules.push({ type: 'kql', context: block.context, query: block.code });
59
+ }
60
+ if (block.lang === 'spl' || block.lang === 'splunk') {
61
+ rules.push({ type: 'spl', context: block.context, query: block.code });
62
+ }
63
+ }
64
+
65
+ // Extract detection patterns from tables
66
+ const detectionTables = [];
67
+ for (const table of skill.tables) {
68
+ if (table.headers.some(h => /event.?id|log|detection|indicator|signature/i.test(h))) {
69
+ detectionTables.push({
70
+ headers: table.headers,
71
+ rows: table.rows.slice(0, 10),
72
+ });
73
+ }
74
+ }
75
+
76
+ // Build detection summary from content
77
+ const detectionSections = skill.sections
78
+ .filter(s => /detect|sigma|rule|alert|monitor|query/i.test(s.title))
79
+ .map(s => ({
80
+ title: s.title,
81
+ summary: s.content.slice(0, 300),
82
+ }));
83
+
84
+ const sigmaValidation = this.validateSigmaRules(rules);
85
+
86
+ return {
87
+ action: 'detect',
88
+ technique,
89
+ mode: 'BLUE',
90
+ attackIds: skill.attackIds,
91
+ requestedFormat: format,
92
+ rules,
93
+ detectionTables,
94
+ detectionSections,
95
+ sigmaValid: sigmaValidation.invalid.length === 0,
96
+ sigmaErrors: sigmaValidation.errors,
97
+ evasionConsiderations: this.extractEvasionNotes(skill),
98
+ status: 'detect_complete',
99
+ };
100
+ }
101
+
102
+ /** Build threat hunting queries. */
103
+ hunt(ctx) {
104
+ const { skill, technique } = ctx;
105
+ if (!skill) throw new Error('No SKILL.md found');
106
+
107
+ const queries = [];
108
+
109
+ // Extract hunting-relevant code blocks
110
+ const huntLangs = ['kql', 'kusto', 'spl', 'splunk', 'sql', 'powershell', 'bash'];
111
+ for (const block of skill.codeBlocks) {
112
+ if (huntLangs.includes(block.lang)) {
113
+ queries.push({
114
+ lang: block.lang,
115
+ context: block.context,
116
+ query: block.code,
117
+ });
118
+ }
119
+ }
120
+
121
+ // Extract indicators from tables
122
+ const indicators = [];
123
+ for (const table of skill.tables) {
124
+ if (table.headers.some(h => /indicator|ioc|artifact|evidence|event/i.test(h))) {
125
+ indicators.push(...table.rows.slice(0, 10));
126
+ }
127
+ }
128
+
129
+ // Hunt hypothesis from sections
130
+ const hypotheses = skill.sections
131
+ .filter(s => /hunt|hypothesis|scenario|use.case/i.test(s.title))
132
+ .map(s => ({
133
+ title: s.title,
134
+ content: s.content.slice(0, 300),
135
+ }));
136
+
137
+ return {
138
+ action: 'hunt',
139
+ technique,
140
+ mode: 'BLUE',
141
+ attackIds: skill.attackIds,
142
+ tools: skill.tools,
143
+ queries: queries.slice(0, 20),
144
+ indicators: indicators.slice(0, 20),
145
+ hypotheses: hypotheses.slice(0, 5),
146
+ status: 'hunt_complete',
147
+ };
148
+ }
149
+
150
+ /** Extract hardening recommendations. */
151
+ harden(ctx) {
152
+ const { skill, technique } = ctx;
153
+ if (!skill) throw new Error('No SKILL.md found');
154
+
155
+ const recommendations = this.extractRecommendations(skill);
156
+
157
+ // Extract CIS/NIST references
158
+ const controls = [];
159
+ const controlRe = /(?:CIS|NIST|ISO)\s*[\w.-]+/g;
160
+ let m;
161
+ while ((m = controlRe.exec(skill.raw))) {
162
+ controls.push(m[0]);
163
+ }
164
+
165
+ // Extract configuration code blocks
166
+ const configs = skill.codeBlocks
167
+ .filter(b => /config|harden|secure|policy|baseline/i.test(b.context))
168
+ .map(b => ({
169
+ lang: b.lang,
170
+ context: b.context,
171
+ config: b.code.slice(0, 500),
172
+ }));
173
+
174
+ return {
175
+ action: 'harden',
176
+ technique,
177
+ mode: 'BLUE',
178
+ attackIds: skill.attackIds,
179
+ recommendations,
180
+ controls: [...new Set(controls)],
181
+ configs: configs.slice(0, 10),
182
+ evasionConsiderations: this.extractEvasionNotes(skill),
183
+ status: 'harden_complete',
184
+ };
185
+ }
186
+
187
+ /** Validate Sigma rules for required structural fields. */
188
+ validateSigmaRules(rules) {
189
+ if (!Array.isArray(rules)) return { valid: [], invalid: [], errors: [] };
190
+ const requiredFields = ['title:', 'logsource:', 'detection:', 'condition:'];
191
+ const valid = [];
192
+ const invalid = [];
193
+ const errors = [];
194
+ rules.forEach((rule, idx) => {
195
+ const ruleText = typeof rule === 'string' ? rule : (rule && rule.rule) || '';
196
+ const missing = requiredFields.filter(f => !ruleText.includes(f));
197
+ if (missing.length === 0) {
198
+ valid.push(idx);
199
+ } else {
200
+ invalid.push(idx);
201
+ errors.push({ index: idx, missing: missing.map(f => f.replace(':', '')) });
202
+ }
203
+ });
204
+ return { valid, invalid, errors };
205
+ }
206
+
207
+ /** PURPLE layer — every BLUE output includes evasion considerations. */
208
+ extractEvasionNotes(skill) {
209
+ const notes = [];
210
+ for (const section of skill.sections) {
211
+ if (/evas|bypass|limitation|blind.spot|false.negative|gap/i.test(section.title)) {
212
+ notes.push({
213
+ area: section.title,
214
+ summary: section.content.slice(0, 200),
215
+ });
216
+ }
217
+ }
218
+ return notes.slice(0, 5);
219
+ }
220
+ }