UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

909 lines 50.5 kB
/** * V3 CLI Security Command * Security scanning, CVE detection, threat modeling, vulnerability management * * Created with ❤️ by ruv.io */ import { output } from '../output.js'; import { execSync } from 'node:child_process'; // Scan subcommand const scanCommand = { name: 'scan', description: 'Run security scan on target (code, dependencies, containers)', options: [ { name: 'target', short: 't', type: 'string', description: 'Target path or URL to scan', default: '.' }, { name: 'depth', short: 'd', type: 'string', description: 'Scan depth: quick, standard, deep', default: 'standard' }, { name: 'type', type: 'string', description: 'Scan type: code, deps, container, all', default: 'all' }, { name: 'output', short: 'o', type: 'string', description: 'Output format: text, json, sarif', default: 'text' }, { name: 'fix', short: 'f', type: 'boolean', description: 'Auto-fix vulnerabilities where possible' }, ], examples: [ { command: 'claude-flow security scan -t ./src', description: 'Scan source directory' }, { command: 'claude-flow security scan --depth deep --fix', description: 'Deep scan with auto-fix' }, ], action: async (ctx) => { const target = ctx.flags.target || '.'; const depth = ctx.flags.depth || 'standard'; const scanType = ctx.flags.type || 'all'; const fix = ctx.flags.fix; output.writeln(); output.writeln(output.bold('Security Scan')); output.writeln(output.dim('─'.repeat(50))); const spinner = output.createSpinner({ text: `Scanning ${target}...`, spinner: 'dots' }); spinner.start(); const findings = []; let criticalCount = 0, highCount = 0, mediumCount = 0, lowCount = 0; try { const fs = await import('fs'); const path = await import('path'); const { execSync } = await import('child_process'); // Phase 1: npm audit for dependency vulnerabilities if (scanType === 'all' || scanType === 'deps') { spinner.setText('Checking dependencies with npm audit...'); try { const packageJsonPath = path.resolve(target, 'package.json'); if (fs.existsSync(packageJsonPath)) { let auditResult; try { auditResult = execSync('npm audit --json', { cwd: path.resolve(target), encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'], }); } catch (auditErr) { // npm audit exits non-zero when vulnerabilities found — stdout still has JSON auditResult = (auditErr instanceof Error && 'stdout' in auditErr ? auditErr.stdout : undefined) || '{}'; } try { const audit = JSON.parse(auditResult); if (audit.vulnerabilities) { for (const [pkg, vuln] of Object.entries(audit.vulnerabilities)) { const sev = vuln.severity || 'low'; const title = Array.isArray(vuln.via) && vuln.via[0]?.title ? vuln.via[0].title : 'Vulnerability'; if (sev === 'critical') criticalCount++; else if (sev === 'high') highCount++; else if (sev === 'moderate' || sev === 'medium') mediumCount++; else lowCount++; findings.push({ severity: sev === 'critical' ? output.error('CRITICAL') : sev === 'high' ? output.warning('HIGH') : sev === 'moderate' || sev === 'medium' ? output.warning('MEDIUM') : output.info('LOW'), type: 'Dependency CVE', location: `package.json:${pkg}`, description: title.substring(0, 35), }); } } } catch { /* JSON parse failed, no vulns */ } } } catch { /* npm audit failed */ } } // Phase 2: Scan for hardcoded secrets if (scanType === 'all' || scanType === 'code') { spinner.setText('Scanning for hardcoded secrets...'); const secretPatterns = [ { pattern: /['"](?:sk-|sk_live_|sk_test_)[a-zA-Z0-9]{20,}['"]/g, type: 'API Key (Stripe/OpenAI)' }, { pattern: /['"]AKIA[A-Z0-9]{16}['"]/g, type: 'AWS Access Key' }, { pattern: /['"]ghp_[a-zA-Z0-9]{36}['"]/g, type: 'GitHub Token' }, { pattern: /['"]xox[baprs]-[a-zA-Z0-9-]+['"]/g, type: 'Slack Token' }, { pattern: /password\s*[:=]\s*['"][^'"]{8,}['"]/gi, type: 'Hardcoded Password' }, ]; const scanDir = (dir, depthLimit) => { if (depthLimit <= 0) return; try { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') continue; const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { scanDir(fullPath, depthLimit - 1); } else if (entry.isFile() && /\.(ts|js|json|env|yml|yaml)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) { try { const content = fs.readFileSync(fullPath, 'utf-8'); const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { for (const { pattern, type } of secretPatterns) { if (pattern.test(lines[i])) { highCount++; findings.push({ severity: output.warning('HIGH'), type: 'Hardcoded Secret', location: `${path.relative(target, fullPath)}:${i + 1}`, description: type, }); pattern.lastIndex = 0; } } } } catch { /* file read error */ } } } } catch { /* dir read error */ } }; const scanDepth = depth === 'deep' ? 10 : depth === 'standard' ? 5 : 3; scanDir(path.resolve(target), scanDepth); } // Phase 3: Check for common security issues in code if ((scanType === 'all' || scanType === 'code') && depth !== 'quick') { spinner.setText('Analyzing code patterns...'); const codePatterns = [ { pattern: /eval\s*\(/g, type: 'Eval Usage', severity: 'medium', desc: 'eval() can execute arbitrary code' }, { pattern: /innerHTML\s*=/g, type: 'innerHTML', severity: 'medium', desc: 'XSS risk with innerHTML' }, { pattern: /dangerouslySetInnerHTML/g, type: 'React XSS', severity: 'medium', desc: 'React XSS risk' }, { pattern: /child_process.*exec[^S]/g, type: 'Command Injection', severity: 'high', desc: 'Possible command injection' }, { pattern: /\$\{.*\}.*sql|sql.*\$\{/gi, type: 'SQL Injection', severity: 'high', desc: 'Possible SQL injection' }, ]; const scanCodeDir = (dir, depthLimit) => { if (depthLimit <= 0) return; try { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist') continue; const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { scanCodeDir(fullPath, depthLimit - 1); } else if (entry.isFile() && /\.(ts|js|tsx|jsx)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) { try { const content = fs.readFileSync(fullPath, 'utf-8'); const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { for (const { pattern, type, severity, desc } of codePatterns) { if (pattern.test(lines[i])) { if (severity === 'high') highCount++; else mediumCount++; findings.push({ severity: severity === 'high' ? output.warning('HIGH') : output.warning('MEDIUM'), type, location: `${path.relative(target, fullPath)}:${i + 1}`, description: desc, }); pattern.lastIndex = 0; } } } } catch { /* file read error */ } } } } catch { /* dir read error */ } }; const scanDepth = depth === 'deep' ? 10 : 5; scanCodeDir(path.resolve(target), scanDepth); } spinner.succeed('Scan complete'); // Display results output.writeln(); if (findings.length > 0) { output.printTable({ columns: [ { key: 'severity', header: 'Severity', width: 12 }, { key: 'type', header: 'Type', width: 18 }, { key: 'location', header: 'Location', width: 25 }, { key: 'description', header: 'Description', width: 35 }, ], data: findings.slice(0, 20), // Show first 20 }); if (findings.length > 20) { output.writeln(output.dim(`... and ${findings.length - 20} more issues`)); } } else { output.writeln(output.success('No security issues found!')); } output.writeln(); output.printBox([ `Target: ${target}`, `Depth: ${depth}`, `Type: ${scanType}`, ``, `Critical: ${criticalCount} High: ${highCount} Medium: ${mediumCount} Low: ${lowCount}`, `Total Issues: ${findings.length}`, ].join('\n'), 'Scan Summary'); // Auto-fix if requested if (fix && criticalCount + highCount > 0) { output.writeln(); const fixSpinner = output.createSpinner({ text: 'Attempting to fix vulnerabilities...', spinner: 'dots' }); fixSpinner.start(); try { try { execSync('npm audit fix', { cwd: path.resolve(target), encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }); } catch { /* npm audit fix may exit non-zero */ } fixSpinner.succeed('Applied available fixes (run scan again to verify)'); } catch { fixSpinner.fail('Some fixes could not be applied automatically'); } } return { success: findings.length === 0 || (criticalCount === 0 && highCount === 0) }; } catch (error) { spinner.fail('Scan failed'); output.printError(`Error: ${error}`); return { success: false }; } }, }; // CVE subcommand const cveCommand = { name: 'cve', description: 'Check and manage CVE vulnerabilities', options: [ { name: 'check', short: 'c', type: 'string', description: 'Check specific CVE ID' }, { name: 'list', short: 'l', type: 'boolean', description: 'List all known CVEs' }, { name: 'severity', short: 's', type: 'string', description: 'Filter by severity: critical, high, medium, low' }, ], examples: [ { command: 'claude-flow security cve --list', description: 'List all CVEs' }, { command: 'claude-flow security cve -c CVE-2024-1234', description: 'Check specific CVE' }, ], action: async (ctx) => { const checkCve = ctx.flags.check; output.writeln(); output.writeln(output.bold('CVE Database')); output.writeln(output.dim('─'.repeat(50))); output.writeln(output.warning('⚠ No CVE database configured.')); output.writeln(output.dim('This command requires a CVE data source (e.g., NVD API) which is not yet integrated.')); output.writeln(); if (checkCve) { output.writeln(`To look up ${output.bold(checkCve)}, use one of these real sources:`); } else { output.writeln('To check for real vulnerabilities, use:'); } output.writeln(); output.writeln(` ${output.dim('$')} npm audit ${output.dim('# dependency vulnerabilities')}`); output.writeln(` ${output.dim('$')} claude-flow security scan ${output.dim('# real code + dependency scan')}`); if (checkCve) { output.writeln(` ${output.dim('$')} open https://nvd.nist.gov/vuln/detail/${checkCve} ${output.dim('# NVD lookup')}`); } return { success: true }; }, }; // Threats subcommand const threatsCommand = { name: 'threats', description: 'Threat modeling and analysis', options: [ { name: 'model', short: 'm', type: 'string', description: 'Threat model: stride, dread, pasta', default: 'stride' }, { name: 'scope', short: 's', type: 'string', description: 'Analysis scope', default: '.' }, { name: 'export', short: 'e', type: 'string', description: 'Export format: json, md, html' }, ], examples: [ { command: 'claude-flow security threats --model stride', description: 'Run STRIDE analysis' }, { command: 'claude-flow security threats -e md', description: 'Export as markdown' }, ], action: async (ctx) => { const model = ctx.flags.model || 'stride'; const scope = ctx.flags.scope || '.'; const exportFormat = ctx.flags.export; output.writeln(); output.writeln(output.bold(`Threat Model: ${model.toUpperCase()}`)); output.writeln(output.dim('─'.repeat(50))); const spinner = output.createSpinner({ text: `Scanning ${scope} for threat indicators...`, spinner: 'dots' }); spinner.start(); const fs = await import('fs'); const path = await import('path'); const rootDir = path.resolve(scope); const findings = []; const extensions = new Set(['.ts', '.js', '.json', '.yaml', '.yml', '.tsx', '.jsx']); const skipDirs = new Set(['node_modules', 'dist', '.git']); let filesScanned = 0; const MAX_FILES = 500; // Threat indicator patterns mapped to STRIDE categories const threatPatterns = [ // Spoofing — weak/missing authentication { pattern: /(?:app|router|server)\s*\.\s*(?:get|post|put|patch|delete)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:async\s+)?\(?(?:req|request)/g, category: 'Spoofing', severity: 'medium', description: 'HTTP endpoint without auth middleware' }, // Tampering — code injection vectors { pattern: /\beval\s*\(/g, category: 'Tampering', severity: 'high', description: 'eval() usage — arbitrary code execution risk' }, { pattern: /\bexecSync\s*\(/g, category: 'Tampering', severity: 'high', description: 'execSync() usage — command injection risk' }, { pattern: /\bexec\s*\(\s*[^)]*\$\{/g, category: 'Tampering', severity: 'high', description: 'exec() with template literal — injection risk' }, { pattern: /child_process.*\bexec\b/g, category: 'Tampering', severity: 'medium', description: 'child_process exec import — review for injection' }, { pattern: /new\s+Function\s*\(/g, category: 'Tampering', severity: 'high', description: 'new Function() — dynamic code execution risk' }, // Repudiation — missing audit/logging // (checked via absence of logging imports, handled separately) // Info Disclosure — secrets and data leaks { pattern: /(?:api[_-]?key|secret|token|password|passwd|credential)\s*[:=]\s*['"][^'"]{8,}['"]/gi, category: 'Info Disclosure', severity: 'high', description: 'Hardcoded credential or secret' }, { pattern: /AKIA[0-9A-Z]{16}/g, category: 'Info Disclosure', severity: 'critical', description: 'AWS Access Key ID detected' }, { pattern: /gh[ps]_[A-Za-z0-9_]{36,}/g, category: 'Info Disclosure', severity: 'high', description: 'GitHub token detected' }, { pattern: /-----BEGIN (?:RSA|EC|DSA|OPENSSH) PRIVATE KEY-----/g, category: 'Info Disclosure', severity: 'critical', description: 'Private key detected' }, { pattern: /http:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)/g, category: 'Info Disclosure', severity: 'medium', description: 'Non-localhost HTTP URL — should use HTTPS' }, // DoS — missing rate limiting / resource protection { pattern: /require\s*\(\s*['"]express['"]\s*\)/g, category: 'DoS', severity: 'low', description: 'Express detected — verify rate-limiting is configured' }, { pattern: /require\s*\(\s*['"]fastify['"]\s*\)/g, category: 'DoS', severity: 'low', description: 'Fastify detected — verify rate-limiting is configured' }, // Elevation of privilege — unsafe deserialization, prototype pollution { pattern: /JSON\.parse\s*\(\s*(?:req\.|request\.)/g, category: 'Elevation', severity: 'medium', description: 'Unsanitized JSON.parse from request — validate input' }, { pattern: /\.__proto__/g, category: 'Elevation', severity: 'high', description: '__proto__ access — prototype pollution risk' }, { pattern: /Object\.assign\s*\(\s*\{\s*\}\s*,\s*(?:req|request)\./g, category: 'Elevation', severity: 'medium', description: 'Object.assign from request — prototype pollution risk' }, ]; // Check for .env files committed to git const checkEnvInGit = () => { try { const tracked = execSync('git ls-files --cached', { cwd: rootDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }); const envFiles = tracked.split('\n').filter((f) => /(?:^|\/)\.env(?:\.|$)/.test(f)); for (const envFile of envFiles) { findings.push({ category: 'Info Disclosure', severity: output.error('CRITICAL'), location: envFile, description: '.env file tracked in git — secrets may be exposed', }); } } catch { /* not a git repo or git not available */ } }; // Recursive file scanner const scanDir = (dir) => { if (filesScanned >= MAX_FILES) return; let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } for (const entry of entries) { if (filesScanned >= MAX_FILES) break; if (skipDirs.has(entry.name) || entry.name.startsWith('.')) continue; const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { scanDir(fullPath); } else if (entry.isFile() && extensions.has(path.extname(entry.name)) && !entry.name.endsWith('.d.ts')) { filesScanned++; try { const stat = fs.statSync(fullPath); if (stat.size > 1024 * 1024) continue; // skip files > 1MB const content = fs.readFileSync(fullPath, 'utf-8'); const lines = content.split('\n'); const relPath = path.relative(rootDir, fullPath); for (let i = 0; i < lines.length; i++) { for (const tp of threatPatterns) { tp.pattern.lastIndex = 0; if (tp.pattern.test(lines[i])) { const sevLabel = tp.severity === 'critical' ? output.error('CRITICAL') : tp.severity === 'high' ? output.warning('HIGH') : tp.severity === 'medium' ? output.warning('MEDIUM') : output.info('LOW'); findings.push({ category: tp.category, severity: sevLabel, location: `${relPath}:${i + 1}`, description: tp.description, }); tp.pattern.lastIndex = 0; } } } } catch { /* file read error */ } } } }; // Check for missing security middleware in Express/Fastify apps const checkMissingMiddleware = () => { const serverFiles = []; const collectServerFiles = (dir, depth) => { if (depth <= 0 || filesScanned >= MAX_FILES) return; try { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { if (skipDirs.has(entry.name) || entry.name.startsWith('.')) continue; const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { collectServerFiles(fullPath, depth - 1); } else if (/\.(ts|js)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) { try { const content = fs.readFileSync(fullPath, 'utf-8'); if (/require\s*\(\s*['"](?:express|fastify)['"]\s*\)/.test(content) || /from\s+['"](?:express|fastify)['"]/.test(content)) { serverFiles.push(fullPath); const relPath = path.relative(rootDir, fullPath); if (!/(?:helmet|lusca)/.test(content)) { findings.push({ category: 'Tampering', severity: output.warning('MEDIUM'), location: relPath, description: 'No helmet/lusca security headers middleware' }); } if (!/(?:cors)/.test(content)) { findings.push({ category: 'Spoofing', severity: output.info('LOW'), location: relPath, description: 'No CORS middleware detected' }); } if (!/(?:rate.?limit|throttle)/.test(content)) { findings.push({ category: 'DoS', severity: output.warning('MEDIUM'), location: relPath, description: 'No rate-limiting middleware detected' }); } } } catch { /* skip */ } } } } catch { /* skip */ } }; collectServerFiles(rootDir, 5); }; checkEnvInGit(); scanDir(rootDir); checkMissingMiddleware(); spinner.succeed(`Scanned ${filesScanned} files`); // STRIDE reference framework const strideRef = [ { category: 'Spoofing', description: 'Can an attacker impersonate a user or service?', example: 'Strong authentication, mTLS' }, { category: 'Tampering', description: 'Can data or code be modified without detection?', example: 'Input validation, integrity checks' }, { category: 'Repudiation', description: 'Can actions be performed without accountability?', example: 'Audit logging, signed commits' }, { category: 'Info Disclosure', description: 'Can sensitive data leak to unauthorized parties?', example: 'Encryption at rest and in transit' }, { category: 'DoS', description: 'Can service availability be degraded?', example: 'Rate limiting, resource quotas' }, { category: 'Elevation', description: 'Can privileges be escalated beyond granted level?', example: 'RBAC, principle of least privilege' }, ]; // Display real findings output.writeln(); if (findings.length > 0) { output.writeln(output.bold(`Findings (${findings.length}):`)); output.writeln(); output.printTable({ columns: [ { key: 'category', header: 'STRIDE Category', width: 18 }, { key: 'severity', header: 'Severity', width: 12 }, { key: 'location', header: 'Location', width: 30 }, { key: 'description', header: 'Description', width: 40 }, ], data: findings.slice(0, 30), }); if (findings.length > 30) { output.writeln(output.dim(`... and ${findings.length - 30} more findings`)); } // Summary by STRIDE category const byCat = {}; for (const f of findings) byCat[f.category] = (byCat[f.category] || 0) + 1; output.writeln(); output.writeln(output.bold('Summary by STRIDE category:')); for (const [cat, count] of Object.entries(byCat).sort((a, b) => b[1] - a[1])) { output.writeln(` ${cat}: ${count} finding${count === 1 ? '' : 's'}`); } } else { output.writeln(output.success('No threat indicators detected in scanned files.')); } // Always show STRIDE reference output.writeln(); output.writeln(output.bold(`${model.toUpperCase()} Reference Framework${findings.length === 0 ? ' (reference only — no issues detected)' : ''}:`)); output.writeln(); output.printTable({ columns: [ { key: 'category', header: `${model.toUpperCase()} Category`, width: 20 }, { key: 'description', header: 'What to Assess', width: 40 }, { key: 'example', header: 'Example Mitigation', width: 30 }, ], data: strideRef, }); // Export if requested if (exportFormat && findings.length > 0) { const exportData = { model: model.toUpperCase(), timestamp: new Date().toISOString(), scope, filesScanned, totalFindings: findings.length, findings: findings.map(f => ({ ...f, severity: f.severity.replace(/\x1b\[[0-9;]*m/g, '') })), strideReference: strideRef, }; if (exportFormat === 'json') { output.writeln(); output.writeln(JSON.stringify(exportData, null, 2)); } } output.writeln(); output.writeln(output.dim(`Files scanned: ${filesScanned} (max ${MAX_FILES})`)); return { success: true }; }, }; // Audit subcommand const auditCommand = { name: 'audit', description: 'Security audit logging and compliance', options: [ { name: 'action', short: 'a', type: 'string', description: 'Action: log, list, export, clear', default: 'list' }, { name: 'limit', short: 'l', type: 'number', description: 'Number of entries to show', default: '20' }, { name: 'filter', short: 'f', type: 'string', description: 'Filter by event type' }, ], examples: [ { command: 'claude-flow security audit --action list', description: 'List audit logs' }, { command: 'claude-flow security audit -a export', description: 'Export audit trail' }, ], action: async (ctx) => { const action = ctx.flags.action || 'list'; output.writeln(); output.writeln(output.bold('Security Audit Log')); output.writeln(output.dim('─'.repeat(60))); // Generate real audit entries from .swarm/ state and session history const { existsSync, readFileSync, readdirSync, statSync } = await import('fs'); const { join } = await import('path'); const auditEntries = []; const swarmDir = join(process.cwd(), '.swarm'); // Check session files for real audit events if (existsSync(swarmDir)) { try { const files = readdirSync(swarmDir).filter(f => f.endsWith('.json')); for (const file of files.slice(-10)) { try { const stat = statSync(join(swarmDir, file)); const ts = stat.mtime.toISOString().replace('T', ' ').substring(0, 19); auditEntries.push({ timestamp: ts, event: file.includes('session') ? 'SESSION_UPDATE' : file.includes('swarm') ? 'SWARM_ACTIVITY' : file.includes('memory') ? 'MEMORY_WRITE' : 'CONFIG_CHANGE', user: 'system', status: output.success('Success') }); } catch { /* skip */ } } } catch { /* ignore */ } } // Add current session entry const now = new Date().toISOString().replace('T', ' ').substring(0, 19); auditEntries.push({ timestamp: now, event: 'AUDIT_RUN', user: 'cli', status: output.success('Success') }); // Sort by timestamp desc auditEntries.sort((a, b) => b.timestamp.localeCompare(a.timestamp)); if (auditEntries.length === 0) { output.writeln(output.dim('No audit events found. Initialize a project first: claude-flow init')); } else { output.printTable({ columns: [ { key: 'timestamp', header: 'Timestamp', width: 22 }, { key: 'event', header: 'Event', width: 20 }, { key: 'user', header: 'User', width: 15 }, { key: 'status', header: 'Status', width: 12 }, ], data: auditEntries.slice(0, parseInt(ctx.flags.limit || '20', 10)), }); } return { success: true }; }, }; // Secrets subcommand const secretsCommand = { name: 'secrets', description: 'Detect and manage secrets in codebase', options: [ { name: 'action', short: 'a', type: 'string', description: 'Action: scan, list, rotate', default: 'scan' }, { name: 'path', short: 'p', type: 'string', description: 'Path to scan', default: '.' }, { name: 'ignore', short: 'i', type: 'string', description: 'Patterns to ignore' }, ], examples: [ { command: 'claude-flow security secrets --action scan', description: 'Scan for secrets' }, { command: 'claude-flow security secrets -a rotate', description: 'Rotate compromised secrets' }, ], action: async (ctx) => { const scanPath = ctx.flags.path || '.'; const ignorePatterns = ctx.flags.ignore; output.writeln(); output.writeln(output.bold('Secret Detection')); output.writeln(output.dim('─'.repeat(50))); const spinner = output.createSpinner({ text: `Scanning ${scanPath} for secrets...`, spinner: 'dots' }); spinner.start(); const fs = await import('fs'); const path = await import('path'); const rootDir = path.resolve(scanPath); const skipDirs = new Set(['node_modules', 'dist', '.git']); const extensions = new Set(['.ts', '.js', '.json', '.yaml', '.yml', '.tsx', '.jsx', '.env', '.toml', '.cfg', '.conf', '.ini', '.properties', '.sh', '.bash', '.zsh']); const ignoreList = ignorePatterns ? ignorePatterns.split(',').map(p => p.trim()) : []; const secretPatterns = [ { pattern: /AKIA[0-9A-Z]{16}/g, type: 'AWS Access Key', risk: 'Critical', action: 'Rotate immediately' }, { pattern: /gh[ps]_[A-Za-z0-9_]{36,}/g, type: 'GitHub Token', risk: 'Critical', action: 'Revoke and rotate' }, { pattern: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, type: 'JWT Token', risk: 'High', action: 'Remove from source' }, { pattern: /-----BEGIN (?:RSA|EC|DSA|OPENSSH) PRIVATE KEY-----/g, type: 'Private Key', risk: 'Critical', action: 'Remove and regenerate' }, { pattern: /(?:mongodb|postgres|mysql|redis):\/\/[^\s'"]+/g, type: 'Connection String', risk: 'High', action: 'Use env variable' }, { pattern: /['"](?:sk-|sk_live_|sk_test_)[a-zA-Z0-9]{20,}['"]/g, type: 'API Key (Stripe/OpenAI)', risk: 'Critical', action: 'Rotate immediately' }, { pattern: /['"]xox[baprs]-[a-zA-Z0-9-]+['"]/g, type: 'Slack Token', risk: 'High', action: 'Revoke and rotate' }, { pattern: /[a-zA-Z0-9_-]*(?:api[_-]?key|secret[_-]?key|auth[_-]?token|access[_-]?token|private[_-]?key)\s*[:=]\s*['"][^'"]{8,}['"]/gi, type: 'Generic Secret/API Key', risk: 'High', action: 'Use env variable' }, { pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]/gi, type: 'Hardcoded Password', risk: 'High', action: 'Use secrets manager' }, ]; const findings = []; let filesScanned = 0; const MAX_FILES = 500; const shouldIgnore = (filePath) => { return ignoreList.some(p => filePath.includes(p)); }; const scanDir = (dir) => { if (filesScanned >= MAX_FILES) return; let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } for (const entry of entries) { if (filesScanned >= MAX_FILES) break; if (skipDirs.has(entry.name)) continue; // Allow dotfiles like .env but skip .git const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { if (entry.name.startsWith('.') && entry.name !== '.env') continue; scanDir(fullPath); } else if (entry.isFile()) { const ext = path.extname(entry.name); const isEnvFile = entry.name.startsWith('.env'); if (!extensions.has(ext) && !isEnvFile) continue; if (entry.name.endsWith('.d.ts')) continue; const relPath = path.relative(rootDir, fullPath); if (shouldIgnore(relPath)) continue; filesScanned++; try { const stat = fs.statSync(fullPath); if (stat.size > 1024 * 1024) continue; // skip files > 1MB const content = fs.readFileSync(fullPath, 'utf-8'); // Quick binary check — skip if null bytes present if (content.includes('\0')) continue; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; for (const sp of secretPatterns) { sp.pattern.lastIndex = 0; const match = sp.pattern.exec(line); if (match) { // Mask the matched secret for safe display const matched = match[0]; const masked = matched.length > 12 ? matched.substring(0, 6) + '***' + matched.substring(matched.length - 3) : '***'; findings.push({ type: sp.type, location: `${relPath}:${i + 1}`, risk: sp.risk, action: sp.action, line: masked, }); sp.pattern.lastIndex = 0; } } } } catch { /* file read error */ } } } }; scanDir(rootDir); spinner.succeed(`Scanned ${filesScanned} files`); output.writeln(); if (findings.length > 0) { const criticalCount = findings.filter(f => f.risk === 'Critical').length; const highCount = findings.filter(f => f.risk === 'High').length; const mediumCount = findings.filter(f => f.risk === 'Medium').length; output.printTable({ columns: [ { key: 'type', header: 'Secret Type', width: 25 }, { key: 'location', header: 'Location', width: 35 }, { key: 'risk', header: 'Risk', width: 12 }, { key: 'action', header: 'Recommended', width: 22 }, ], data: findings.slice(0, 25).map(f => ({ type: f.type, location: f.location, risk: f.risk === 'Critical' ? output.error(f.risk) : f.risk === 'High' ? output.warning(f.risk) : output.warning(f.risk), action: f.action, })), }); if (findings.length > 25) { output.writeln(output.dim(`... and ${findings.length - 25} more secrets found`)); } output.writeln(); output.printBox([ `Path: ${scanPath}`, `Files scanned: ${filesScanned}`, ``, `Critical: ${criticalCount} High: ${highCount} Medium: ${mediumCount}`, `Total secrets found: ${findings.length}`, ].join('\n'), 'Secrets Summary'); } else { output.writeln(output.success('No secrets detected.')); output.writeln(); output.printBox([ `Path: ${scanPath}`, `Files scanned: ${filesScanned}`, ``, `No hardcoded secrets, API keys, tokens, or credentials found.`, ].join('\n'), 'Secrets Summary'); } return { success: findings.length === 0 }; }, }; // Defend subcommand (AIDefence integration) const defendCommand = { name: 'defend', description: 'AI manipulation defense - detect prompt injection, jailbreaks, and PII', options: [ { name: 'input', short: 'i', type: 'string', description: 'Input text to scan for threats' }, { name: 'file', short: 'f', type: 'string', description: 'File to scan for threats' }, { name: 'quick', short: 'Q', type: 'boolean', description: 'Quick scan (faster, less detailed)' }, { name: 'learn', short: 'l', type: 'boolean', description: 'Enable learning mode', default: 'true' }, { name: 'stats', short: 's', type: 'boolean', description: 'Show detection statistics' }, { name: 'output', short: 'o', type: 'string', description: 'Output format: text, json', default: 'text' }, ], examples: [ { command: 'claude-flow security defend -i "ignore previous instructions"', description: 'Scan text for threats' }, { command: 'claude-flow security defend -f ./prompts.txt', description: 'Scan file for threats' }, { command: 'claude-flow security defend --stats', description: 'Show detection statistics' }, ], action: async (ctx) => { const inputText = ctx.flags.input; const filePath = ctx.flags.file; const quickMode = ctx.flags.quick; const showStats = ctx.flags.stats; const outputFormat = ctx.flags.output || 'text'; const enableLearning = ctx.flags.learn !== false; output.writeln(); output.writeln(output.bold('🛡️ AIDefence - AI Manipulation Defense System')); output.writeln(output.dim('─'.repeat(55))); // Dynamic import of aidefence (allows package to be optional) let createAIDefence; try { const aidefence = await import('@claude-flow/aidefence'); createAIDefence = aidefence.createAIDefence; } catch { output.error('AIDefence package not installed. Run: npm install @claude-flow/aidefence'); return { success: false, message: 'AIDefence not available' }; } const defender = createAIDefence({ enableLearning }); // Show stats mode if (showStats) { const stats = await defender.getStats(); output.writeln(); output.printBox([ `Detection Count: ${stats.detectionCount}`, `Avg Detection Time: ${stats.avgDetectionTimeMs.toFixed(3)}ms`, `Learned Patterns: ${stats.learnedPatterns}`, `Mitigation Strategies: ${stats.mitigationStrategies}`, `Avg Mitigation Effectiveness: ${(stats.avgMitigationEffectiveness * 100).toFixed(1)}%`, ].join('\n'), 'Detection Statistics'); return { success: true }; } // Get input to scan let textToScan = inputText; if (filePath) { try { const fs = await import('fs/promises'); textToScan = await fs.readFile(filePath, 'utf-8'); output.writeln(output.dim(`Reading file: ${filePath}`)); } catch (err) { output.error(`Failed to read file: ${filePath}`); return { success: false, message: 'File not found' }; } } if (!textToScan) { output.writeln('Usage: claude-flow security defend -i "<text>" or -f <file>'); output.writeln(); output.writeln('Options:'); output.printList([ '-i, --input Text to scan for AI manipulation attempts', '-f, --file File path to scan', '-q, --quick Quick scan mode (faster)', '-s, --stats Show detection statistics', '--learn Enable pattern learning (default: true)', ]); return { success: true }; } const spinner = output.createSpinner({ text: 'Scanning for threats...', spinner: 'dots' }); spinner.start(); // Perform scan const startTime = performance.now(); const result = quickMode ? { ...defender.quickScan(textToScan), threats: [], piiFound: false, detectionTimeMs: 0, inputHash: '', safe: !defender.quickScan(textToScan).threat } : await defender.detect(textToScan); const scanTime = performance.now() - startTime; spinner.stop(); // JSON output if (outputFormat === 'json') { output.writeln(JSON.stringify({ safe: result.safe, threats: result.threats || [], piiFound: result.piiFound, detectionTimeMs: scanTime, }, null, 2)); return { success: true }; } // Text output output.writeln(); if (result.safe && !result.piiFound) { output.writeln(output.success('✅ No threats detected')); } else { if (!result.safe && result.threats) { output.writeln(output.error(`⚠️ ${result.threats.length} threat(s) detected:`)); output.writeln(); for (const threat of result.threats) { const severityColor = { critical: output.error, high: output.warning, medium: output.info, low: output.dim, }[threat.severity] || output.dim; output.writeln(` ${severityColor(`[${threat.severity.toUpperCase()}]`)} ${threat.type}`); output.writeln(` ${output.dim(threat.description)}`); output.writeln(` Confidence: ${(threat.confidence * 100).toFixed(1)}%`); output.writeln(); } // Show mitigation recommendations const criticalThreats = result.threats.filter(t => t.severity === 'critical'); if (criticalThreats.length > 0 && enableLearning) { output.writeln(output.bold('Recommended Mitigations:')); for (const threat of criticalThreats) { const mitigation = await defender.getBestMitigation(threat.type); if (mitigation) { output.writeln(` ${threat.type}: ${output.bold(mitigation.strategy)} (${(mitigation.effectiveness * 100).toFixed(0)}% effective)`); } } output.writeln(); } } if (result.piiFound) { output.writeln(output.warning('⚠️ PII detected (emails, SSNs, API keys, etc.)')); output.writeln(); } } output.writeln(output.dim(`Detection time: ${scanTime.toFixed(3)}ms`)); return { success: result.safe }; }, }; // Main security command export const securityCommand = { name: 'security', description: 'Security scanning, CVE detection, threat modeling, AI defense', subcommands: [scanCommand, cveCommand, threatsCommand, auditCommand, secretsCommand, defendCommand], examples: [ { command: 'claude-flow security scan', description: 'Run security scan' }, { command: 'claude-flow security cve --list', description: 'List known CVEs' }, { command: 'claude-flow security threats', description: 'Run threat analysis' }, ], action: async () => { output.writeln(); output.writeln(output.bold('RuFlo Security Suite')); output.writeln(output.dim('Comprehensive security scanning and vulnerability management')); output.writeln(); output.writeln('Subcommands:'); output.printList([ 'scan - Run security scans on code, deps, containers', 'cve - Check and manage CVE vulnerabilities', 'threats - Threat modeling (STRIDE, DREAD, PASTA)', 'audit - Security audit logging and compliance', 'secrets - Detect