UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

1,208 lines 56.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SecurityScanner = void 0; exports.createSecurityRule = createSecurityRule; exports.scanSecurity = scanSecurity; const events_1 = require("events"); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const child_process_1 = require("child_process"); const fast_glob_1 = __importDefault(require("fast-glob")); class SecurityScanner extends events_1.EventEmitter { constructor(config) { super(); this.config = { scanners: [ { name: 'npm-audit', enabled: true }, { name: 'eslint-security', enabled: true } ], generateReport: true, outputPath: './security-reports', includePatterns: ['**/*.{js,ts,jsx,tsx,json,yml,yaml,dockerfile}'], excludePatterns: ['**/node_modules/**', '**/dist/**', '**/coverage/**'], thresholds: { critical: 0, high: 0, medium: 5, low: 10, total: 20 }, ...config }; } async scan(projectPath) { this.emit('scan:start', { project: projectPath }); const result = { projectPath, timestamp: new Date(), summary: this.createEmptySummary(), vulnerabilities: [], dependencies: this.createEmptyDependencyAnalysis(), codeAnalysis: this.createEmptyCodeAnalysis(), configurationAnalysis: this.createEmptyConfigAnalysis(), scanners: [], metrics: this.createEmptyMetrics() }; try { // Run dependency analysis result.dependencies = await this.analyzeDependencies(projectPath); // Run code security analysis result.codeAnalysis = await this.analyzeCode(projectPath); // Run configuration analysis result.configurationAnalysis = await this.analyzeConfiguration(projectPath); // Run external scanners for (const scanner of this.config.scanners.filter(s => s.enabled)) { const scannerResult = await this.runScanner(scanner, projectPath); result.scanners.push(scannerResult); } // Aggregate vulnerabilities result.vulnerabilities = await this.aggregateVulnerabilities(result); // Calculate metrics result.metrics = this.calculateSecurityMetrics(result); result.summary = this.generateSummary(result); // Calculate trends if historical data exists result.trends = await this.calculateTrends(result); this.emit('scan:complete', result); return result; } catch (error) { this.emit('scan:error', error); throw error; } } async analyzeDependencies(projectPath) { this.emit('analyze:dependencies:start'); const analysis = { totalDependencies: 0, vulnerableDependencies: 0, outdatedDependencies: 0, licenseIssues: [], dependencyTree: [], recommendations: [] }; try { // Read package.json const packageJsonPath = path.join(projectPath, 'package.json'); if (await fs.pathExists(packageJsonPath)) { const packageJson = await fs.readJson(packageJsonPath); const deps = { ...packageJson.dependencies || {}, ...packageJson.devDependencies || {} }; analysis.totalDependencies = Object.keys(deps).length; // Run npm audit try { const auditOutput = (0, child_process_1.execSync)('npm audit --json', { cwd: projectPath, encoding: 'utf-8', stdio: 'pipe' }); const auditData = JSON.parse(auditOutput); analysis.vulnerableDependencies = Object.keys(auditData.vulnerabilities || {}).length; // Process vulnerabilities for (const [name, vuln] of Object.entries(auditData.vulnerabilities || {})) { const vulnData = vuln; if (vulnData.severity === 'high' || vulnData.severity === 'critical') { analysis.recommendations.push({ dependency: name, currentVersion: vulnData.range || 'unknown', recommendedVersion: vulnData.fixAvailable?.name || 'latest', reason: `Security vulnerability: ${vulnData.title}`, urgency: vulnData.severity === 'critical' ? 'critical' : 'high' }); } } } catch (auditError) { this.emit('audit:error', auditError); } // Check for outdated dependencies try { const outdatedOutput = (0, child_process_1.execSync)('npm outdated --json', { cwd: projectPath, encoding: 'utf-8', stdio: 'pipe' }); const outdatedData = JSON.parse(outdatedOutput); analysis.outdatedDependencies = Object.keys(outdatedData).length; } catch (outdatedError) { // npm outdated returns non-zero exit code when outdated packages found if (outdatedError.stdout) { try { const outdatedData = JSON.parse(outdatedError.stdout); analysis.outdatedDependencies = Object.keys(outdatedData).length; } catch { // Ignore parsing errors } } } // Analyze licenses analysis.licenseIssues = await this.analyzeLicenses(deps); } } catch (error) { this.emit('dependencies:error', error); } return analysis; } async analyzeLicenses(dependencies) { const issues = []; // Define problematic licenses const problematicLicenses = [ 'GPL-2.0', 'GPL-3.0', 'AGPL-1.0', 'AGPL-3.0', 'CPAL-1.0', 'EPL-1.0', 'EPL-2.0' ]; // This would typically integrate with a license checking service // For now, we'll use a simplified approach for (const [name, version] of Object.entries(dependencies)) { // Mock license checking const mockLicense = Math.random() > 0.95 ? 'GPL-3.0' : 'MIT'; if (problematicLicenses.includes(mockLicense)) { issues.push({ dependency: name, version, license: mockLicense, risk: 'high', reason: 'Copyleft license may require code disclosure' }); } } return issues; } async analyzeCode(projectPath) { this.emit('analyze:code:start'); const analysis = { totalFiles: 0, vulnerableFiles: 0, patterns: [], hotspots: [], recommendations: [] }; try { // Find source files const files = await (0, fast_glob_1.default)(this.config.includePatterns, { cwd: projectPath, absolute: true, ignore: this.config.excludePatterns }); analysis.totalFiles = files.length; // Analyze each file for (const file of files) { const fileAnalysis = await this.analyzeSourceFile(file); if (fileAnalysis.vulnerabilities.length > 0) { analysis.vulnerableFiles++; for (const vuln of fileAnalysis.vulnerabilities) { analysis.hotspots.push({ file: path.relative(projectPath, file), line: vuln.line, category: vuln.category, severity: vuln.severity, description: vuln.message, remediation: vuln.remediation || 'Review and fix security issue' }); } } } // Identify common patterns analysis.patterns = this.identifySecurityPatterns(analysis.hotspots); analysis.recommendations = this.generateCodeRecommendations(analysis); } catch (error) { this.emit('code:error', error); } return analysis; } async analyzeSourceFile(filePath) { const content = await fs.readFile(filePath, 'utf-8'); const vulnerabilities = []; // Apply built-in security rules const builtInRules = this.getBuiltInSecurityRules(); for (const rule of builtInRules) { const fileContent = { path: filePath, content, type: this.getFileType(filePath), language: this.getLanguage(filePath) }; const violations = rule.check(fileContent); vulnerabilities.push(...violations); } // Apply custom rules if (this.config.customRules) { for (const rule of this.config.customRules) { const fileContent = { path: filePath, content, type: this.getFileType(filePath), language: this.getLanguage(filePath) }; const violations = rule.check(fileContent); vulnerabilities.push(...violations); } } return { vulnerabilities }; } getBuiltInSecurityRules() { return [ { id: 'hardcoded-password', name: 'Hardcoded Password', description: 'Detects hardcoded passwords in source code', severity: 'high', category: 'authentication', cwe: 'CWE-798', pattern: /password\s*[=:]\s*["'][^"']+["']/i, check: (file) => { const violations = []; const lines = file.content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (/password\s*[=:]\s*["'][^"']+["']/i.test(line)) { violations.push({ line: i + 1, message: 'Hardcoded password detected', severity: 'high', category: 'authentication', cwe: 'CWE-798', evidence: line.trim(), remediation: 'Use environment variables or secure configuration for passwords' }); } } return violations; }, remediation: 'Use environment variables or secure configuration management' }, { id: 'sql-injection', name: 'SQL Injection', description: 'Detects potential SQL injection vulnerabilities', severity: 'critical', category: 'injection', cwe: 'CWE-89', check: (file) => { const violations = []; const lines = file.content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Look for string concatenation in SQL queries if (/(?:SELECT|INSERT|UPDATE|DELETE).*\+.*\$\{/i.test(line)) { violations.push({ line: i + 1, message: 'Potential SQL injection vulnerability', severity: 'critical', category: 'injection', cwe: 'CWE-89', evidence: line.trim(), remediation: 'Use parameterized queries or prepared statements' }); } } return violations; }, remediation: 'Use parameterized queries, prepared statements, or ORM frameworks' }, { id: 'xss-vulnerability', name: 'Cross-Site Scripting (XSS)', description: 'Detects potential XSS vulnerabilities', severity: 'high', category: 'xss', cwe: 'CWE-79', check: (file) => { const violations = []; const lines = file.content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Look for direct innerHTML usage with user input if (/innerHTML\s*=.*\$\{/i.test(line)) { violations.push({ line: i + 1, message: 'Potential XSS vulnerability in innerHTML usage', severity: 'high', category: 'xss', cwe: 'CWE-79', evidence: line.trim(), remediation: 'Use textContent or proper escaping for user input' }); } } return violations; }, remediation: 'Sanitize user input and use safe DOM manipulation methods' }, { id: 'insecure-random', name: 'Insecure Random Number Generation', description: 'Detects use of insecure random number generators', severity: 'medium', category: 'cryptography', cwe: 'CWE-338', check: (file) => { const violations = []; const lines = file.content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; if (/Math\.random\(\)/.test(line) && /password|token|salt|key/i.test(line)) { violations.push({ line: i + 1, message: 'Insecure random number generation for security-sensitive value', severity: 'medium', category: 'cryptography', cwe: 'CWE-338', evidence: line.trim(), remediation: 'Use crypto.randomBytes() for cryptographically secure random values' }); } } return violations; }, remediation: 'Use cryptographically secure random number generators' } ]; } getFileType(filePath) { const filename = path.basename(filePath).toLowerCase(); if (filename === 'package.json' || filename === 'dockerfile' || filename.endsWith('.yml') || filename.endsWith('.yaml')) { return 'config'; } if (filename.includes('build') || filename.includes('webpack') || filename.includes('rollup')) { return 'build'; } if (filename === 'package-lock.json' || filename === 'yarn.lock') { return 'dependency'; } return 'source'; } getLanguage(filePath) { const ext = path.extname(filePath).toLowerCase(); const languageMap = { '.js': 'javascript', '.jsx': 'javascript', '.ts': 'typescript', '.tsx': 'typescript', '.py': 'python', '.java': 'java', '.go': 'go', '.rs': 'rust', '.php': 'php', '.rb': 'ruby', '.cs': 'csharp', '.cpp': 'cpp', '.c': 'c' }; return languageMap[ext] || 'unknown'; } identifySecurityPatterns(hotspots) { const patterns = new Map(); for (const hotspot of hotspots) { const key = `${hotspot.category}-${hotspot.severity}`; if (patterns.has(key)) { const pattern = patterns.get(key); pattern.occurrences++; pattern.files.push(hotspot.file); } else { patterns.set(key, { pattern: `${hotspot.category} vulnerabilities`, category: hotspot.category, occurrences: 1, files: [hotspot.file], severity: hotspot.severity }); } } return Array.from(patterns.values()).sort((a, b) => b.occurrences - a.occurrences); } generateCodeRecommendations(analysis) { const recommendations = []; if (analysis.vulnerableFiles > 0) { recommendations.push(`Review ${analysis.vulnerableFiles} files with security vulnerabilities`); } const criticalHotspots = analysis.hotspots.filter(h => h.severity === 'critical'); if (criticalHotspots.length > 0) { recommendations.push(`Address ${criticalHotspots.length} critical security issues immediately`); } const injectionIssues = analysis.hotspots.filter(h => h.category === 'injection'); if (injectionIssues.length > 0) { recommendations.push('Implement input validation and parameterized queries to prevent injection attacks'); } const xssIssues = analysis.hotspots.filter(h => h.category === 'xss'); if (xssIssues.length > 0) { recommendations.push('Implement proper output encoding to prevent XSS vulnerabilities'); } return recommendations; } async analyzeConfiguration(projectPath) { this.emit('analyze:config:start'); const analysis = { files: [], issues: [], recommendations: [] }; try { // Find configuration files const configPatterns = [ 'package.json', 'Dockerfile', '*.yml', '*.yaml', '.env*', 'nginx.conf', 'webpack.config.*', 'tsconfig.json' ]; const configFiles = await (0, fast_glob_1.default)(configPatterns, { cwd: projectPath, absolute: true, ignore: this.config.excludePatterns }); for (const file of configFiles) { const configFile = await this.analyzeConfigFile(file, projectPath); analysis.files.push(configFile); analysis.issues.push(...configFile.issues); } analysis.recommendations = this.generateConfigRecommendations(analysis); } catch (error) { this.emit('config:error', error); } return analysis; } async analyzeConfigFile(filePath, projectPath) { const content = await fs.readFile(filePath, 'utf-8'); const relativePath = path.relative(projectPath, filePath); const configFile = { path: relativePath, type: this.getConfigFileType(filePath), issues: [], securityScore: 100 }; // Analyze based on file type switch (configFile.type) { case 'package.json': configFile.issues = this.analyzePackageJson(content, relativePath); break; case 'dockerfile': configFile.issues = this.analyzeDockerfile(content, relativePath); break; case 'env': configFile.issues = this.analyzeEnvFile(content, relativePath); break; default: configFile.issues = this.analyzeGenericConfig(content, relativePath); } // Calculate security score const criticalIssues = configFile.issues.filter(i => i.severity === 'critical').length; const highIssues = configFile.issues.filter(i => i.severity === 'high').length; const mediumIssues = configFile.issues.filter(i => i.severity === 'medium').length; configFile.securityScore = Math.max(0, 100 - (criticalIssues * 30 + highIssues * 20 + mediumIssues * 10)); return configFile; } getConfigFileType(filePath) { const filename = path.basename(filePath).toLowerCase(); if (filename === 'package.json') return 'package.json'; if (filename === 'dockerfile' || filename.startsWith('dockerfile.')) return 'dockerfile'; if (filename.startsWith('.env')) return 'env'; if (filename.includes('nginx')) return 'nginx'; if (filename.includes('ci') || filename.includes('workflow')) return 'ci'; return 'other'; } analyzePackageJson(content, filePath) { const issues = []; try { const packageJson = JSON.parse(content); // Check for dependencies with known vulnerabilities if (packageJson.dependencies) { for (const [dep, version] of Object.entries(packageJson.dependencies)) { if (typeof version === 'string' && version.includes('*')) { issues.push({ file: filePath, setting: `dependencies.${dep}`, issue: 'Wildcard version dependency', severity: 'medium', remediation: 'Use specific version ranges to avoid unexpected updates' }); } } } // Check scripts for potentially dangerous commands if (packageJson.scripts) { for (const [script, command] of Object.entries(packageJson.scripts)) { if (typeof command === 'string' && /rm\s+-rf|sudo|curl.*\|/.test(command)) { issues.push({ file: filePath, setting: `scripts.${script}`, issue: 'Potentially dangerous script command', severity: 'high', remediation: 'Review script for security implications' }); } } } } catch (error) { issues.push({ file: filePath, setting: 'JSON syntax', issue: 'Invalid JSON format', severity: 'medium', remediation: 'Fix JSON syntax errors' }); } return issues; } analyzeDockerfile(content, filePath) { const issues = []; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const lineNum = i + 1; // Check for running as root if (/^USER\s+root$/i.test(line)) { issues.push({ file: filePath, line: lineNum, setting: 'USER', issue: 'Running container as root user', severity: 'high', remediation: 'Create and use a non-root user' }); } // Check for --privileged flag if (/--privileged/.test(line)) { issues.push({ file: filePath, line: lineNum, setting: 'privileged', issue: 'Using privileged mode', severity: 'critical', remediation: 'Avoid privileged mode unless absolutely necessary' }); } // Check for ADD with remote URLs if (/^ADD\s+https?:\/\//.test(line)) { issues.push({ file: filePath, line: lineNum, setting: 'ADD', issue: 'Using ADD with remote URL', severity: 'medium', remediation: 'Use COPY for local files or RUN with curl for remote content' }); } } return issues; } analyzeEnvFile(content, filePath) { const issues = []; const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); const lineNum = i + 1; // Check for hardcoded secrets if (/(?:password|secret|key|token)\s*=.*[^=\s]/i.test(line)) { issues.push({ file: filePath, line: lineNum, setting: 'environment variable', issue: 'Potential hardcoded secret in environment file', severity: 'high', remediation: 'Use placeholder values and set real secrets at runtime' }); } } return issues; } analyzeGenericConfig(content, filePath) { const issues = []; // Basic checks for any configuration file if (/password\s*[:=]\s*[^#\s]/i.test(content)) { issues.push({ file: filePath, setting: 'configuration', issue: 'Potential hardcoded password', severity: 'high', remediation: 'Use external configuration or environment variables' }); } return issues; } generateConfigRecommendations(analysis) { const recommendations = []; const criticalIssues = analysis.issues.filter(i => i.severity === 'critical'); if (criticalIssues.length > 0) { recommendations.push(`Fix ${criticalIssues.length} critical configuration security issues`); } const dockerIssues = analysis.issues.filter(i => i.file.toLowerCase().includes('dockerfile')); if (dockerIssues.length > 0) { recommendations.push('Review Docker configuration for security best practices'); } const envIssues = analysis.issues.filter(i => i.file.includes('.env')); if (envIssues.length > 0) { recommendations.push('Review environment files for hardcoded secrets'); } return recommendations; } async runScanner(scanner, projectPath) { this.emit('scanner:start', { name: scanner.name }); const result = { scanner: scanner.name, success: false, duration: 0, vulnerabilities: 0, errors: [] }; const startTime = Date.now(); try { switch (scanner.name) { case 'npm-audit': await this.runNpmAudit(projectPath, result); break; case 'snyk': await this.runSnyk(projectPath, result); break; case 'semgrep': await this.runSemgrep(projectPath, result); break; case 'eslint-security': await this.runESLintSecurity(projectPath, result); break; default: result.errors = [`Unknown scanner: ${scanner.name}`]; } result.success = result.errors?.length === 0; result.duration = Date.now() - startTime; } catch (error) { result.errors = [error.message]; result.duration = Date.now() - startTime; } this.emit('scanner:complete', result); return result; } async runNpmAudit(projectPath, result) { try { const output = (0, child_process_1.execSync)('npm audit --json', { cwd: projectPath, encoding: 'utf-8', stdio: 'pipe' }); const auditData = JSON.parse(output); result.vulnerabilities = Object.keys(auditData.vulnerabilities || {}).length; result.rawOutput = output; } catch (error) { if (error.stdout) { try { const auditData = JSON.parse(error.stdout); result.vulnerabilities = Object.keys(auditData.vulnerabilities || {}).length; result.rawOutput = error.stdout; } catch { result.errors = ['Failed to parse npm audit output']; } } else { result.errors = ['npm audit command failed']; } } } async runSnyk(projectPath, result) { try { const output = (0, child_process_1.execSync)('snyk test --json', { cwd: projectPath, encoding: 'utf-8', stdio: 'pipe' }); const snykData = JSON.parse(output); result.vulnerabilities = snykData.vulnerabilities?.length || 0; result.rawOutput = output; } catch (error) { result.errors = ['Snyk not available or authentication required']; } } async runSemgrep(projectPath, result) { try { const output = (0, child_process_1.execSync)('semgrep --config=auto --json', { cwd: projectPath, encoding: 'utf-8', stdio: 'pipe' }); const semgrepData = JSON.parse(output); result.vulnerabilities = semgrepData.results?.length || 0; result.rawOutput = output; } catch (error) { result.errors = ['Semgrep not available']; } } async runESLintSecurity(projectPath, result) { try { const output = (0, child_process_1.execSync)('npx eslint . --ext .js,.ts,.jsx,.tsx --format json', { cwd: projectPath, encoding: 'utf-8', stdio: 'pipe' }); const eslintData = JSON.parse(output); let securityIssues = 0; for (const file of eslintData) { for (const message of file.messages) { if (message.ruleId && message.ruleId.includes('security')) { securityIssues++; } } } result.vulnerabilities = securityIssues; result.rawOutput = output; } catch (error) { if (error.stdout) { result.rawOutput = error.stdout; } // ESLint returns non-zero exit code when issues found } } async aggregateVulnerabilities(scanResult) { const vulnerabilities = []; // Convert code analysis hotspots to vulnerabilities for (const hotspot of scanResult.codeAnalysis.hotspots) { vulnerabilities.push({ id: `code-${vulnerabilities.length}`, title: hotspot.description, description: hotspot.description, severity: hotspot.severity, category: hotspot.category, affected: [{ type: 'code', name: hotspot.file, path: hotspot.file }], discovered: new Date(), source: 'static-analysis', references: [], remediation: { priority: hotspot.severity === 'critical' ? 'immediate' : hotspot.severity === 'high' ? 'high' : 'medium', effort: '1-2h', steps: [hotspot.remediation], automatedFix: false } }); } // Convert configuration issues to vulnerabilities for (const issue of scanResult.configurationAnalysis.issues) { if (issue.severity === 'critical' || issue.severity === 'high') { vulnerabilities.push({ id: `config-${vulnerabilities.length}`, title: issue.issue, description: issue.issue, severity: issue.severity, category: 'configuration', affected: [{ type: 'configuration', name: issue.file, path: issue.file }], discovered: new Date(), source: 'config-analysis', references: [], remediation: { priority: issue.severity === 'critical' ? 'immediate' : 'high', effort: '30min', steps: [issue.remediation], automatedFix: false } }); } } return vulnerabilities; } calculateSecurityMetrics(scanResult) { const vulnerabilities = scanResult.vulnerabilities; const riskDistribution = { critical: 0, high: 0, medium: 0, low: 0, info: 0 }; const categoryDistribution = { injection: 0, authentication: 0, authorization: 0, cryptography: 0, configuration: 0, 'sensitive-data': 0, dependency: 0, xss: 0, csrf: 0, xxe: 0, deserialization: 0, other: 0 }; for (const vuln of vulnerabilities) { riskDistribution[vuln.severity]++; categoryDistribution[vuln.category]++; } return { riskDistribution, categoryDistribution, coverageMetrics: { codeScanned: scanResult.codeAnalysis.totalFiles, dependenciesScanned: scanResult.dependencies.totalDependencies, configurationScanned: scanResult.configurationAnalysis.files.length, totalCoverage: 85 // Mock coverage percentage }, complianceMetrics: { owasp: { top10Coverage: this.calculateOWASPCoverage(vulnerabilities), issuesFound: this.getOWASPIssues(vulnerabilities) }, cwe: { top25Coverage: this.calculateCWECoverage(vulnerabilities), issuesFound: this.getCWEIssues(vulnerabilities) }, pci: vulnerabilities.filter(v => v.category === 'sensitive-data').length === 0, hipaa: vulnerabilities.filter(v => v.category === 'sensitive-data').length === 0, gdpr: vulnerabilities.filter(v => v.category === 'sensitive-data').length === 0 } }; } calculateOWASPCoverage(vulnerabilities) { const owaspCategories = ['injection', 'authentication', 'sensitive-data', 'xxe', 'authorization', 'configuration', 'xss', 'deserialization', 'dependency']; const foundCategories = new Set(vulnerabilities.map(v => v.category)); const coveredCategories = owaspCategories.filter(cat => foundCategories.has(cat)); return (coveredCategories.length / owaspCategories.length) * 100; } getOWASPIssues(vulnerabilities) { const owaspMap = { injection: 'A03:2021 - Injection', authentication: 'A07:2021 - Identification and Authentication Failures', 'sensitive-data': 'A02:2021 - Cryptographic Failures', xss: 'A03:2021 - Injection', authorization: 'A01:2021 - Broken Access Control', configuration: 'A05:2021 - Security Misconfiguration' }; const issues = {}; for (const vuln of vulnerabilities) { const owaspCategory = owaspMap[vuln.category]; if (owaspCategory) { issues[owaspCategory] = (issues[owaspCategory] || 0) + 1; } } return Object.entries(issues).map(([category, count]) => ({ category, count })); } calculateCWECoverage(vulnerabilities) { const cweCount = vulnerabilities.filter(v => v.cwe).length; return vulnerabilities.length > 0 ? (cweCount / vulnerabilities.length) * 100 : 0; } getCWEIssues(vulnerabilities) { const cweMap = {}; for (const vuln of vulnerabilities) { if (vuln.cwe) { cweMap[vuln.cwe] = (cweMap[vuln.cwe] || 0) + 1; } } return Object.entries(cweMap).map(([cwe, count]) => ({ cwe, count })); } generateSummary(scanResult) { const vulnerabilities = scanResult.vulnerabilities; const summary = { totalVulnerabilities: vulnerabilities.length, criticalCount: vulnerabilities.filter(v => v.severity === 'critical').length, highCount: vulnerabilities.filter(v => v.severity === 'high').length, mediumCount: vulnerabilities.filter(v => v.severity === 'medium').length, lowCount: vulnerabilities.filter(v => v.severity === 'low').length, riskScore: this.calculateRiskScore(vulnerabilities), complianceScore: this.calculateComplianceScore(scanResult.metrics), recommendations: this.generateSummaryRecommendations(scanResult) }; return summary; } calculateRiskScore(vulnerabilities) { let score = 0; for (const vuln of vulnerabilities) { switch (vuln.severity) { case 'critical': score += 10; break; case 'high': score += 7; break; case 'medium': score += 4; break; case 'low': score += 1; break; } } // Normalize to 0-100 scale (100 being highest risk) return Math.min(100, score); } calculateComplianceScore(metrics) { let score = 100; // Deduct points for compliance failures if (!metrics.complianceMetrics.pci) score -= 20; if (!metrics.complianceMetrics.hipaa) score -= 15; if (!metrics.complianceMetrics.gdpr) score -= 15; // Deduct points based on OWASP coverage if (metrics.complianceMetrics.owasp.top10Coverage < 80) { score -= (80 - metrics.complianceMetrics.owasp.top10Coverage) / 2; } return Math.max(0, score); } generateSummaryRecommendations(scanResult) { const recommendations = []; if (scanResult.summary.criticalCount > 0) { recommendations.push(`Address ${scanResult.summary.criticalCount} critical vulnerabilities immediately`); } if (scanResult.dependencies.vulnerableDependencies > 0) { recommendations.push(`Update ${scanResult.dependencies.vulnerableDependencies} vulnerable dependencies`); } if (scanResult.codeAnalysis.vulnerableFiles > 0) { recommendations.push(`Review ${scanResult.codeAnalysis.vulnerableFiles} files with security issues`); } const configIssues = scanResult.configurationAnalysis.issues.filter(i => i.severity === 'critical' || i.severity === 'high'); if (configIssues.length > 0) { recommendations.push(`Fix ${configIssues.length} configuration security issues`); } return recommendations; } async calculateTrends(scanResult) { // Load historical data and calculate trends // This would typically involve comparing with previous scan results return []; } async generateReport(scanResult) { const actionPlan = this.generateActionPlan(scanResult); const compliance = this.generateComplianceReport(scanResult); const trends = this.generateTrendAnalysis(scanResult); const summary = { riskLevel: this.determineRiskLevel(scanResult.summary.riskScore), totalVulnerabilities: scanResult.summary.totalVulnerabilities, criticalIssues: scanResult.summary.criticalCount, complianceScore: scanResult.summary.complianceScore, trend: 'stable', lastScan: scanResult.timestamp }; const report = { summary, scan: scanResult, actionPlan, compliance, trends, timestamp: new Date() }; if (this.config.generateReport) { await this.saveReport(report); } return report; } determineRiskLevel(riskScore) { if (riskScore >= 70) return 'critical'; if (riskScore >= 50) return 'high'; if (riskScore >= 25) return 'medium'; return 'low'; } generateActionPlan(scanResult) { const actionItems = []; // Critical vulnerabilities first const criticalVulns = scanResult.vulnerabilities.filter(v => v.severity === 'critical'); if (criticalVulns.length > 0) { actionItems.push({ priority: 'critical', category: 'other', title: 'Fix Critical Vulnerabilities', description: `Address ${criticalVulns.length} critical security vulnerabilities`, effort: '1-2d', impact: 'High', vulnerabilities: criticalVulns.map(v => v.id), automatedFix: false }); } // Dependency vulnerabilities if (scanResult.dependencies.vulnerableDependencies > 0) { actionItems.push({ priority: 'high', category: 'dependency', title: 'Update Vulnerable Dependencies', description: `Update ${scanResult.dependencies.vulnerableDependencies} dependencies with known vulnerabilities`, effort: '4-8h', impact: 'Medium', vulnerabilities: [], automatedFix: true }); } // Configuration issues const configIssues = scanResult.configurationAnalysis.issues.filter(i => i.severity === 'critical' || i.severity === 'high'); if (configIssues.length > 0) { actionItems.push({ priority: 'medium', category: 'configuration', title: 'Fix Configuration Issues', description: `Address ${configIssues.length} security configuration issues`, effort: '2-4h', impact: 'Medium', vulnerabilities: [], automatedFix: false }); } return actionItems; } generateComplianceReport(scanResult) { const frameworks = [ { name: 'OWASP Top 10', score: scanResult.metrics.complianceMetrics.owasp.top10Coverage, requirements: this.generateOWASPRequirements(scanResult) }, { name: 'CWE Top 25', score: scanResult.metrics.complianceMetrics.cwe.top25Coverage, requirements: this.generateCWERequirements(scanResult) } ]; const overallScore = frameworks.reduce((sum, f) => sum + f.score, 0) / frameworks.length; return { frameworks, overallScore, gaps: this.identifyComplianceGaps(scanResult) }; } generateOWASPRequirements(scanResult) { const requirements = [ { id: 'A01', description: 'Broken Access Control', status: scanResult.vulnerabilities.some(v => v.category === 'authorization') ? 'non-compliant' : 'compliant', issues: [] }, { id: 'A02', description: 'Cryptographic Failures', status: scanResult.vulnerabilities.some(v => v.category === 'cryptography') ? 'non-compliant' : 'compliant', issues: [] }, { id: 'A03', description: 'Injection', status: scanResult.vulnerabilities.some(v => v.category === 'injection') ? 'non-compliant' : 'compliant', issues: [] } ]; return requirements; } generateCWERequirements(scanResult) { // Simplified CWE requirements return [ { id: 'CWE-79', description: 'Cross-site Scripting', status: scanResult.vulnerabilities.some(v => v.cwe === 'CWE-79') ? 'non-compliant' : 'compliant', issues: [] }, { id: 'CWE-89', description: 'SQL Injection', status: scanResult.vulnerabilities.some(v => v.cwe === 'CWE-89') ? 'non-compliant' : 'compliant', issues: [] } ]; } identifyComplianceGaps(scanResult) { const gaps = []; // Check for common compliance gaps const injectionVulns = scanResult.vulnerabilities.filter(v => v.category === 'injection'); if (injectionVulns.length > 0) { gaps.push({ framework: 'OWASP Top 10', requirement: 'A03:2021 - Injection', severity: 'high', description: 'Application vulnerable to injection attacks', remediation: 'Implement input validation and parameterized queries' }); } return gaps; } generateTrendAnalysis(scanResult) { return { historical: scanResult.trends || [], predictions: [], patterns: [] }; } async saveReport(report) { await fs.ensureDir(this.config.outputPath); // Save JSON report const jsonPath = path.join(this.config.outputPath, 'security-report.json'); await fs.writeJson(jsonPath, report, { spaces: 2 }); // Save HTML report const htmlPath = path.join(this.config.outputPath, 'security-report.html'); const html = this.generateHtmlReport(report); await fs.writeFile(htmlPath, html); this.emit('report:saved', { json: jsonPath, html: htmlPath }); } generateHtmlReport(report) { return `<!DOCTYPE html> <html> <head> <title>Security Scan Report</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } .header { background: #f44336; color: white; padding: 20px; border-radius: 5px; } .summary { background: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0; } .metric { display: inline-block; margin: 10px; padding: 10px; background: white; border-radius: 3px; } .critical { color: #d32f2f; font-weight: bold; } .high { color: #f57c00; fo