monomind 1.17.0 → 1.17.2

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 (94) hide show
  1. package/.claude/agents/engineering/engineering-security-engineer.md +1 -1
  2. package/.claude/commands/mastermind/_repeat.md +4 -0
  3. package/.claude/commands/mastermind/master.md +52 -1
  4. package/.claude/scheduled_tasks.lock +1 -1
  5. package/.claude/skills/mastermind/_repeat.md +2 -0
  6. package/package.json +1 -1
  7. package/packages/@monomind/cli/.claude/agents/engineering/engineering-security-engineer.md +1 -1
  8. package/packages/@monomind/cli/.claude/commands/mastermind/_repeat.md +4 -0
  9. package/packages/@monomind/cli/.claude/commands/mastermind/master.md +52 -1
  10. package/packages/@monomind/cli/.claude/skills/mastermind/_repeat.md +2 -0
  11. package/packages/@monomind/cli/dist/src/__tests__/browse-analyzer.test.js +42 -59
  12. package/packages/@monomind/cli/dist/src/agents/registry-builder.d.ts +8 -0
  13. package/packages/@monomind/cli/dist/src/agents/registry-builder.js +22 -0
  14. package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +18 -0
  15. package/packages/@monomind/cli/dist/src/browser/dashboard/ui.html +37 -125
  16. package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.d.ts +17 -0
  17. package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +320 -0
  18. package/packages/@monomind/cli/dist/src/commands/agent-ops.d.ts +9 -0
  19. package/packages/@monomind/cli/dist/src/commands/agent-ops.js +329 -0
  20. package/packages/@monomind/cli/dist/src/commands/agent.js +5 -907
  21. package/packages/@monomind/cli/dist/src/commands/analyze-ast.d.ts +26 -0
  22. package/packages/@monomind/cli/dist/src/commands/analyze-ast.js +284 -0
  23. package/packages/@monomind/cli/dist/src/commands/analyze-boundaries.d.ts +14 -0
  24. package/packages/@monomind/cli/dist/src/commands/analyze-boundaries.js +295 -0
  25. package/packages/@monomind/cli/dist/src/commands/analyze-diff.d.ts +8 -0
  26. package/packages/@monomind/cli/dist/src/commands/analyze-diff.js +395 -0
  27. package/packages/@monomind/cli/dist/src/commands/analyze-graph.d.ts +14 -0
  28. package/packages/@monomind/cli/dist/src/commands/analyze-graph.js +304 -0
  29. package/packages/@monomind/cli/dist/src/commands/analyze-imports.d.ts +11 -0
  30. package/packages/@monomind/cli/dist/src/commands/analyze-imports.js +287 -0
  31. package/packages/@monomind/cli/dist/src/commands/analyze-symbols.d.ts +14 -0
  32. package/packages/@monomind/cli/dist/src/commands/analyze-symbols.js +302 -0
  33. package/packages/@monomind/cli/dist/src/commands/analyze.d.ts +38 -0
  34. package/packages/@monomind/cli/dist/src/commands/analyze.js +12 -1827
  35. package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.d.ts +26 -0
  36. package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +189 -0
  37. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.d.ts +20 -0
  38. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +432 -0
  39. package/packages/@monomind/cli/dist/src/commands/doctor.js +54 -943
  40. package/packages/@monomind/cli/dist/src/commands/hive-mind-comms.d.ts +11 -0
  41. package/packages/@monomind/cli/dist/src/commands/hive-mind-comms.js +242 -0
  42. package/packages/@monomind/cli/dist/src/commands/hive-mind-helpers.d.ts +35 -0
  43. package/packages/@monomind/cli/dist/src/commands/hive-mind-helpers.js +203 -0
  44. package/packages/@monomind/cli/dist/src/commands/hive-mind-ops.d.ts +8 -0
  45. package/packages/@monomind/cli/dist/src/commands/hive-mind-ops.js +233 -0
  46. package/packages/@monomind/cli/dist/src/commands/hive-mind-spawn.d.ts +12 -0
  47. package/packages/@monomind/cli/dist/src/commands/hive-mind-spawn.js +274 -0
  48. package/packages/@monomind/cli/dist/src/commands/hive-mind.js +10 -1129
  49. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.d.ts +4 -4
  50. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.js +19 -819
  51. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-gaps.d.ts +7 -0
  52. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-gaps.js +334 -0
  53. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-routing.d.ts +7 -0
  54. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-routing.js +399 -0
  55. package/packages/@monomind/cli/dist/src/commands/init-subcommands.d.ts +8 -0
  56. package/packages/@monomind/cli/dist/src/commands/init-subcommands.js +156 -0
  57. package/packages/@monomind/cli/dist/src/commands/init-upgrade.d.ts +6 -0
  58. package/packages/@monomind/cli/dist/src/commands/init-upgrade.js +203 -0
  59. package/packages/@monomind/cli/dist/src/commands/init-wizard.d.ts +6 -0
  60. package/packages/@monomind/cli/dist/src/commands/init-wizard.js +246 -0
  61. package/packages/@monomind/cli/dist/src/commands/init.js +6 -623
  62. package/packages/@monomind/cli/dist/src/commands/memory-admin.d.ts +10 -0
  63. package/packages/@monomind/cli/dist/src/commands/memory-admin.js +433 -0
  64. package/packages/@monomind/cli/dist/src/commands/memory-crud.d.ts +9 -0
  65. package/packages/@monomind/cli/dist/src/commands/memory-crud.js +342 -0
  66. package/packages/@monomind/cli/dist/src/commands/memory-list.d.ts +10 -0
  67. package/packages/@monomind/cli/dist/src/commands/memory-list.js +321 -0
  68. package/packages/@monomind/cli/dist/src/commands/memory-transfer.d.ts +9 -0
  69. package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +372 -0
  70. package/packages/@monomind/cli/dist/src/commands/memory.d.ts +6 -0
  71. package/packages/@monomind/cli/dist/src/commands/memory.js +10 -1441
  72. package/packages/@monomind/cli/dist/src/commands/neural-core.d.ts +8 -0
  73. package/packages/@monomind/cli/dist/src/commands/neural-core.js +274 -0
  74. package/packages/@monomind/cli/dist/src/commands/neural-optimize.d.ts +7 -0
  75. package/packages/@monomind/cli/dist/src/commands/neural-optimize.js +332 -0
  76. package/packages/@monomind/cli/dist/src/commands/neural-registry.d.ts +7 -0
  77. package/packages/@monomind/cli/dist/src/commands/neural-registry.js +290 -0
  78. package/packages/@monomind/cli/dist/src/commands/neural.js +3 -974
  79. package/packages/@monomind/cli/dist/src/commands/platforms.js +327 -7
  80. package/packages/@monomind/cli/dist/src/commands/security-cve.d.ts +6 -0
  81. package/packages/@monomind/cli/dist/src/commands/security-cve.js +310 -0
  82. package/packages/@monomind/cli/dist/src/commands/security-misc.d.ts +9 -0
  83. package/packages/@monomind/cli/dist/src/commands/security-misc.js +293 -0
  84. package/packages/@monomind/cli/dist/src/commands/security-scan.d.ts +18 -0
  85. package/packages/@monomind/cli/dist/src/commands/security-scan.js +328 -0
  86. package/packages/@monomind/cli/dist/src/commands/security.js +3 -958
  87. package/packages/@monomind/cli/dist/src/commands/session.js +1 -1
  88. package/packages/@monomind/cli/dist/src/commands/swarm.js +23 -17
  89. package/packages/@monomind/cli/dist/src/index.js +8 -37
  90. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +77 -0
  91. package/packages/@monomind/cli/dist/src/parser.js +11 -6
  92. package/packages/@monomind/cli/dist/src/routing/llm-caller.js +1 -2
  93. package/packages/@monomind/cli/package.json +2 -3
  94. package/packages/@monomind/cli/scripts/understand-analyze.mjs +1 -1
