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,583 @@
|
|
|
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
|
+
* Autonomous security research engine for self-improving skill generation.
|
|
7
|
+
*
|
|
8
|
+
* @module autonomous/researcher
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, chmodSync } from 'node:fs';
|
|
13
|
+
import { join, resolve, isAbsolute } from 'node:path';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Data classes
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
export class ResearchHypothesis {
|
|
20
|
+
constructor({
|
|
21
|
+
hypothesisId = randomUUID(),
|
|
22
|
+
domain = '',
|
|
23
|
+
techniqueName = '',
|
|
24
|
+
description = '',
|
|
25
|
+
skillContent = '',
|
|
26
|
+
agentScript = '',
|
|
27
|
+
referenceContent = '',
|
|
28
|
+
confidence = 0,
|
|
29
|
+
status = 'proposed',
|
|
30
|
+
testResults = {},
|
|
31
|
+
createdAt = new Date().toISOString(),
|
|
32
|
+
} = {}) {
|
|
33
|
+
this.hypothesisId = hypothesisId;
|
|
34
|
+
this.domain = domain;
|
|
35
|
+
this.techniqueName = techniqueName;
|
|
36
|
+
this.description = description;
|
|
37
|
+
this.skillContent = skillContent;
|
|
38
|
+
this.agentScript = agentScript;
|
|
39
|
+
this.referenceContent = referenceContent;
|
|
40
|
+
this.confidence = confidence;
|
|
41
|
+
this.status = status;
|
|
42
|
+
this.testResults = testResults;
|
|
43
|
+
this.createdAt = createdAt;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class ResearchExperiment {
|
|
48
|
+
constructor({
|
|
49
|
+
experimentId = randomUUID(),
|
|
50
|
+
hypothesis = new ResearchHypothesis(),
|
|
51
|
+
validationChecks = [],
|
|
52
|
+
checkResults = {},
|
|
53
|
+
overallPass = false,
|
|
54
|
+
createdAt = new Date().toISOString(),
|
|
55
|
+
} = {}) {
|
|
56
|
+
this.experimentId = experimentId;
|
|
57
|
+
this.hypothesis = hypothesis;
|
|
58
|
+
this.validationChecks = validationChecks;
|
|
59
|
+
this.checkResults = checkResults;
|
|
60
|
+
this.overallPass = overallPass;
|
|
61
|
+
this.createdAt = createdAt;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// MITRE ATT&CK tactics mapped to technique IDs
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
const _MITRE_TACTICS = {
|
|
70
|
+
'reconnaissance': ['T1595', 'T1592', 'T1589', 'T1590', 'T1591', 'T1596', 'T1597', 'T1598'],
|
|
71
|
+
'resource-development': ['T1583', 'T1584', 'T1585', 'T1586', 'T1587', 'T1588'],
|
|
72
|
+
'initial-access': ['T1189', 'T1190', 'T1133', 'T1200', 'T1566', 'T1195', 'T1199', 'T1078'],
|
|
73
|
+
'execution': ['T1059', 'T1203', 'T1047', 'T1053', 'T1129', 'T1106', 'T1204'],
|
|
74
|
+
'persistence': ['T1098', 'T1197', 'T1547', 'T1136', 'T1543', 'T1546', 'T1053', 'T1078'],
|
|
75
|
+
'privilege-escalation': ['T1548', 'T1134', 'T1547', 'T1484', 'T1055', 'T1053', 'T1078'],
|
|
76
|
+
'defense-evasion': ['T1140', 'T1027', 'T1036', 'T1070', 'T1562', 'T1202', 'T1218', 'T1222'],
|
|
77
|
+
'credential-access': ['T1110', 'T1003', 'T1555', 'T1552', 'T1187', 'T1557', 'T1558'],
|
|
78
|
+
'discovery': ['T1087', 'T1482', 'T1083', 'T1135', 'T1046', 'T1057', 'T1018', 'T1082'],
|
|
79
|
+
'lateral-movement': ['T1021', 'T1210', 'T1534', 'T1570', 'T1563', 'T1080'],
|
|
80
|
+
'collection': ['T1560', 'T1123', 'T1119', 'T1005', 'T1039', 'T1025', 'T1074', 'T1114'],
|
|
81
|
+
'command-and-control': ['T1071', 'T1132', 'T1001', 'T1568', 'T1573', 'T1095', 'T1572', 'T1090'],
|
|
82
|
+
'exfiltration': ['T1041', 'T1048', 'T1567', 'T1029', 'T1030', 'T1537'],
|
|
83
|
+
'impact': ['T1485', 'T1486', 'T1565', 'T1491', 'T1561', 'T1499', 'T1529'],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// SkillGapAnalyzer
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
export class SkillGapAnalyzer {
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} skillsDir
|
|
93
|
+
*/
|
|
94
|
+
constructor(skillsDir) {
|
|
95
|
+
this.skillsDir = resolve(String(skillsDir));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @returns {string[]} */
|
|
99
|
+
_listDomains() {
|
|
100
|
+
if (!existsSync(this.skillsDir)) return [];
|
|
101
|
+
try {
|
|
102
|
+
return readdirSync(this.skillsDir)
|
|
103
|
+
.filter(d => !d.startsWith('.') && statSync(join(this.skillsDir, d)).isDirectory())
|
|
104
|
+
.sort();
|
|
105
|
+
} catch { return []; }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {string} domain
|
|
110
|
+
* @returns {string[]}
|
|
111
|
+
*/
|
|
112
|
+
_listTechniques(domain) {
|
|
113
|
+
const techDir = join(this.skillsDir, domain, 'techniques');
|
|
114
|
+
if (!existsSync(techDir)) return [];
|
|
115
|
+
try {
|
|
116
|
+
return readdirSync(techDir)
|
|
117
|
+
.filter(d => !d.startsWith('.') && statSync(join(techDir, d)).isDirectory())
|
|
118
|
+
.sort();
|
|
119
|
+
} catch { return []; }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Parse YAML frontmatter from a SKILL.md file.
|
|
124
|
+
* @param {string} skillMdPath
|
|
125
|
+
* @returns {object}
|
|
126
|
+
*/
|
|
127
|
+
_parseSkillFrontmatter(skillMdPath) {
|
|
128
|
+
if (!existsSync(skillMdPath)) return {};
|
|
129
|
+
try {
|
|
130
|
+
const text = readFileSync(skillMdPath, 'utf-8');
|
|
131
|
+
const parts = text.split('---');
|
|
132
|
+
for (let i = 1; i < parts.length; i++) {
|
|
133
|
+
const candidate = parts[i].trim();
|
|
134
|
+
if (!candidate) continue;
|
|
135
|
+
// Lazy import yaml
|
|
136
|
+
try {
|
|
137
|
+
const YAML = require('yaml');
|
|
138
|
+
const data = YAML.parse(candidate);
|
|
139
|
+
if (data && typeof data === 'object') return data;
|
|
140
|
+
} catch { continue; }
|
|
141
|
+
}
|
|
142
|
+
} catch { /* ok */ }
|
|
143
|
+
return {};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Map MITRE ATT&CK tactics to covered/uncovered techniques.
|
|
148
|
+
*/
|
|
149
|
+
analyzeCoverage() {
|
|
150
|
+
const domains = this._listDomains();
|
|
151
|
+
const coveredIds = new Set();
|
|
152
|
+
|
|
153
|
+
for (const domain of domains) {
|
|
154
|
+
const domainSkill = join(this.skillsDir, domain, 'SKILL.md');
|
|
155
|
+
const meta = this._parseSkillFrontmatter(domainSkill);
|
|
156
|
+
const mitre = meta.metadata || {};
|
|
157
|
+
if (typeof mitre === 'object' && Array.isArray(mitre['mitre-attack'])) {
|
|
158
|
+
for (const id of mitre['mitre-attack']) coveredIds.add(String(id));
|
|
159
|
+
}
|
|
160
|
+
for (const tech of this._listTechniques(domain)) {
|
|
161
|
+
const techSkill = join(this.skillsDir, domain, 'techniques', tech, 'SKILL.md');
|
|
162
|
+
const techMeta = this._parseSkillFrontmatter(techSkill);
|
|
163
|
+
const techMitre = techMeta.metadata || {};
|
|
164
|
+
if (typeof techMitre === 'object' && Array.isArray(techMitre['mitre-attack'])) {
|
|
165
|
+
for (const id of techMitre['mitre-attack']) coveredIds.add(String(id));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const coverage = {};
|
|
171
|
+
for (const [tactic, techniqueIds] of Object.entries(_MITRE_TACTICS)) {
|
|
172
|
+
const covered = techniqueIds.filter(t => coveredIds.has(t));
|
|
173
|
+
const uncovered = techniqueIds.filter(t => !coveredIds.has(t));
|
|
174
|
+
coverage[tactic] = {
|
|
175
|
+
total: techniqueIds.length,
|
|
176
|
+
covered,
|
|
177
|
+
uncovered,
|
|
178
|
+
pct: Math.round((covered.length / Math.max(techniqueIds.length, 1)) * 1000) / 10,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return coverage;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Identify domains with fewer than 24 techniques. */
|
|
185
|
+
findGaps() {
|
|
186
|
+
const gaps = [];
|
|
187
|
+
for (const domain of this._listDomains()) {
|
|
188
|
+
const techniques = this._listTechniques(domain);
|
|
189
|
+
if (techniques.length < 24) {
|
|
190
|
+
gaps.push({
|
|
191
|
+
domain,
|
|
192
|
+
techniqueCount: techniques.length,
|
|
193
|
+
deficit: 24 - techniques.length,
|
|
194
|
+
existing: techniques,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return gaps;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Suggest new technique names based on existing patterns.
|
|
203
|
+
* @param {string} domain
|
|
204
|
+
* @param {number} [count=5]
|
|
205
|
+
*/
|
|
206
|
+
suggestNewTechniques(domain, count = 5) {
|
|
207
|
+
const existing = this._listTechniques(domain);
|
|
208
|
+
const verbCounts = {};
|
|
209
|
+
for (const d of this._listDomains()) {
|
|
210
|
+
for (const tech of this._listTechniques(d)) {
|
|
211
|
+
const verb = tech.includes('-') ? tech.split('-')[0] : tech;
|
|
212
|
+
verbCounts[verb] = (verbCounts[verb] || 0) + 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const popularVerbs = Object.keys(verbCounts).sort((a, b) => verbCounts[b] - verbCounts[a]);
|
|
216
|
+
const domainWords = domain.replace(/-/g, ' ').split(' ');
|
|
217
|
+
const suggestions = [];
|
|
218
|
+
const existingSet = new Set(existing);
|
|
219
|
+
|
|
220
|
+
for (const verb of popularVerbs) {
|
|
221
|
+
if (suggestions.length >= count) break;
|
|
222
|
+
for (const kw of domainWords) {
|
|
223
|
+
const candidate = `${verb}-${kw}-operations`;
|
|
224
|
+
if (!existingSet.has(candidate) && !suggestions.includes(candidate)) {
|
|
225
|
+
suggestions.push(candidate);
|
|
226
|
+
if (suggestions.length >= count) break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const candidateAlt = `${verb}-${domain}-analysis`;
|
|
230
|
+
if (!existingSet.has(candidateAlt) && !suggestions.includes(candidateAlt)) {
|
|
231
|
+
suggestions.push(candidateAlt);
|
|
232
|
+
if (suggestions.length >= count) break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return suggestions.slice(0, count);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Get full coverage report. */
|
|
239
|
+
getCoverageReport() {
|
|
240
|
+
const domains = this._listDomains();
|
|
241
|
+
let totalTechniques = 0;
|
|
242
|
+
const domainStats = [];
|
|
243
|
+
for (const domain of domains) {
|
|
244
|
+
const techs = this._listTechniques(domain);
|
|
245
|
+
totalTechniques += techs.length;
|
|
246
|
+
domainStats.push({ domain, techniqueCount: techs.length });
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
totalDomains: domains.length,
|
|
250
|
+
totalTechniques,
|
|
251
|
+
domainStats,
|
|
252
|
+
mitreCoverage: this.analyzeCoverage(),
|
|
253
|
+
gapCount: this.findGaps().length,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// Content generators
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
function _titleCase(slug) {
|
|
263
|
+
return slug.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function _generateSkillMd(domain, techniqueName, description) {
|
|
267
|
+
return `<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
|
|
268
|
+
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
|
|
269
|
+
---
|
|
270
|
+
name: ${techniqueName}
|
|
271
|
+
description: >-
|
|
272
|
+
${description}
|
|
273
|
+
domain: cybersecurity
|
|
274
|
+
subdomain: ${domain}
|
|
275
|
+
tags:
|
|
276
|
+
- ${domain}
|
|
277
|
+
- ${techniqueName.split('-')[0]}
|
|
278
|
+
- security-automation
|
|
279
|
+
version: "1.0"
|
|
280
|
+
author: defconxt
|
|
281
|
+
license: AGPL-3.0
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
# ${_titleCase(techniqueName)}
|
|
285
|
+
|
|
286
|
+
## Quick Reference
|
|
287
|
+
|
|
288
|
+
| Item | Detail |
|
|
289
|
+
|---|---|
|
|
290
|
+
| Domain | ${domain} |
|
|
291
|
+
| Technique | ${techniqueName} |
|
|
292
|
+
| ATT&CK | See metadata |
|
|
293
|
+
| Difficulty | Intermediate |
|
|
294
|
+
|
|
295
|
+
## Overview
|
|
296
|
+
|
|
297
|
+
${description}
|
|
298
|
+
|
|
299
|
+
## Workflow
|
|
300
|
+
|
|
301
|
+
1. **Preparation** — Gather required tooling and access.
|
|
302
|
+
2. **Execution** — Run the technique using the agent script.
|
|
303
|
+
3. **Analysis** — Review output and correlate findings.
|
|
304
|
+
4. **Reporting** — Document results and remediation steps.
|
|
305
|
+
|
|
306
|
+
## Verification
|
|
307
|
+
|
|
308
|
+
\`\`\`bash
|
|
309
|
+
python3 scripts/agent.py analyze --target example
|
|
310
|
+
python3 scripts/agent.py report --format json
|
|
311
|
+
\`\`\`
|
|
312
|
+
|
|
313
|
+
## References
|
|
314
|
+
|
|
315
|
+
- MITRE ATT&CK: https://attack.mitre.org/
|
|
316
|
+
- See \`references/api-reference.md\` for command details.
|
|
317
|
+
`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function _generateAgentPy(domain, techniqueName) {
|
|
321
|
+
const funcPrefix = techniqueName.replace(/-/g, '_').slice(0, 20);
|
|
322
|
+
return `#!/usr/bin/env python3
|
|
323
|
+
"""Agent tooling for ${_titleCase(techniqueName).toLowerCase()}."""
|
|
324
|
+
|
|
325
|
+
import argparse
|
|
326
|
+
import json
|
|
327
|
+
import sys
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def ${funcPrefix}_analyze(target, verbose=False):
|
|
331
|
+
"""Analyze a target for ${techniqueName} indicators."""
|
|
332
|
+
results = {
|
|
333
|
+
"action": "analyze",
|
|
334
|
+
"technique": "${techniqueName}",
|
|
335
|
+
"domain": "${domain}",
|
|
336
|
+
"target": target,
|
|
337
|
+
"findings": [],
|
|
338
|
+
"risk_level": "medium",
|
|
339
|
+
}
|
|
340
|
+
if verbose:
|
|
341
|
+
results["debug"] = {"verbose": True}
|
|
342
|
+
return results
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def main():
|
|
346
|
+
parser = argparse.ArgumentParser(description="${techniqueName} agent")
|
|
347
|
+
parser.add_argument("--target", required=True)
|
|
348
|
+
parser.add_argument("--verbose", action="store_true")
|
|
349
|
+
parser.add_argument("--format", choices=["json", "text"], default="json")
|
|
350
|
+
args = parser.parse_args()
|
|
351
|
+
|
|
352
|
+
result = ${funcPrefix}_analyze(args.target, args.verbose)
|
|
353
|
+
if args.format == "json":
|
|
354
|
+
print(json.dumps(result, indent=2))
|
|
355
|
+
else:
|
|
356
|
+
for k, v in result.items():
|
|
357
|
+
print(f"{k}: {v}")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
if __name__ == "__main__":
|
|
361
|
+
main()
|
|
362
|
+
`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function _generateApiReference(domain, techniqueName) {
|
|
366
|
+
const title = _titleCase(techniqueName);
|
|
367
|
+
return `<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
|
|
368
|
+
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
|
|
369
|
+
|
|
370
|
+
# API Reference — ${title}
|
|
371
|
+
|
|
372
|
+
## Agent Script
|
|
373
|
+
|
|
374
|
+
| Command | Description |
|
|
375
|
+
|---|---|
|
|
376
|
+
| \`python3 agent.py analyze --target <t>\` | Analyze a target for ${techniqueName} indicators |
|
|
377
|
+
|
|
378
|
+
## Options
|
|
379
|
+
|
|
380
|
+
| Flag | Type | Default | Description |
|
|
381
|
+
|---|---|---|---|
|
|
382
|
+
| \`--target\` | str | *(required)* | Target identifier for analysis |
|
|
383
|
+
| \`--verbose\` | flag | false | Enable verbose output |
|
|
384
|
+
| \`--format\` | str | json | Output format |
|
|
385
|
+
|
|
386
|
+
## Output Format
|
|
387
|
+
|
|
388
|
+
All commands return JSON.
|
|
389
|
+
`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ---------------------------------------------------------------------------
|
|
393
|
+
// AutonomousResearcher
|
|
394
|
+
// ---------------------------------------------------------------------------
|
|
395
|
+
|
|
396
|
+
export class AutonomousResearcher {
|
|
397
|
+
/**
|
|
398
|
+
* @param {string} skillsDir
|
|
399
|
+
* @param {object} [memory] - CipherMemory instance (optional, lazy-loaded)
|
|
400
|
+
*/
|
|
401
|
+
constructor(skillsDir, memory = null) {
|
|
402
|
+
this.skillsDir = resolve(String(skillsDir));
|
|
403
|
+
this.memory = memory;
|
|
404
|
+
this._history = [];
|
|
405
|
+
this._analyzer = new SkillGapAnalyzer(this.skillsDir);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Create a complete hypothesis with SKILL.md, agent.py, and api-reference.md.
|
|
410
|
+
* @param {string} domain
|
|
411
|
+
* @param {string} techniqueName
|
|
412
|
+
* @returns {ResearchHypothesis}
|
|
413
|
+
*/
|
|
414
|
+
generateHypothesis(domain, techniqueName) {
|
|
415
|
+
const description =
|
|
416
|
+
`Automated security technique for ${_titleCase(techniqueName).toLowerCase()} ` +
|
|
417
|
+
`within the ${domain} domain. Provides analysis, scanning, and reporting ` +
|
|
418
|
+
`capabilities for defensive and offensive security workflows.`;
|
|
419
|
+
|
|
420
|
+
return new ResearchHypothesis({
|
|
421
|
+
domain,
|
|
422
|
+
techniqueName,
|
|
423
|
+
description,
|
|
424
|
+
skillContent: _generateSkillMd(domain, techniqueName, description),
|
|
425
|
+
agentScript: _generateAgentPy(domain, techniqueName),
|
|
426
|
+
referenceContent: _generateApiReference(domain, techniqueName),
|
|
427
|
+
confidence: 0.75,
|
|
428
|
+
status: 'proposed',
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Run validation checks against a hypothesis.
|
|
434
|
+
* @param {ResearchHypothesis} hypothesis
|
|
435
|
+
* @returns {ResearchExperiment}
|
|
436
|
+
*/
|
|
437
|
+
validateHypothesis(hypothesis) {
|
|
438
|
+
const checks = ['yaml_valid', 'script_compiles', 'references_exist', 'skill_has_sections', 'content_length'];
|
|
439
|
+
const results = {};
|
|
440
|
+
|
|
441
|
+
results.yaml_valid = AutonomousResearcher._checkYaml(hypothesis.skillContent);
|
|
442
|
+
results.script_compiles = AutonomousResearcher._checkCompiles(hypothesis.agentScript);
|
|
443
|
+
results.references_exist = AutonomousResearcher._checkReferences(hypothesis.referenceContent);
|
|
444
|
+
results.skill_has_sections = AutonomousResearcher._checkSkillSections(hypothesis.skillContent);
|
|
445
|
+
results.content_length = AutonomousResearcher._checkContentLength(
|
|
446
|
+
hypothesis.skillContent, hypothesis.agentScript, hypothesis.referenceContent
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
const overall = Object.values(results).every(v => v);
|
|
450
|
+
hypothesis.testResults = { ...results };
|
|
451
|
+
hypothesis.status = 'testing';
|
|
452
|
+
|
|
453
|
+
return new ResearchExperiment({
|
|
454
|
+
hypothesis,
|
|
455
|
+
validationChecks: checks,
|
|
456
|
+
checkResults: results,
|
|
457
|
+
overallPass: overall,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
static _checkYaml(skillContent) {
|
|
462
|
+
const parts = skillContent.split('---');
|
|
463
|
+
for (let i = 1; i < parts.length; i++) {
|
|
464
|
+
const candidate = parts[i].trim();
|
|
465
|
+
if (!candidate) continue;
|
|
466
|
+
try {
|
|
467
|
+
const YAML = require('yaml');
|
|
468
|
+
const data = YAML.parse(candidate);
|
|
469
|
+
return data && typeof data === 'object' && 'name' in data;
|
|
470
|
+
} catch { continue; }
|
|
471
|
+
}
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
static _checkCompiles(script) {
|
|
476
|
+
// For JS port: just verify it's a non-empty string with valid Python syntax indicators
|
|
477
|
+
// We can't actually parse Python in Node.js, but we can check for structure
|
|
478
|
+
return typeof script === 'string' && script.length > 100 && script.includes('def ') && script.includes('import ');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
static _checkReferences(refContent) {
|
|
482
|
+
return !!refContent && (refContent.trim().startsWith('#') || refContent.includes('# '));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
static _checkSkillSections(skillContent) {
|
|
486
|
+
const required = ['Quick Reference', 'Workflow', 'Verification', 'References'];
|
|
487
|
+
const lower = skillContent.toLowerCase();
|
|
488
|
+
return required.every(section => lower.includes(section.toLowerCase()));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
static _checkContentLength(skill, script, reference) {
|
|
492
|
+
return skill.length >= 200 && script.length >= 200 && reference.length >= 100;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Write hypothesis files to disk.
|
|
497
|
+
* @param {ResearchHypothesis} hypothesis
|
|
498
|
+
* @returns {boolean}
|
|
499
|
+
*/
|
|
500
|
+
applyHypothesis(hypothesis) {
|
|
501
|
+
try {
|
|
502
|
+
const base = join(this.skillsDir, hypothesis.domain, 'techniques', hypothesis.techniqueName);
|
|
503
|
+
const scriptsDir = join(base, 'scripts');
|
|
504
|
+
const refsDir = join(base, 'references');
|
|
505
|
+
|
|
506
|
+
mkdirSync(scriptsDir, { recursive: true });
|
|
507
|
+
mkdirSync(refsDir, { recursive: true });
|
|
508
|
+
|
|
509
|
+
writeFileSync(join(base, 'SKILL.md'), hypothesis.skillContent, 'utf-8');
|
|
510
|
+
const agentPath = join(scriptsDir, 'agent.py');
|
|
511
|
+
writeFileSync(agentPath, hypothesis.agentScript, 'utf-8');
|
|
512
|
+
try { chmodSync(agentPath, 0o755); } catch { /* ok */ }
|
|
513
|
+
writeFileSync(join(refsDir, 'api-reference.md'), hypothesis.referenceContent, 'utf-8');
|
|
514
|
+
|
|
515
|
+
hypothesis.status = 'accepted';
|
|
516
|
+
|
|
517
|
+
// Store in memory if available
|
|
518
|
+
if (this.memory) {
|
|
519
|
+
try {
|
|
520
|
+
// Lazy import to avoid circular deps
|
|
521
|
+
this.memory.store({
|
|
522
|
+
content: `Generated technique ${hypothesis.techniqueName} in ${hypothesis.domain}`,
|
|
523
|
+
keywords: [hypothesis.domain, hypothesis.techniqueName, hypothesis.hypothesisId],
|
|
524
|
+
});
|
|
525
|
+
} catch { /* memory is optional */ }
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
return true;
|
|
529
|
+
} catch {
|
|
530
|
+
hypothesis.status = 'rejected';
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Full cycle: generate → validate → apply if passing.
|
|
537
|
+
* @param {string} domain
|
|
538
|
+
* @param {string} techniqueName
|
|
539
|
+
* @returns {ResearchExperiment}
|
|
540
|
+
*/
|
|
541
|
+
runExperiment(domain, techniqueName) {
|
|
542
|
+
const hypothesis = this.generateHypothesis(domain, techniqueName);
|
|
543
|
+
const experiment = this.validateHypothesis(hypothesis);
|
|
544
|
+
|
|
545
|
+
if (experiment.overallPass) {
|
|
546
|
+
const applied = this.applyHypothesis(hypothesis);
|
|
547
|
+
if (!applied) {
|
|
548
|
+
hypothesis.status = 'rejected';
|
|
549
|
+
experiment.overallPass = false;
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
hypothesis.status = 'rejected';
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
this._history.push({
|
|
556
|
+
experimentId: experiment.experimentId,
|
|
557
|
+
hypothesisId: hypothesis.hypothesisId,
|
|
558
|
+
domain,
|
|
559
|
+
techniqueName,
|
|
560
|
+
overallPass: experiment.overallPass,
|
|
561
|
+
status: hypothesis.status,
|
|
562
|
+
checks: { ...experiment.checkResults },
|
|
563
|
+
createdAt: experiment.createdAt,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
return experiment;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Run experiments for multiple techniques in a domain.
|
|
571
|
+
* @param {string} domain
|
|
572
|
+
* @param {string[]} techniques
|
|
573
|
+
* @returns {ResearchExperiment[]}
|
|
574
|
+
*/
|
|
575
|
+
batchExpand(domain, techniques) {
|
|
576
|
+
return techniques.map(tech => this.runExperiment(domain, tech));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** Return all experiments with results. */
|
|
580
|
+
getResearchHistory() {
|
|
581
|
+
return [...this._history];
|
|
582
|
+
}
|
|
583
|
+
}
|