cipher-security 2.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.
- package/bin/cipher.js +566 -0
- package/lib/api/billing.js +321 -0
- package/lib/api/compliance.js +693 -0
- package/lib/api/controls.js +1401 -0
- package/lib/api/index.js +49 -0
- package/lib/api/marketplace.js +467 -0
- package/lib/api/openai-proxy.js +383 -0
- package/lib/api/server.js +685 -0
- package/lib/autonomous/feedback-loop.js +554 -0
- package/lib/autonomous/framework.js +512 -0
- package/lib/autonomous/index.js +97 -0
- package/lib/autonomous/leaderboard.js +594 -0
- package/lib/autonomous/modes/architect.js +412 -0
- package/lib/autonomous/modes/blue.js +386 -0
- package/lib/autonomous/modes/incident.js +684 -0
- package/lib/autonomous/modes/privacy.js +369 -0
- package/lib/autonomous/modes/purple.js +294 -0
- package/lib/autonomous/modes/recon.js +250 -0
- package/lib/autonomous/parallel.js +587 -0
- package/lib/autonomous/researcher.js +583 -0
- package/lib/autonomous/runner.js +955 -0
- package/lib/autonomous/scheduler.js +615 -0
- package/lib/autonomous/task-parser.js +127 -0
- package/lib/autonomous/validators/forensic.js +266 -0
- package/lib/autonomous/validators/osint.js +216 -0
- package/lib/autonomous/validators/privacy.js +296 -0
- package/lib/autonomous/validators/purple.js +298 -0
- package/lib/autonomous/validators/sigma.js +248 -0
- package/lib/autonomous/validators/threat-model.js +363 -0
- package/lib/benchmark/agent.js +119 -0
- package/lib/benchmark/baselines.js +43 -0
- package/lib/benchmark/builder.js +143 -0
- package/lib/benchmark/config.js +35 -0
- package/lib/benchmark/coordinator.js +91 -0
- package/lib/benchmark/index.js +20 -0
- package/lib/benchmark/llm.js +58 -0
- package/lib/benchmark/models.js +137 -0
- package/lib/benchmark/reporter.js +103 -0
- package/lib/benchmark/runner.js +103 -0
- package/lib/benchmark/sandbox.js +96 -0
- package/lib/benchmark/scorer.js +32 -0
- package/lib/benchmark/solver.js +166 -0
- package/lib/benchmark/tools.js +62 -0
- package/lib/bot/bot.js +238 -0
- package/lib/brand.js +105 -0
- package/lib/commands.js +100 -0
- package/lib/complexity.js +377 -0
- package/lib/config.js +213 -0
- package/lib/gateway/client.js +309 -0
- package/lib/gateway/commands.js +991 -0
- package/lib/gateway/config-validate.js +109 -0
- package/lib/gateway/gateway.js +367 -0
- package/lib/gateway/index.js +62 -0
- package/lib/gateway/mode.js +309 -0
- package/lib/gateway/plugins.js +222 -0
- package/lib/gateway/prompt.js +214 -0
- package/lib/mcp/server.js +262 -0
- package/lib/memory/compressor.js +425 -0
- package/lib/memory/engine.js +763 -0
- package/lib/memory/evolution.js +668 -0
- package/lib/memory/index.js +58 -0
- package/lib/memory/orchestrator.js +506 -0
- package/lib/memory/retriever.js +515 -0
- package/lib/memory/synthesizer.js +333 -0
- package/lib/pipeline/async-scanner.js +510 -0
- package/lib/pipeline/binary-analysis.js +1043 -0
- package/lib/pipeline/dom-xss-scanner.js +435 -0
- package/lib/pipeline/github-actions.js +792 -0
- package/lib/pipeline/index.js +124 -0
- package/lib/pipeline/osint.js +498 -0
- package/lib/pipeline/sarif.js +373 -0
- package/lib/pipeline/scanner.js +880 -0
- package/lib/pipeline/template-manager.js +525 -0
- package/lib/pipeline/xss-scanner.js +353 -0
- package/lib/setup-wizard.js +288 -0
- package/package.json +31 -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
|
+
}
|