@@ -5,964 +5,9 @@
5
5
  * github.com/monoes/monomind
6
6
  */
7
7
  import { output } from '../output.js';
8
- import { statSync, readFileSync, readdirSync, writeFileSync, renameSync, mkdirSync, realpathSync } from 'fs';
9
- import { join, resolve, sep, relative } from 'path';
10
- import { execFile } from 'child_process';
11
- import { promisify } from 'util';
12
- import * as https from 'https';
13
- // ─── Shared secret scanning ─────────────────────────────────────────────────
14
- const SECRET_PATTERNS = [
15
- { pattern: /['"](?:sk-|sk_live_|sk_test_)[a-zA-Z0-9]{20,}['"]/g, type: 'API Key (Stripe/OpenAI)' },
16
- { pattern: /['"]AKIA[A-Z0-9]{16}['"]/g, type: 'AWS Access Key' },
17
- { pattern: /['"]ghp_[a-zA-Z0-9]{36}['"]/g, type: 'GitHub Token' },
18
- { pattern: /['"]xox[baprs]-[a-zA-Z0-9-]+['"]/g, type: 'Slack Token' },
19
- { pattern: /password\s*[:=]\s*['"][^'"]{8,}['"]/gi, type: 'Hardcoded Password' },
20
- ];
21
- function findSecretsInDir(dir, depthLimit, baseDir, findings) {
22
- if (depthLimit <= 0)
23
- return;
24
- try {
25
- const entries = readdirSync(dir, { withFileTypes: true });
26
- for (const entry of entries) {
27
- const isDotEnv = /^\.env(\..+)?$/.test(entry.name);
28
- if ((entry.name.startsWith('.') && !isDotEnv) || entry.name === 'node_modules' || entry.name === 'dist')
29
- continue;
30
- const fullPath = join(dir, entry.name);
31
- if (entry.isDirectory()) {
32
- findSecretsInDir(fullPath, depthLimit - 1, baseDir, findings);
33
- }
34
- else if (entry.isFile() && (/\.(ts|js|json|yml|yaml)$/.test(entry.name) || isDotEnv) && !entry.name.endsWith('.d.ts')) {
35
- try {
36
- if (statSync(fullPath).size > 1024 * 1024)
37
- continue; // skip files > 1 MB
38
- const content = readFileSync(fullPath, 'utf-8');
39
- const lines = content.split('\n');
40
- for (let i = 0; i < lines.length; i++) {
41
- for (const { pattern, type } of SECRET_PATTERNS) {
42
- pattern.lastIndex = 0;
43
- let m;
44
- while ((m = pattern.exec(lines[i])) !== null) {
45
- findings.push({
46
- severity: output.warning('HIGH'),
47
- type: 'Hardcoded Secret',
48
- location: `${relative(baseDir, fullPath)}:${i + 1}`,
49
- description: type,
50
- });
51
- }
52
- }
53
- }
54
- }
55
- catch { /* file read error */ }
56
- }
57
- }
58
- }
59
- catch { /* dir read error */ }
60
- }
61
- // Scan subcommand
62
- const scanCommand = {
63
- name: 'scan',
64
- description: 'Run security scan on target (code, dependencies, containers)',
65
- options: [
66
- { name: 'target', short: 't', type: 'string', description: 'Target path or URL to scan', default: '.' },
67
- { name: 'depth', short: 'd', type: 'string', description: 'Scan depth: quick, standard, deep', default: 'standard' },
68
- { name: 'type', type: 'string', description: 'Scan type: code, deps, container, all', default: 'all' },
69
- { name: 'output', short: 'o', type: 'string', description: 'Output format: text, json, sarif', default: 'text' },
70
- { name: 'fix', short: 'f', type: 'boolean', description: 'Auto-fix vulnerabilities where possible' },
71
- ],
72
- examples: [
73
- { command: 'monomind security scan -t ./src', description: 'Scan source directory' },
74
- { command: 'monomind security scan --depth deep --fix', description: 'Deep scan with auto-fix' },
75
- ],
76
- action: async (ctx) => {
77
- const target = ctx.flags.target || '.';
78
- const depth = ctx.flags.depth || 'standard';
79
- const scanType = ctx.flags.type || 'all';
80
- const fix = ctx.flags.fix;
81
- // Guard: confine --target to cwd; execSync and scanDir run inside it
82
- if (target !== '.') {
83
- try {
84
- const resolvedTgt = realpathSync(resolve(target));
85
- const cwd = realpathSync(process.cwd());
86
- if (!resolvedTgt.startsWith(cwd + sep) && resolvedTgt !== cwd) {
87
- output.printError('--target must be within the current working directory');
88
- return { success: false };
89
- }
90
- }
91
- catch {
92
- output.printError(`--target path does not exist or is not accessible: ${target}`);
93
- return { success: false };
94
- }
95
- }
96
- output.writeln();
97
- output.writeln(output.bold('Security Scan'));
98
- output.writeln(output.dim('─'.repeat(50)));
99
- const spinner = output.createSpinner({ text: `Scanning ${target}...`, spinner: 'dots' });
100
- spinner.start();
101
- const findings = [];
102
- let criticalCount = 0, highCount = 0, mediumCount = 0, lowCount = 0;
103
- try {
104
- const fs = await import('fs');
105
- const path = await import('path');
106
- const { execSync } = await import('child_process');
107
- // Phase 1: npm audit for dependency vulnerabilities
108
- if (scanType === 'all' || scanType === 'deps') {
109
- spinner.setText('Checking dependencies with npm audit...');
110
- try {
111
- const packageJsonPath = path.resolve(target, 'package.json');
112
- if (fs.existsSync(packageJsonPath)) {
113
- let auditResult;
114
- try {
115
- auditResult = execSync('npm audit --json', {
116
- cwd: path.resolve(target),
117
- encoding: 'utf-8',
118
- maxBuffer: 10 * 1024 * 1024,
119
- stdio: ['pipe', 'pipe', 'pipe'],
120
- timeout: 30_000,
121
- });
122
- }
123
- catch (auditErr) {
124
- // npm audit exits non-zero when vulnerabilities found — stdout still has JSON
125
- auditResult = auditErr.stdout || '{}';
126
- }
127
- try {
128
- const audit = JSON.parse(auditResult);
129
- if (audit.vulnerabilities) {
130
- for (const [pkg, vuln] of Object.entries(audit.vulnerabilities)) {
131
- const sev = vuln.severity || 'low';
132
- const firstVia = Array.isArray(vuln.via) ? vuln.via[0] : undefined;
133
- const title = firstVia && typeof firstVia === 'object' && firstVia.title ? firstVia.title : 'Vulnerability';
134
- if (sev === 'critical')
135
- criticalCount++;
136
- else if (sev === 'high')
137
- highCount++;
138
- else if (sev === 'moderate' || sev === 'medium')
139
- mediumCount++;
140
- else
141
- lowCount++;
142
- findings.push({
143
- severity: sev === 'critical' ? output.error('CRITICAL') :
144
- sev === 'high' ? output.warning('HIGH') :
145
- sev === 'moderate' || sev === 'medium' ? output.warning('MEDIUM') : output.info('LOW'),
146
- type: 'Dependency CVE',
147
- location: `package.json:${pkg}`,
148
- description: title.substring(0, 35),
149
- });
150
- }
151
- }
152
- }
153
- catch { /* JSON parse failed, no vulns */ }
154
- }
155
- }
156
- catch { /* npm audit failed */ }
157
- }
158
- // Phase 2: Scan for hardcoded secrets
159
- if (scanType === 'all' || scanType === 'code') {
160
- spinner.setText('Scanning for hardcoded secrets...');
161
- const scanDepth = depth === 'deep' ? 10 : depth === 'standard' ? 5 : 3;
162
- const prevCount = findings.length;
163
- findSecretsInDir(path.resolve(target), scanDepth, path.resolve(target), findings);
164
- highCount += findings.length - prevCount;
165
- }
166
- // Phase 3: Check for common security issues in code
167
- if ((scanType === 'all' || scanType === 'code') && depth !== 'quick') {
168
- spinner.setText('Analyzing code patterns...');
169
- const codePatterns = [
170
- { pattern: /eval\s*\(/g, type: 'Eval Usage', severity: 'medium', desc: 'eval() can execute arbitrary code' },
171
- { pattern: /innerHTML\s*=/g, type: 'innerHTML', severity: 'medium', desc: 'XSS risk with innerHTML' },
172
- { pattern: /dangerouslySetInnerHTML/g, type: 'React XSS', severity: 'medium', desc: 'React XSS risk' },
173
- { pattern: /child_process.*exec[^S]/g, type: 'Command Injection', severity: 'high', desc: 'Possible command injection' },
174
- { pattern: /\$\{.*\}.*sql|sql.*\$\{/gi, type: 'SQL Injection', severity: 'high', desc: 'Possible SQL injection' },
175
- ];
176
- const scanCodeDir = (dir, depthLimit) => {
177
- if (depthLimit <= 0)
178
- return;
179
- try {
180
- const entries = fs.readdirSync(dir, { withFileTypes: true });
181
- for (const entry of entries) {
182
- if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist')
183
- continue;
184
- const fullPath = path.join(dir, entry.name);
185
- if (entry.isDirectory()) {
186
- scanCodeDir(fullPath, depthLimit - 1);
187
- }
188
- else if (entry.isFile() && /\.(ts|js|tsx|jsx)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
189
- try {
190
- if (fs.statSync(fullPath).size > 1024 * 1024)
191
- continue; // skip files > 1 MB
192
- const content = fs.readFileSync(fullPath, 'utf-8');
193
- const lines = content.split('\n');
194
- for (let i = 0; i < lines.length; i++) {
195
- for (const { pattern, type, severity, desc } of codePatterns) {
196
- pattern.lastIndex = 0;
197
- let m;
198
- while ((m = pattern.exec(lines[i])) !== null) {
199
- if (severity === 'high')
200
- highCount++;
201
- else
202
- mediumCount++;
203
- findings.push({
204
- severity: severity === 'high' ? output.warning('HIGH') : output.warning('MEDIUM'),
205
- type,
206
- location: `${path.relative(target, fullPath)}:${i + 1}`,
207
- description: desc,
208
- });
209
- }
210
- }
211
- }
212
- }
213
- catch { /* file read error */ }
214
- }
215
- }
216
- }
217
- catch { /* dir read error */ }
218
- };
219
- const scanDepth = depth === 'deep' ? 10 : 5;
220
- scanCodeDir(path.resolve(target), scanDepth);
221
- }
222
- spinner.succeed('Scan complete');
223
- // Display results
224
- output.writeln();
225
- if (findings.length > 0) {
226
- output.printTable({
227
- columns: [
228
- { key: 'severity', header: 'Severity', width: 12 },
229
- { key: 'type', header: 'Type', width: 18 },
230
- { key: 'location', header: 'Location', width: 25 },
231
- { key: 'description', header: 'Description', width: 35 },
232
- ],
233
- data: findings.slice(0, 20), // Show first 20
234
- });
235
- if (findings.length > 20) {
236
- output.writeln(output.dim(`... and ${findings.length - 20} more issues`));
237
- }
238
- }
239
- else {
240
- output.writeln(output.success('No security issues found!'));
241
- }
242
- output.writeln();
243
- output.printBox([
244
- `Target: ${target}`,
245
- `Depth: ${depth}`,
246
- `Type: ${scanType}`,
247
- ``,
248
- `Critical: ${criticalCount} High: ${highCount} Medium: ${mediumCount} Low: ${lowCount}`,
249
- `Total Issues: ${findings.length}`,
250
- ].join('\n'), 'Scan Summary');
251
- // Auto-fix if requested
252
- if (fix && criticalCount + highCount > 0) {
253
- // Refuse --fix when target is outside cwd: `npm audit fix` runs lifecycle scripts
254
- // (pre/post-install) from the target directory's package.json, allowing arbitrary
255
- // code execution if the target was attacker-controlled.
256
- const resolvedTarget = realpathSync(path.resolve(target));
257
- const cwd = realpathSync(process.cwd());
258
- if (!resolvedTarget.startsWith(cwd + path.sep) && resolvedTarget !== cwd) {
259
- output.writeln();
260
- output.printError('--fix is only allowed when --target is within the current working directory');
261
- return { success: false };
262
- }
263
- output.writeln();
264
- const fixSpinner = output.createSpinner({ text: 'Attempting to fix vulnerabilities...', spinner: 'dots' });
265
- fixSpinner.start();
266
- try {
267
- try {
268
- execSync('npm audit fix', { cwd: resolvedTarget, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
269
- }
270
- catch { /* npm audit fix may exit non-zero */ }
271
- fixSpinner.succeed('Applied available fixes (run scan again to verify)');
272
- }
273
- catch {
274
- fixSpinner.fail('Some fixes could not be applied automatically');
275
- }
276
- }
277
- return { success: findings.length === 0 || (criticalCount === 0 && highCount === 0) };
278
- }
279
- catch (error) {
280
- spinner.fail('Scan failed');
281
- output.printError(`Error: ${error}`);
282
- return { success: false };
283
- }
284
- },
285
- };
286
- // ─── CVE helpers ────────────────────────────────────────────────────────────
287
- const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
288
- function getCveCache(cveId, cacheDir) {
289
- const filePath = join(cacheDir, `${cveId.toUpperCase()}.json`);
290
- try {
291
- const stat = statSync(filePath);
292
- if (Date.now() - stat.mtimeMs > CACHE_TTL_MS)
293
- return null;
294
- return JSON.parse(readFileSync(filePath, 'utf8'));
295
- }
296
- catch {
297
- return null;
298
- }
299
- }
300
- const CVE_ID_RE = /^CVE-\d{4}-\d{4,}$/i;
301
- function saveCveCache(cveId, cacheDir, data) {
302
- if (!CVE_ID_RE.test(cveId))
303
- throw new Error('Invalid CVE ID');
304
- mkdirSync(cacheDir, { recursive: true });
305
- const dest = join(cacheDir, `${cveId.toUpperCase()}.json`);
306
- const tmp = dest + '.tmp';
307
- writeFileSync(tmp, JSON.stringify(data));
308
- renameSync(tmp, dest);
309
- }
310
- function httpsGet(url, timeoutMs = 10_000) {
311
- return new Promise((resolve, reject) => {
312
- const req = https.get(url, { headers: { 'User-Agent': 'monomind-cli/1.0' }, timeout: timeoutMs }, (res) => {
313
- if (res.statusCode !== 200) {
314
- req.destroy();
315
- reject(new Error(`HTTP ${res.statusCode}`));
316
- return;
317
- }
318
- let data = '';
319
- res.on('data', (chunk) => { data += chunk; });
320
- res.on('end', () => resolve(data));
321
- });
322
- req.on('timeout', () => req.destroy(new Error(`Request timed out after ${timeoutMs}ms`)));
323
- req.on('error', reject);
324
- });
325
- }
326
- function severityColor(severity, score) {
327
- const s = (severity || '').toUpperCase();
328
- const label = s + (score !== undefined ? ` (${score})` : '');
329
- if (s === 'CRITICAL')
330
- return output.error(label);
331
- if (s === 'HIGH')
332
- return output.warning(label);
333
- if (s === 'MEDIUM')
334
- return output.info(label);
335
- return output.dim(label || 'UNKNOWN');
336
- }
337
- const execFileAsync = promisify(execFile);
338
- // ─── CVE subcommand ──────────────────────────────────────────────────────────
339
- const cveCommand = {
340
- name: 'cve',
341
- description: 'Check CVEs via NVD/OSV or list project vulnerabilities via npm audit',
342
- options: [
343
- { name: 'check', short: 'c', type: 'string', description: 'Check specific CVE ID (e.g. CVE-2024-1234)' },
344
- { name: 'list', short: 'l', type: 'boolean', description: 'List all vulnerabilities via npm audit' },
345
- { name: 'severity', short: 's', type: 'string', description: 'Filter by severity: critical, high, medium, low' },
346
- { name: 'json', type: 'boolean', description: 'Output as JSON' },
347
- { name: 'no-cache', type: 'boolean', description: 'Skip cache and fetch fresh data' },
348
- ],
349
- examples: [
350
- { command: 'monomind security cve --list', description: 'List vulnerabilities from npm audit' },
351
- { command: 'monomind security cve -c CVE-2024-1234', description: 'Check specific CVE via NVD/OSV' },
352
- { command: 'monomind security cve --list --severity high', description: 'Show only high-severity issues' },
353
- ],
354
- action: async (ctx) => {
355
- const checkCve = ctx.flags.check;
356
- const doList = ctx.flags.list;
357
- const severityFilter = ctx.flags.severity?.toLowerCase();
358
- const jsonOutput = ctx.flags.json;
359
- const noCache = ctx.flags['no-cache'];
360
- output.writeln();
361
- output.writeln(output.bold('CVE / Vulnerability Scanner'));
362
- output.writeln(output.dim('─'.repeat(50)));
363
- // ── --check CVE-XXXX-YYYY ──────────────────────────────────────────────
364
- if (checkCve) {
365
- const CVE_PATTERN = /^CVE-\d{4}-\d{4,}$/i;
366
- if (!CVE_PATTERN.test(checkCve)) {
367
- output.writeln(output.error(`Invalid CVE ID format: "${checkCve}"`));
368
- output.writeln(output.dim('Expected format: CVE-YYYY-NNNN (e.g. CVE-2024-12345)'));
369
- return { success: false };
370
- }
371
- const cveId = checkCve.toUpperCase();
372
- const cacheDir = join(ctx.cwd, '.monomind', 'cache', 'cve');
373
- // Check cache first (unless --no-cache)
374
- let cveData = noCache ? null : getCveCache(cveId, cacheDir);
375
- let source = 'cache';
376
- if (!cveData) {
377
- const spinner = output.createSpinner({ text: `Fetching ${cveId} from NVD...`, spinner: 'dots' });
378
- spinner.start();
379
- // Try NVD first
380
- try {
381
- const nvdUrl = `https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=${cveId}`;
382
- const nvdRaw = await httpsGet(nvdUrl);
383
- cveData = { _source: 'nvd', ...JSON.parse(nvdRaw) };
384
- source = 'NVD';
385
- spinner.succeed(`Fetched from NVD`);
386
- }
387
- catch {
388
- spinner.setText(`NVD unavailable — trying OSV...`);
389
- // Fallback: OSV
390
- try {
391
- const osvUrl = `https://api.osv.dev/v1/vulns/${cveId}`;
392
- const osvRaw = await httpsGet(osvUrl);
393
- cveData = { _source: 'osv', ...JSON.parse(osvRaw) };
394
- source = 'OSV';
395
- spinner.succeed(`Fetched from OSV`);
396
- }
397
- catch {
398
- spinner.fail('Could not fetch CVE data');
399
- output.writeln(output.error('Could not fetch CVE data — check your network connection'));
400
- return { success: false };
401
- }
402
- }
403
- // Cache the result — only cache non-empty NVD responses
404
- const nvdVulns = cveData.vulnerabilities;
405
- if (!Array.isArray(nvdVulns) || nvdVulns.length > 0) {
406
- saveCveCache(cveId, cacheDir, cveData);
407
- }
408
- }
409
- // Parse and display
410
- const raw = cveData;
411
- if (jsonOutput) {
412
- output.writeln(JSON.stringify(raw, null, 2));
413
- return { success: true };
414
- }
415
- if (raw._source === 'nvd') {
416
- // NVD v2 parsing
417
- const vulns = raw.vulnerabilities;
418
- if (!vulns || vulns.length === 0) {
419
- output.writeln(output.warning(`No data found for ${cveId}`));
420
- return { success: true };
421
- }
422
- const cve = vulns[0].cve;
423
- const published = (cve.published || '').split('T')[0];
424
- const lastMod = (cve.lastModified || '').split('T')[0];
425
- const descriptions = cve.descriptions;
426
- const desc = descriptions?.find(d => d.lang === 'en')?.value || 'No description available';
427
- const metrics = cve.metrics;
428
- const cvssV31 = metrics?.cvssMetricV31;
429
- const cvssData = cvssV31?.[0]?.cvssData;
430
- const score = cvssData?.baseScore;
431
- const severity = cvssData?.baseSeverity || 'N/A';
432
- const references = cve.references;
433
- output.writeln();
434
- output.printBox([
435
- `CVE ID: ${cveId}`,
436
- `Source: ${source}`,
437
- `Published: ${published}`,
438
- `Last Modified: ${lastMod}`,
439
- `Severity: ${severityColor(severity, score)}`,
440
- ``,
441
- `Description:`,
442
- ` ${desc}`,
443
- ``,
444
- `References:`,
445
- ...(references || []).slice(0, 3).map(r => ` - ${r.url}`),
446
- ].join('\n'), 'CVE Details');
447
- }
448
- else {
449
- // OSV parsing
450
- const osv = raw;
451
- const osvId = osv.id || cveId;
452
- const summary = osv.summary || osv.details || 'No description available';
453
- const affected = osv.affected;
454
- const references = osv.references;
455
- output.writeln();
456
- const affectedLines = [];
457
- if (affected && affected.length > 0) {
458
- for (const a of affected.slice(0, 5)) {
459
- const pkgName = a.package?.name || 'unknown';
460
- const ecosystem = a.package?.ecosystem || '';
461
- affectedLines.push(` - ${pkgName}${ecosystem ? ` (${ecosystem})` : ''}`);
462
- }
463
- }
464
- output.printBox([
465
- `CVE ID: ${osvId}`,
466
- `Source: OSV (CVSS score: N/A)`,
467
- `Severity: N/A`,
468
- ``,
469
- `Description:`,
470
- ` ${summary}`,
471
- ...(affectedLines.length > 0 ? ['', 'Affected packages:', ...affectedLines] : []),
472
- ``,
473
- `References:`,
474
- ...(references || []).slice(0, 3).map(r => ` - ${r.url}`),
475
- ].join('\n'), 'CVE Details');
476
- }
477
- return { success: true };
478
- }
479
- // ── --list ─────────────────────────────────────────────────────────────
480
- if (doList) {
481
- const spinner = output.createSpinner({ text: 'Running npm audit...', spinner: 'dots' });
482
- spinner.start();
483
- let auditOutput = '';
484
- try {
485
- const { stdout } = await execFileAsync('npm', ['audit', '--json'], {
486
- cwd: ctx.cwd,
487
- timeout: 30000,
488
- });
489
- auditOutput = stdout;
490
- }
491
- catch (err) {
492
- // Exit code 1 means vulnerabilities found — stdout still has JSON
493
- const execErr = err;
494
- auditOutput = execErr.stdout || '';
495
- if (!auditOutput) {
496
- spinner.fail('npm audit failed');
497
- output.writeln(output.warning('npm audit failed: ' + (execErr.message || 'unknown error')));
498
- output.writeln(output.dim('Make sure package-lock.json exists (run `npm install` first).'));
499
- return { success: false };
500
- }
501
- }
502
- spinner.succeed('npm audit complete');
503
- let auditJson;
504
- try {
505
- auditJson = JSON.parse(auditOutput);
506
- }
507
- catch {
508
- output.writeln(output.error('Could not parse npm audit output'));
509
- return { success: false };
510
- }
511
- if (jsonOutput) {
512
- output.writeln(JSON.stringify(auditJson, null, 2));
513
- return { success: true };
514
- }
515
- const vulnerabilities = auditJson.vulnerabilities;
516
- const metadata = auditJson.metadata;
517
- const counts = metadata?.vulnerabilities || {};
518
- const rows = [];
519
- if (vulnerabilities) {
520
- for (const [pkgName, vuln] of Object.entries(vulnerabilities)) {
521
- const sev = vuln.severity || 'unknown';
522
- // Normalize severity filter: accept "medium" to match "moderate"
523
- if (severityFilter) {
524
- const normalizedSev = sev === 'moderate' ? 'medium' : sev;
525
- if (normalizedSev !== severityFilter && sev !== severityFilter)
526
- continue;
527
- }
528
- // Extract advisory/CVE info from first object-type via entry
529
- const viaObj = vuln.via.find(v => typeof v === 'object');
530
- let advisoryId = '—';
531
- if (viaObj?.url) {
532
- // Try to extract CVE or GHSA from URL
533
- const cveMatch = viaObj.url.match(/CVE-\d{4}-\d+/i);
534
- const ghsaMatch = viaObj.url.match(/GHSA-[a-z0-9-]+/i);
535
- if (cveMatch)
536
- advisoryId = cveMatch[0].toUpperCase();
537
- else if (ghsaMatch)
538
- advisoryId = ghsaMatch[0].toUpperCase();
539
- else
540
- advisoryId = viaObj.url.split('/').pop() || advisoryId;
541
- }
542
- const sevColored = sev === 'critical' ? output.error('CRITICAL') :
543
- sev === 'high' ? output.warning('HIGH') :
544
- sev === 'moderate' || sev === 'medium' ? output.info('MEDIUM') :
545
- output.dim(sev.toUpperCase());
546
- const fixAvail = vuln.fixAvailable === true ? output.success('Yes') :
547
- vuln.fixAvailable && typeof vuln.fixAvailable === 'object' ?
548
- output.success(`${vuln.fixAvailable.version}`) :
549
- output.dim('No');
550
- rows.push({
551
- id: advisoryId,
552
- severity: sevColored,
553
- package: pkgName,
554
- range: vuln.range || '—',
555
- fix: fixAvail,
556
- });
557
- }
558
- }
559
- output.writeln();
560
- if (rows.length === 0) {
561
- output.writeln(output.success('No vulnerabilities found' + (severityFilter ? ` matching severity: ${severityFilter}` : '') + '.'));
562
- }
563
- else {
564
- output.printTable({
565
- columns: [
566
- { key: 'id', header: 'CVE / Advisory', width: 22 },
567
- { key: 'severity', header: 'Severity', width: 12 },
568
- { key: 'package', header: 'Package', width: 22 },
569
- { key: 'range', header: 'Affected Range', width: 20 },
570
- { key: 'fix', header: 'Fix Available', width: 16 },
571
- ],
572
- data: rows,
573
- });
574
- }
575
- // Summary line
576
- const critical = counts['critical'] || 0;
577
- const high = counts['high'] || 0;
578
- const moderate = counts['moderate'] || 0;
579
- const low = counts['low'] || 0;
580
- output.writeln();
581
- output.writeln(output.bold('Summary: ') +
582
- output.error(`${critical} critical`) + ' ' +
583
- output.warning(`${high} high`) + ' ' +
584
- output.info(`${moderate} medium`) + ' ' +
585
- output.dim(`${low} low`));
586
- return { success: critical === 0 && high === 0 };
587
- }
588
- // No subcommand provided — show usage
589
- output.writeln('Usage:');
590
- output.printList([
591
- '--check CVE-XXXX-YYYY Look up a specific CVE via NVD/OSV',
592
- '--list List project vulnerabilities (npm audit)',
593
- '--severity <level> Filter --list by: critical, high, medium, low',
594
- '--json Output raw JSON',
595
- '--no-cache Skip local cache (forces fresh fetch)',
596
- ]);
597
- output.writeln();
598
- output.writeln(output.dim('Examples:'));
599
- output.writeln(output.dim(' monomind security cve --check CVE-2021-44228'));
600
- output.writeln(output.dim(' monomind security cve --list --severity critical'));
601
- return { success: true };
602
- },
603
- };
604
- // Threats subcommand
605
- const threatsCommand = {
606
- name: 'threats',
607
- description: 'Threat modeling and analysis',
608
- options: [
609
- { name: 'model', short: 'm', type: 'string', description: 'Threat model: stride, dread, pasta', default: 'stride' },
610
- { name: 'scope', short: 's', type: 'string', description: 'Analysis scope', default: '.' },
611
- { name: 'export', short: 'e', type: 'string', description: 'Export format: json, md, html' },
612
- ],
613
- examples: [
614
- { command: 'monomind security threats --model stride', description: 'Run STRIDE analysis' },
615
- { command: 'monomind security threats -e md', description: 'Export as markdown' },
616
- ],
617
- action: async (ctx) => {
618
- const model = ctx.flags.model || 'stride';
619
- output.writeln();
620
- output.writeln(output.bold(`Threat Model: ${model.toUpperCase()}`));
621
- output.writeln(output.dim('─'.repeat(50)));
622
- output.printTable({
623
- columns: [
624
- { key: 'category', header: 'Category', width: 20 },
625
- { key: 'threat', header: 'Threat', width: 30 },
626
- { key: 'risk', header: 'Risk', width: 10 },
627
- { key: 'mitigation', header: 'Mitigation', width: 30 },
628
- ],
629
- data: [
630
- { category: 'Spoofing', threat: 'API key theft', risk: output.error('High'), mitigation: 'Use secure key storage' },
631
- { category: 'Tampering', threat: 'Data manipulation', risk: output.warning('Medium'), mitigation: 'Input validation' },
632
- { category: 'Repudiation', threat: 'Action denial', risk: output.info('Low'), mitigation: 'Audit logging' },
633
- { category: 'Info Disclosure', threat: 'Data leakage', risk: output.error('High'), mitigation: 'Encryption at rest' },
634
- { category: 'DoS', threat: 'Resource exhaustion', risk: output.warning('Medium'), mitigation: 'Rate limiting' },
635
- { category: 'Elevation', threat: 'Privilege escalation', risk: output.error('High'), mitigation: 'RBAC implementation' },
636
- ],
637
- });
638
- return { success: true };
639
- },
640
- };
641
- // Audit subcommand
642
- const auditCommand = {
643
- name: 'audit',
644
- description: 'Security audit logging and compliance',
645
- options: [
646
- { name: 'action', short: 'a', type: 'string', description: 'Action: log, list, export, clear', default: 'list' },
647
- { name: 'limit', short: 'l', type: 'number', description: 'Number of entries to show', default: '20' },
648
- { name: 'filter', short: 'f', type: 'string', description: 'Filter by event type' },
649
- ],
650
- examples: [
651
- { command: 'monomind security audit --action list', description: 'List audit logs' },
652
- { command: 'monomind security audit -a export', description: 'Export audit trail' },
653
- ],
654
- action: async (ctx) => {
655
- const action = ctx.flags.action || 'list';
656
- output.writeln();
657
- output.writeln(output.bold('Security Audit Log'));
658
- output.writeln(output.dim('─'.repeat(60)));
659
- // Generate real audit entries from .swarm/ state and session history
660
- const { existsSync, readFileSync, readdirSync, statSync } = await import('fs');
661
- const { join } = await import('path');
662
- const auditEntries = [];
663
- const swarmDir = join(process.cwd(), '.swarm');
664
- // Check session files for real audit events
665
- if (existsSync(swarmDir)) {
666
- try {
667
- const files = readdirSync(swarmDir).filter(f => f.endsWith('.json'));
668
- for (const file of files.slice(-10)) {
669
- try {
670
- const stat = statSync(join(swarmDir, file));
671
- const ts = stat.mtime.toISOString().replace('T', ' ').substring(0, 19);
672
- auditEntries.push({
673
- timestamp: ts,
674
- event: file.includes('session') ? 'SESSION_UPDATE' :
675
- file.includes('swarm') ? 'SWARM_ACTIVITY' :
676
- file.includes('memory') ? 'MEMORY_WRITE' : 'CONFIG_CHANGE',
677
- user: 'system',
678
- status: output.success('Success')
679
- });
680
- }
681
- catch { /* skip */ }
682
- }
683
- }
684
- catch { /* ignore */ }
685
- }
686
- // Add current session entry
687
- const now = new Date().toISOString().replace('T', ' ').substring(0, 19);
688
- auditEntries.push({ timestamp: now, event: 'AUDIT_RUN', user: 'cli', status: output.success('Success') });
689
- // Sort by timestamp desc
690
- auditEntries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
691
- if (auditEntries.length === 0) {
692
- output.writeln(output.dim('No audit events found. Initialize a project first: monomind init'));
693
- }
694
- else {
695
- output.printTable({
696
- columns: [
697
- { key: 'timestamp', header: 'Timestamp', width: 22 },
698
- { key: 'event', header: 'Event', width: 20 },
699
- { key: 'user', header: 'User', width: 15 },
700
- { key: 'status', header: 'Status', width: 12 },
701
- ],
702
- data: auditEntries.slice(0, parseInt(ctx.flags.limit || '20', 10)),
703
- });
704
- }
705
- return { success: true };
706
- },
707
- };
708
- // Secrets subcommand
709
- const secretsCommand = {
710
- name: 'secrets',
711
- description: 'Detect hardcoded secrets in codebase',
712
- options: [
713
- { name: 'path', short: 'p', type: 'string', description: 'Path to scan', default: '.' },
714
- { name: 'depth', short: 'd', type: 'string', description: 'Scan depth: quick, standard, deep', default: 'standard' },
715
- ],
716
- examples: [
717
- { command: 'monomind security secrets', description: 'Scan current directory for secrets' },
718
- { command: 'monomind security secrets -p ./src --depth deep', description: 'Deep scan of src directory' },
719
- ],
720
- action: async (ctx) => {
721
- const targetPath = ctx.flags.path || '.';
722
- const depth = ctx.flags.depth || 'standard';
723
- // Guard: confine --path to cwd
724
- if (targetPath !== '.') {
725
- try {
726
- const resolvedTgt = realpathSync(resolve(targetPath));
727
- const cwd = realpathSync(process.cwd());
728
- if (!resolvedTgt.startsWith(cwd + sep) && resolvedTgt !== cwd) {
729
- output.printError('--path must be within the current working directory');
730
- return { success: false };
731
- }
732
- }
733
- catch {
734
- output.printError(`--path does not exist or is not accessible: ${targetPath}`);
735
- return { success: false };
736
- }
737
- }
738
- output.writeln();
739
- output.writeln(output.bold('Secret Detection'));
740
- output.writeln(output.dim('─'.repeat(50)));
741
- const spinner = output.createSpinner({ text: `Scanning ${targetPath}...`, spinner: 'dots' });
742
- spinner.start();
743
- const findings = [];
744
- const scanDepth = depth === 'deep' ? 10 : depth === 'standard' ? 5 : 3;
745
- findSecretsInDir(resolve(targetPath), scanDepth, resolve(targetPath), findings);
746
- spinner.succeed('Scan complete');
747
- output.writeln();
748
- if (findings.length === 0) {
749
- output.writeln(output.success('No secrets found.'));
750
- }
751
- else {
752
- output.printTable({
753
- columns: [
754
- { key: 'severity', header: 'Severity', width: 12 },
755
- { key: 'description', header: 'Description', width: 25 },
756
- { key: 'location', header: 'Location', width: 40 },
757
- ],
758
- data: findings.slice(0, 20),
759
- });
760
- if (findings.length > 20) {
761
- output.writeln(output.dim(`... and ${findings.length - 20} more`));
762
- }
763
- }
764
- output.writeln();
765
- output.writeln(output.bold('Summary: ') + `${findings.length} secret(s) found in ${targetPath}`);
766
- return { success: findings.length === 0 };
767
- },
768
- };
769
- // Defend subcommand (MonoFence integration)
770
- const defendCommand = {
771
- name: 'defend',
772
- description: 'AI manipulation defense - detect prompt injection, jailbreaks, and PII',
773
- options: [
774
- { name: 'input', short: 'i', type: 'string', description: 'Input text to scan for threats' },
775
- { name: 'file', short: 'f', type: 'string', description: 'File to scan for threats' },
776
- { name: 'quick', short: 'Q', type: 'boolean', description: 'Quick scan (faster, less detailed)' },
777
- { name: 'learn', short: 'l', type: 'boolean', description: 'Enable learning mode', default: 'true' },
778
- { name: 'stats', short: 's', type: 'boolean', description: 'Show detection statistics' },
779
- { name: 'output', short: 'o', type: 'string', description: 'Output format: text, json', default: 'text' },
780
- ],
781
- examples: [
782
- { command: 'monomind security defend -i "ignore previous instructions"', description: 'Scan text for threats' },
783
- { command: 'monomind security defend -f ./prompts.txt', description: 'Scan file for threats' },
784
- { command: 'monomind security defend --stats', description: 'Show detection statistics' },
785
- ],
786
- action: async (ctx) => {
787
- const inputText = ctx.flags.input;
788
- const filePath = ctx.flags.file;
789
- const quickMode = ctx.flags.quick;
790
- const showStats = ctx.flags.stats;
791
- const outputFormat = ctx.flags.output || 'text';
792
- const enableLearning = ctx.flags.learn !== false;
793
- output.writeln();
794
- output.writeln(output.bold('🛡️ MonoFence - AI Manipulation Defense System'));
795
- output.writeln(output.dim('─'.repeat(55)));
796
- // Dynamic import of aidefence (allows package to be optional)
797
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
798
- let createMonoDefence;
799
- try {
800
- // @ts-expect-error — optional peer dep resolved at runtime
801
- const aidefence = await import('monofence-ai');
802
- createMonoDefence = aidefence.createMonoDefence;
803
- }
804
- catch {
805
- output.printError('MonoFence package not installed. Run: npm install monofence-ai');
806
- return { success: false, message: 'MonoFence not available' };
807
- }
808
- const defender = createMonoDefence({ enableLearning });
809
- // Show stats mode
810
- if (showStats) {
811
- const stats = await defender.getStats();
812
- output.writeln();
813
- output.printBox([
814
- `Detection Count: ${stats.detectionCount}`,
815
- `Avg Detection Time: ${stats.avgDetectionTimeMs.toFixed(3)}ms`,
816
- `Learned Patterns: ${stats.learnedPatterns}`,
817
- `Mitigation Strategies: ${stats.mitigationStrategies}`,
818
- `Avg Mitigation Effectiveness: ${(stats.avgMitigationEffectiveness * 100).toFixed(1)}%`,
819
- ].join('\n'), 'Detection Statistics');
820
- return { success: true };
821
- }
822
- // Get input to scan
823
- let textToScan = inputText;
824
- if (filePath) {
825
- // Guard: confine --file to cwd before any stat/read
826
- try {
827
- const resolvedFile = realpathSync(resolve(filePath));
828
- const cwd = realpathSync(process.cwd());
829
- if (!resolvedFile.startsWith(cwd + sep) && resolvedFile !== cwd) {
830
- output.printError('--file must be within the current working directory');
831
- return { success: false };
832
- }
833
- }
834
- catch {
835
- output.printError(`File not found: ${filePath}`);
836
- return { success: false, message: 'File not found' };
837
- }
838
- try {
839
- const fs = await import('fs/promises');
840
- const MAX_DEFEND_FILE_BYTES = 10 * 1024 * 1024;
841
- const { size } = await fs.stat(filePath);
842
- if (size > MAX_DEFEND_FILE_BYTES) {
843
- output.printError(`File too large (${(size / 1024 / 1024).toFixed(1)} MB). Maximum is 10 MB.`);
844
- return { success: false, message: 'File too large' };
845
- }
846
- textToScan = await fs.readFile(filePath, 'utf-8');
847
- output.writeln(output.dim(`Reading file: ${filePath}`));
848
- }
849
- catch (err) {
850
- output.printError(`Failed to read file: ${filePath}`);
851
- return { success: false, message: 'File not found' };
852
- }
853
- }
854
- if (!textToScan) {
855
- output.writeln('Usage: monomind security defend -i "<text>" or -f <file>');
856
- output.writeln();
857
- output.writeln('Options:');
858
- output.printList([
859
- '-i, --input Text to scan for AI manipulation attempts',
860
- '-f, --file File path to scan',
861
- '-q, --quick Quick scan mode (faster)',
862
- '-s, --stats Show detection statistics',
863
- '--learn Enable pattern learning (default: true)',
864
- ]);
865
- return { success: true };
866
- }
867
- const spinner = output.createSpinner({ text: 'Scanning for threats...', spinner: 'dots' });
868
- spinner.start();
869
- // Perform scan
870
- const startTime = performance.now();
871
- const qr = quickMode ? defender.quickScan(textToScan) : null;
872
- const result = quickMode
873
- ? { ...qr, threats: [], piiFound: false, detectionTimeMs: 0, inputHash: '', safe: !qr.threat }
874
- : await defender.detect(textToScan);
875
- const scanTime = performance.now() - startTime;
876
- spinner.stop();
877
- // JSON output
878
- if (outputFormat === 'json') {
879
- output.writeln(JSON.stringify({
880
- safe: result.safe,
881
- threats: result.threats || [],
882
- piiFound: result.piiFound,
883
- detectionTimeMs: scanTime,
884
- }, null, 2));
885
- return { success: true };
886
- }
887
- // Text output
888
- output.writeln();
889
- if (result.safe && !result.piiFound) {
890
- output.writeln(output.success('✅ No threats detected'));
891
- }
892
- else {
893
- if (!result.safe && result.threats) {
894
- output.writeln(output.error(`⚠️ ${result.threats.length} threat(s) detected:`));
895
- output.writeln();
896
- for (const threat of result.threats) {
897
- const severityColor = {
898
- critical: output.error,
899
- high: output.warning,
900
- medium: output.info,
901
- low: output.dim,
902
- }[threat.severity] || output.dim;
903
- output.writeln(` ${severityColor(`[${threat.severity.toUpperCase()}]`)} ${threat.type}`);
904
- output.writeln(` ${output.dim(threat.description)}`);
905
- output.writeln(` Confidence: ${(threat.confidence * 100).toFixed(1)}%`);
906
- output.writeln();
907
- }
908
- // Show mitigation recommendations
909
- const criticalThreats = result.threats.filter(t => t.severity === 'critical');
910
- if (criticalThreats.length > 0 && enableLearning) {
911
- output.writeln(output.bold('Recommended Mitigations:'));
912
- for (const threat of criticalThreats) {
913
- const mitigation = await defender.getBestMitigation(threat.type);
914
- if (mitigation) {
915
- output.writeln(` ${threat.type}: ${output.bold(mitigation.strategy)} (${(mitigation.effectiveness * 100).toFixed(0)}% effective)`);
916
- }
917
- }
918
- output.writeln();
919
- }
920
- }
921
- if (result.piiFound) {
922
- output.writeln(output.warning('⚠️ PII detected (emails, SSNs, API keys, etc.)'));
923
- output.writeln();
924
- }
925
- }
926
- output.writeln(output.dim(`Detection time: ${scanTime.toFixed(3)}ms`));
927
- return { success: result.safe };
928
- },
929
- };
930
- // Redteam subcommand (PyRIT-style adversarial testing)
931
- // Source: https://github.com/Azure/PyRIT
932
- const redteamCommand = {
933
- name: 'redteam',
934
- description: 'Adversarial red-team testing — prompt injection, jailbreak, and manipulation scenarios (PyRIT-style)',
935
- options: [
936
- { name: 'target', short: 't', type: 'string', description: 'Target agent ID or endpoint to test' },
937
- { name: 'scenarios', short: 's', type: 'string', description: 'Comma-separated attack categories: injection,jailbreak,adversarial,pii,all', default: 'all' },
938
- { name: 'iterations', short: 'n', type: 'number', description: 'Number of attack iterations per scenario', default: '5' },
939
- { name: 'dry-run', type: 'boolean', description: 'Print attack prompts without executing them' },
940
- { name: 'output', short: 'o', type: 'string', description: 'Output format: text, json', default: 'text' },
941
- { name: 'threshold', type: 'number', description: 'Failure threshold 0-1 — report when success rate exceeds this', default: '0.1' },
942
- ],
943
- examples: [
944
- { command: 'monomind security redteam --target my-agent', description: 'Run all red-team scenarios against an agent' },
945
- { command: 'monomind security redteam --target my-agent --scenarios injection,jailbreak', description: 'Test specific attack categories' },
946
- { command: 'monomind security redteam --target my-agent --dry-run', description: 'Preview attack prompts without executing' },
947
- { command: 'monomind security redteam --target my-agent --output json', description: 'JSON output for CI integration' },
948
- ],
949
- action: async (ctx) => {
950
- const target = ctx.flags.target;
951
- output.writeln();
952
- output.writeln(output.warning('⚠ Red-team simulation not yet implemented.'));
953
- output.writeln(output.dim('This command will contact the target agent and evaluate its real responses once implemented.'));
954
- if (target) {
955
- output.writeln(output.dim(`Target specified: ${target}`));
956
- }
957
- output.writeln();
958
- output.writeln('To test prompt injection resistance manually:');
959
- output.writeln(output.dim(' 1. Run the target agent'));
960
- output.writeln(output.dim(' 2. Send adversarial prompts and evaluate responses'));
961
- output.writeln(output.dim(' 3. Check agent logs for unexpected tool calls'));
962
- return { success: false, exitCode: 1 };
963
- },
964
- };
965
- // Main security command
8
+ import { scanCommand, secretsCommand } from './security-scan.js';
9
+ import { cveCommand } from './security-cve.js';
10
+ import { threatsCommand, auditCommand, defendCommand, redteamCommand } from './security-misc.js';
966
11
  export const securityCommand = {
967
12
  name: 'security',
968
13
  description: 'Security scanning, CVE detection, threat modeling, AI defense',