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,369 @@
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
+ * PRIVACY mode agent — Compliance Assessment.
7
+ *
8
+ * Performs autonomous codebase analysis for PII patterns and GDPR triggers.
9
+ * Ported from autonomous/modes/privacy.py.
10
+ *
11
+ * @module autonomous/modes/privacy
12
+ */
13
+
14
+ import { ModeAgentConfig, ToolRegistry } from '../framework.js';
15
+ import { DPIAValidator } from '../validators/privacy.js';
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // PII regex patterns (inline — NOT imported from skills/)
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /** @type {Object<string, [RegExp, string]>} */
22
+ export const PII_PATTERNS = {
23
+ ssn: [
24
+ /\b\d{3}-\d{2}-\d{4}\b/g,
25
+ 'Social Security Number',
26
+ ],
27
+ credit_card: [
28
+ /\b(?:4\d{3}|5[1-5]\d{2}|3[47]\d{2}|6(?:011|5\d{2}))[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g,
29
+ 'Credit Card Number',
30
+ ],
31
+ email: [
32
+ /\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/g,
33
+ 'Email Address',
34
+ ],
35
+ phone: [
36
+ /\b(?:\+1[- ]?)?\(?\d{3}\)?[- ]?\d{3}[- ]?\d{4}\b/g,
37
+ 'US Phone Number',
38
+ ],
39
+ ip_address: [
40
+ /\b(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b/g,
41
+ 'IP Address',
42
+ ],
43
+ };
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // WP29 DPIA criteria and trigger keywords
47
+ // ---------------------------------------------------------------------------
48
+
49
+ export const WP29_CRITERIA = [
50
+ 'Evaluation or scoring (profiling, predicting)',
51
+ 'Automated decision-making with legal/significant effect',
52
+ 'Systematic monitoring (observation, tracking)',
53
+ 'Sensitive data or data of highly personal nature',
54
+ 'Data processed on a large scale',
55
+ 'Matching or combining datasets',
56
+ 'Data concerning vulnerable subjects',
57
+ 'Innovative use of new technology',
58
+ 'Cross-border transfer outside EEA',
59
+ 'Processing prevents exercising data subject rights',
60
+ ];
61
+
62
+ export const TRIGGER_KEYWORDS = {
63
+ 'profiling': 0,
64
+ 'scoring': 0,
65
+ 'automated decision': 1,
66
+ 'systematic monitoring': 2,
67
+ 'surveillance': 2,
68
+ 'health': 3,
69
+ 'biometric': 3,
70
+ 'genetic': 3,
71
+ 'racial': 3,
72
+ 'political': 3,
73
+ 'large-scale': 4,
74
+ 'large scale': 4,
75
+ 'combining': 5,
76
+ 'matching': 5,
77
+ 'children': 6,
78
+ 'employee': 6,
79
+ 'patient': 6,
80
+ 'vulnerable': 6,
81
+ 'innovative': 7,
82
+ 'new technology': 7,
83
+ 'cross-border': 8,
84
+ 'transfer': 8,
85
+ 'prevent': 9,
86
+ 'restrict': 9,
87
+ };
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // Tool handlers
91
+ // ---------------------------------------------------------------------------
92
+
93
+ /**
94
+ * Scan source code content for PII patterns using inline regexes.
95
+ * @param {*} context
96
+ * @param {Object} toolInput
97
+ * @returns {string}
98
+ */
99
+ export function _privacyScanCodebase(context, toolInput) {
100
+ const codeContent = toolInput.code_content || '';
101
+ if (!codeContent) {
102
+ return "ERROR: 'code_content' parameter is required.";
103
+ }
104
+
105
+ const filename = toolInput.filename || '<unknown>';
106
+ const findings = [];
107
+
108
+ const lines = codeContent.split('\n');
109
+ for (const [piiType, [pattern, label]] of Object.entries(PII_PATTERNS)) {
110
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
111
+ const line = lines[lineNum];
112
+ // Reset regex lastIndex for each line (global flag)
113
+ pattern.lastIndex = 0;
114
+ let match;
115
+ while ((match = pattern.exec(line)) !== null) {
116
+ findings.push({
117
+ type: piiType,
118
+ label,
119
+ line: lineNum + 1,
120
+ match: match[0],
121
+ });
122
+ }
123
+ }
124
+ }
125
+
126
+ return JSON.stringify({
127
+ filename,
128
+ findings,
129
+ total_findings: findings.length,
130
+ }, null, 2);
131
+ }
132
+
133
+ /**
134
+ * Evaluate WP29 DPIA trigger criteria against a processing description.
135
+ * @param {*} context
136
+ * @param {Object} toolInput
137
+ * @returns {string}
138
+ */
139
+ export function _privacyAssessDpiaTriggers(context, toolInput) {
140
+ const description = toolInput.processing_description || '';
141
+ if (!description) {
142
+ return "ERROR: 'processing_description' parameter is required.";
143
+ }
144
+
145
+ const descLower = description.toLowerCase();
146
+ const matchedIndices = new Set();
147
+
148
+ for (const [keyword, index] of Object.entries(TRIGGER_KEYWORDS)) {
149
+ if (descLower.includes(keyword)) {
150
+ matchedIndices.add(index);
151
+ }
152
+ }
153
+
154
+ const matchedCriteria = [...matchedIndices].sort((a, b) => a - b).map(i => WP29_CRITERIA[i]);
155
+ const dpiaRequired = matchedIndices.size >= 2;
156
+
157
+ return JSON.stringify({
158
+ processing_description: description,
159
+ criteria_matched: matchedCriteria,
160
+ criteria_count: matchedCriteria.length,
161
+ dpia_required: dpiaRequired,
162
+ reasoning: dpiaRequired
163
+ ? `DPIA required: ${matchedCriteria.length} WP29 criteria matched (threshold: 2)`
164
+ : `DPIA may not be required: only ${matchedCriteria.length} criteria matched`,
165
+ article_35_3_triggers: {
166
+ systematic_profiling: [0, 1].some(i => matchedIndices.has(i)),
167
+ special_category_large_scale: [3, 4].every(i => matchedIndices.has(i)),
168
+ systematic_public_monitoring: matchedIndices.has(2) && matchedIndices.has(4),
169
+ },
170
+ }, null, 2);
171
+ }
172
+
173
+ /**
174
+ * Store a structured JSON DPIA report in context.
175
+ * @param {*} context
176
+ * @param {Object} toolInput
177
+ * @returns {string}
178
+ */
179
+ export function _privacyWriteDpiaReport(context, toolInput) {
180
+ const report = toolInput.report || '';
181
+
182
+ if (typeof context !== 'object' || context === null) {
183
+ return 'ERROR: Context must be a dict.';
184
+ }
185
+
186
+ let reportData;
187
+ if (typeof report === 'string') {
188
+ try {
189
+ reportData = JSON.parse(report);
190
+ } catch {
191
+ reportData = report;
192
+ }
193
+ } else {
194
+ reportData = report;
195
+ }
196
+
197
+ context.report = reportData;
198
+ const filename = toolInput.filename || 'dpia_report.json';
199
+
200
+ return (
201
+ `DPIA report stored as ${filename}. ` +
202
+ `Report is available in context['report'].`
203
+ );
204
+ }
205
+
206
+ // ---------------------------------------------------------------------------
207
+ // Tool schemas (Anthropic format)
208
+ // ---------------------------------------------------------------------------
209
+
210
+ const _PRIVACY_SCAN_CODEBASE_SCHEMA = {
211
+ name: 'scan_codebase',
212
+ description:
213
+ 'Scan source code content for PII patterns (SSN, credit card, email, phone, ' +
214
+ 'IP address) using regex matching. Returns findings with type, label, line, match.',
215
+ input_schema: {
216
+ type: 'object',
217
+ properties: {
218
+ code_content: {
219
+ type: 'string',
220
+ description: 'Source code content to scan for PII patterns',
221
+ },
222
+ filename: {
223
+ type: 'string',
224
+ description: 'Optional filename for attribution in results',
225
+ },
226
+ },
227
+ required: ['code_content'],
228
+ },
229
+ };
230
+
231
+ const _PRIVACY_ASSESS_DPIA_TRIGGERS_SCHEMA = {
232
+ name: 'assess_dpia_triggers',
233
+ description:
234
+ 'Evaluate a data processing description against WP29 DPIA trigger criteria. ' +
235
+ 'Returns matched criteria, count, DPIA determination, and Article 35(3) triggers.',
236
+ input_schema: {
237
+ type: 'object',
238
+ properties: {
239
+ processing_description: {
240
+ type: 'string',
241
+ description: 'Description of the data processing activity to assess',
242
+ },
243
+ },
244
+ required: ['processing_description'],
245
+ },
246
+ };
247
+
248
+ const _PRIVACY_WRITE_DPIA_REPORT_SCHEMA = {
249
+ name: 'write_dpia_report',
250
+ description:
251
+ 'Submit the completed DPIA report as JSON with required sections: ' +
252
+ 'processing_description, data_flows, legal_basis, risk_assessment, ' +
253
+ 'mitigations, gdpr_articles, findings.',
254
+ input_schema: {
255
+ type: 'object',
256
+ properties: {
257
+ report: {
258
+ type: 'string',
259
+ description:
260
+ 'Full JSON DPIA report with processing_description, data_flows, ' +
261
+ 'legal_basis, risk_assessment, mitigations, gdpr_articles, findings.',
262
+ },
263
+ filename: {
264
+ type: 'string',
265
+ description: 'Filename for the report (e.g. dpia_report.json)',
266
+ },
267
+ },
268
+ required: ['report'],
269
+ },
270
+ };
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // System prompt template
274
+ // ---------------------------------------------------------------------------
275
+
276
+ const _PRIVACY_SYSTEM_PROMPT = `\
277
+ You are an expert privacy engineer and GDPR compliance analyst. Your task is \
278
+ to analyze a codebase for data processing activities and produce a structured \
279
+ Data Protection Impact Assessment (DPIA) report.
280
+
281
+ ## Codebase
282
+ Description: {codebase_description}
283
+ Files: {file_listing}
284
+
285
+ ## Instructions
286
+ 1. Use \`scan_codebase\` to scan each source file for PII patterns.
287
+ 2. Use \`assess_dpia_triggers\` to evaluate WP29 criteria.
288
+ 3. Produce a structured JSON DPIA report using \`write_dpia_report\`.
289
+
290
+ ## Rules
291
+ - Cite specific GDPR articles (e.g., Art. 35)
292
+ - Assess all WP29 DPIA trigger criteria systematically
293
+ - Flag any PII found in source code as high-priority findings
294
+ - Document data flows completely
295
+ `;
296
+
297
+ // ---------------------------------------------------------------------------
298
+ // Output parser (fallback for text-based output)
299
+ // ---------------------------------------------------------------------------
300
+
301
+ /**
302
+ * Extract JSON DPIA report from LLM text output.
303
+ * @param {string} text
304
+ * @returns {Object}
305
+ */
306
+ export function _privacyOutputParser(text) {
307
+ if (!text || !text.trim()) {
308
+ return { raw_text: text, parse_error: 'empty output' };
309
+ }
310
+
311
+ let matches = [...text.matchAll(/```json\s*\n(.*?)```/gs)].map(m => m[1]);
312
+ if (matches.length > 0) {
313
+ const jsonText = matches.join('\n');
314
+ try { return JSON.parse(jsonText); } catch (e) {
315
+ return { raw_text: text, parse_error: e.message };
316
+ }
317
+ }
318
+
319
+ matches = [...text.matchAll(/```\s*\n(.*?)```/gs)].map(m => m[1]);
320
+ if (matches.length > 0) {
321
+ const jsonText = matches.join('\n');
322
+ try { return JSON.parse(jsonText); } catch (e) {
323
+ return { raw_text: text, parse_error: e.message };
324
+ }
325
+ }
326
+
327
+ try { return JSON.parse(text); } catch (e) {
328
+ return { raw_text: text, parse_error: e.message };
329
+ }
330
+ }
331
+
332
+ // ---------------------------------------------------------------------------
333
+ // Factory function
334
+ // ---------------------------------------------------------------------------
335
+
336
+ /**
337
+ * Build a PRIVACY-mode ModeAgentConfig for compliance assessment.
338
+ * @returns {ModeAgentConfig}
339
+ */
340
+ function _makePrivacyConfig() {
341
+ const reg = new ToolRegistry();
342
+ reg.register('scan_codebase', _PRIVACY_SCAN_CODEBASE_SCHEMA, _privacyScanCodebase);
343
+ reg.register('assess_dpia_triggers', _PRIVACY_ASSESS_DPIA_TRIGGERS_SCHEMA, _privacyAssessDpiaTriggers);
344
+ reg.register('write_dpia_report', _PRIVACY_WRITE_DPIA_REPORT_SCHEMA, _privacyWriteDpiaReport);
345
+
346
+ return new ModeAgentConfig({
347
+ mode: 'PRIVACY',
348
+ toolRegistry: reg,
349
+ systemPromptTemplate: _PRIVACY_SYSTEM_PROMPT,
350
+ validator: new DPIAValidator(),
351
+ maxTurns: 15,
352
+ requiresSandbox: false,
353
+ completionCheck: null,
354
+ outputParser: _privacyOutputParser,
355
+ outputFormat: 'json',
356
+ });
357
+ }
358
+
359
+ // ---------------------------------------------------------------------------
360
+ // Registration function — called by runner.initModes()
361
+ // ---------------------------------------------------------------------------
362
+
363
+ /**
364
+ * Register PRIVACY mode with the given registerMode function.
365
+ * @param {Function} registerMode
366
+ */
367
+ export function register(registerMode) {
368
+ registerMode('PRIVACY', _makePrivacyConfig);
369
+ }
@@ -0,0 +1,294 @@
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
+ * PURPLE mode agent — Emulate + Detect Cycle.
7
+ *
8
+ * Orchestrates a two-phase purple team exercise: (1) write an emulation plan,
9
+ * (2) write a matching Sigma detection rule. Cross-coherence validated.
10
+ * Ported from autonomous/modes/purple.py.
11
+ *
12
+ * @module autonomous/modes/purple
13
+ */
14
+
15
+ import { ModeAgentConfig, ToolRegistry } from '../framework.js';
16
+ import { ATTACK_TECHNIQUES } from './blue.js';
17
+ import { PurpleValidator } from '../validators/purple.js';
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Tool handlers
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /**
24
+ * Look up a MITRE ATT&CK technique by ID.
25
+ * Thin wrapper around BLUE's ATTACK_TECHNIQUES dict.
26
+ * @param {*} context
27
+ * @param {Object} toolInput
28
+ * @returns {string}
29
+ */
30
+ export function _purpleLookupTechnique(context, toolInput) {
31
+ const techniqueId = (toolInput.technique_id || '').toUpperCase();
32
+ const entry = ATTACK_TECHNIQUES[techniqueId];
33
+
34
+ if (!entry) {
35
+ return (
36
+ `Technique ${techniqueId} is not in the local lookup table. ` +
37
+ `Proceed using your own knowledge of this ATT&CK technique. ` +
38
+ `Available techniques: ${Object.keys(ATTACK_TECHNIQUES).sort().join(', ')}`
39
+ );
40
+ }
41
+
42
+ const dataSourcesStr = entry.data_sources.map(ds => ` - ${ds}`).join('\n');
43
+ return (
44
+ `ATT&CK Technique: ${techniqueId} — ${entry.name}\n` +
45
+ `Tactic: ${entry.tactic}\n` +
46
+ `Description: ${entry.description}\n` +
47
+ `Data Sources:\n${dataSourcesStr}`
48
+ );
49
+ }
50
+
51
+ /**
52
+ * Store an emulation plan JSON in the shared context.
53
+ * @param {*} context
54
+ * @param {Object} toolInput
55
+ * @returns {string}
56
+ */
57
+ export function _purpleWriteEmulationPlan(context, toolInput) {
58
+ const content = toolInput.content || '';
59
+
60
+ if (typeof context !== 'object' || context === null) {
61
+ return 'ERROR: Context must be a dict.';
62
+ }
63
+
64
+ let parsed;
65
+ try {
66
+ parsed = JSON.parse(content);
67
+ } catch (e) {
68
+ return `ERROR: Invalid JSON in emulation plan: ${e.message}`;
69
+ }
70
+
71
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
72
+ return 'ERROR: Emulation plan must be a JSON object (dict).';
73
+ }
74
+
75
+ context.emulation_plan = parsed;
76
+ const technique = parsed.technique_id || 'unknown';
77
+ return (
78
+ `Emulation plan stored for technique ${technique}. ` +
79
+ `Fields: ${Object.keys(parsed).sort().join(', ')}.`
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Store a Sigma detection rule YAML string in the shared context.
85
+ * @param {*} context
86
+ * @param {Object} toolInput
87
+ * @returns {string}
88
+ */
89
+ export function _purpleWriteDetectionRule(context, toolInput) {
90
+ const content = toolInput.content || '';
91
+ const filename = toolInput.filename || 'detection.yml';
92
+
93
+ if (typeof context !== 'object' || context === null) {
94
+ return 'ERROR: Context must be a dict.';
95
+ }
96
+
97
+ context.detection_rule = { filename, content };
98
+ return (
99
+ `Detection rule stored as ${filename}. ` +
100
+ `Content length: ${content.length} characters.`
101
+ );
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Tool schemas (Anthropic format)
106
+ // ---------------------------------------------------------------------------
107
+
108
+ const _PURPLE_LOOKUP_TECHNIQUE_SCHEMA = {
109
+ name: 'lookup_technique',
110
+ description:
111
+ 'Look up a MITRE ATT&CK technique by its ID (e.g. T1059.001). ' +
112
+ 'Returns technique name, tactic, description, and relevant data ' +
113
+ 'sources to help design emulation plans and detection rules.',
114
+ input_schema: {
115
+ type: 'object',
116
+ properties: {
117
+ technique_id: {
118
+ type: 'string',
119
+ description: 'ATT&CK technique ID (e.g. T1059.001, T1190)',
120
+ },
121
+ },
122
+ required: ['technique_id'],
123
+ },
124
+ };
125
+
126
+ const _PURPLE_WRITE_EMULATION_PLAN_SCHEMA = {
127
+ name: 'write_emulation_plan',
128
+ description:
129
+ 'Submit a completed emulation plan as a JSON object. Describes how to ' +
130
+ 'execute the ATT&CK technique including prerequisites, steps, artifacts, cleanup.',
131
+ input_schema: {
132
+ type: 'object',
133
+ properties: {
134
+ content: {
135
+ type: 'string',
136
+ description:
137
+ 'Full JSON content of the emulation plan. Must include: technique_id, ' +
138
+ 'technique_name, tactic, prerequisites, emulation_steps, expected_artifacts, cleanup.',
139
+ },
140
+ },
141
+ required: ['content'],
142
+ },
143
+ };
144
+
145
+ const _PURPLE_WRITE_DETECTION_RULE_SCHEMA = {
146
+ name: 'write_detection_rule',
147
+ description:
148
+ 'Submit a completed Sigma detection rule as YAML. The rule should ' +
149
+ 'detect the activity described in the emulation plan.',
150
+ input_schema: {
151
+ type: 'object',
152
+ properties: {
153
+ filename: {
154
+ type: 'string',
155
+ description: 'Filename for the rule (e.g. detect_powershell_execution.yml)',
156
+ },
157
+ content: {
158
+ type: 'string',
159
+ description: 'Full YAML content of the Sigma detection rule',
160
+ },
161
+ },
162
+ required: ['content'],
163
+ },
164
+ };
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // System prompt template
168
+ // ---------------------------------------------------------------------------
169
+
170
+ const _PURPLE_SYSTEM_PROMPT = `\
171
+ You are an expert purple team engineer who designs both adversary emulation \
172
+ plans and matching detection rules for MITRE ATT&CK techniques. Your task \
173
+ is to produce a coherent emulate→detect cycle for a specific TTP.
174
+
175
+ ## Task
176
+ Design an emulation plan and a matching Sigma detection rule for TTP: \
177
+ {ttp_id} — {ttp_description}
178
+
179
+ ## Phase 1: Emulation Plan
180
+ 1. Use the \`lookup_technique\` tool to retrieve metadata about the target TTP.
181
+ 2. Design an emulation plan with prerequisites, steps, artifacts, cleanup.
182
+ 3. Submit the plan via \`write_emulation_plan\` as a JSON object with fields:
183
+ technique_id, technique_name, tactic, prerequisites, emulation_steps,
184
+ expected_artifacts, cleanup.
185
+
186
+ ## Phase 2: Detection Rule
187
+ 4. Design a Sigma detection rule that catches the emulated activity.
188
+ 5. Submit the rule via \`write_detection_rule\` as valid Sigma YAML.
189
+
190
+ ## Coherence Requirement
191
+ The detection rule's ATT&CK tags MUST reference the same technique ID as \
192
+ the emulation plan.
193
+ `;
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Output parser (fallback for text-based output)
197
+ // ---------------------------------------------------------------------------
198
+
199
+ /**
200
+ * Extract emulation plan JSON and Sigma YAML from LLM text output.
201
+ * @param {string} text
202
+ * @returns {Object}
203
+ */
204
+ export function _purpleOutputParser(text) {
205
+ if (!text || !text.trim()) {
206
+ return { emulation_plan: null, detection_rule: null };
207
+ }
208
+
209
+ // Extract JSON emulation plan from ```json fences, then bare fences
210
+ let emulationPlan = null;
211
+ const jsonMatches = [...text.matchAll(/```json\s*\n(.*?)```/gs)].map(m => m[1]);
212
+ for (const match of jsonMatches) {
213
+ try {
214
+ const parsed = JSON.parse(match.trim());
215
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
216
+ emulationPlan = parsed;
217
+ break;
218
+ }
219
+ } catch {
220
+ continue;
221
+ }
222
+ }
223
+
224
+ if (emulationPlan === null) {
225
+ const bareMatches = [...text.matchAll(/```\s*\n(.*?)```/gs)].map(m => m[1]);
226
+ for (const match of bareMatches) {
227
+ try {
228
+ const parsed = JSON.parse(match.trim());
229
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
230
+ emulationPlan = parsed;
231
+ break;
232
+ }
233
+ } catch {
234
+ continue;
235
+ }
236
+ }
237
+ }
238
+
239
+ // Extract YAML detection rule from ```yaml fences, then bare fences
240
+ let detectionRule = null;
241
+ const yamlMatches = [...text.matchAll(/```ya?ml\s*\n(.*?)```/gs)].map(m => m[1]);
242
+ if (yamlMatches.length > 0) {
243
+ detectionRule = yamlMatches.join('\n---\n');
244
+ } else {
245
+ const bareMatches = [...text.matchAll(/```\s*\n(.*?)```/gs)].map(m => m[1]);
246
+ const yamlLike = bareMatches.filter(m =>
247
+ /^\s*(title|logsource|detection):/m.test(m)
248
+ );
249
+ if (yamlLike.length > 0) {
250
+ detectionRule = yamlLike.join('\n---\n');
251
+ }
252
+ }
253
+
254
+ return { emulation_plan: emulationPlan, detection_rule: detectionRule };
255
+ }
256
+
257
+ // ---------------------------------------------------------------------------
258
+ // Factory function
259
+ // ---------------------------------------------------------------------------
260
+
261
+ /**
262
+ * Build a PURPLE-mode ModeAgentConfig for emulate→detect cycle.
263
+ * @returns {ModeAgentConfig}
264
+ */
265
+ function _makePurpleConfig() {
266
+ const reg = new ToolRegistry();
267
+ reg.register('lookup_technique', _PURPLE_LOOKUP_TECHNIQUE_SCHEMA, _purpleLookupTechnique);
268
+ reg.register('write_emulation_plan', _PURPLE_WRITE_EMULATION_PLAN_SCHEMA, _purpleWriteEmulationPlan);
269
+ reg.register('write_detection_rule', _PURPLE_WRITE_DETECTION_RULE_SCHEMA, _purpleWriteDetectionRule);
270
+
271
+ return new ModeAgentConfig({
272
+ mode: 'PURPLE',
273
+ toolRegistry: reg,
274
+ systemPromptTemplate: _PURPLE_SYSTEM_PROMPT,
275
+ validator: new PurpleValidator(),
276
+ maxTurns: 15,
277
+ requiresSandbox: false,
278
+ completionCheck: null,
279
+ outputParser: _purpleOutputParser,
280
+ outputFormat: 'json',
281
+ });
282
+ }
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // Registration function — called by runner.initModes()
286
+ // ---------------------------------------------------------------------------
287
+
288
+ /**
289
+ * Register PURPLE mode with the given registerMode function.
290
+ * @param {Function} registerMode
291
+ */
292
+ export function register(registerMode) {
293
+ registerMode('PURPLE', _makePurpleConfig);
294
+ }