UNPKG

agentic-qe

Version:

Agentic Quality Engineering Fleet System - AI-driven quality management platform

378 lines 15.7 kB
"use strict"; /** * Real Security Scanner Implementation * Integrates ESLint Security, Semgrep, and NPM Audit */ 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RealSecurityScanner = void 0; const child_process_1 = require("child_process"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); /** * Real Security Scanner using actual tools */ class RealSecurityScanner { constructor(workingDir = process.cwd()) { this.workingDir = workingDir; } /** * Run ESLint security scan */ async runESLintScan(target) { const startTime = Date.now(); const findings = []; try { // Create temporary ESLint config with security plugin const eslintConfig = { env: { node: true, es2021: true }, extends: ['eslint:recommended'], plugins: ['security'], rules: { 'security/detect-object-injection': 'warn', 'security/detect-non-literal-fs-filename': 'warn', 'security/detect-eval-with-expression': 'error', 'security/detect-non-literal-regexp': 'warn', 'security/detect-unsafe-regex': 'error', 'security/detect-buffer-noassert': 'error', 'security/detect-child-process': 'warn', 'security/detect-disable-mustache-escape': 'error', 'security/detect-no-csrf-before-method-override': 'error', 'security/detect-non-literal-require': 'warn', 'security/detect-possible-timing-attacks': 'warn', 'security/detect-pseudoRandomBytes': 'error' } }; const configPath = path.join(this.workingDir, '.eslintrc.security.json'); fs.writeFileSync(configPath, JSON.stringify(eslintConfig, null, 2)); // Run ESLint const result = (0, child_process_1.spawnSync)('npx', [ 'eslint', '--config', configPath, '--format', 'json', '--no-eslintrc', target ], { cwd: this.workingDir, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 // 10MB buffer }); // Clean up config try { fs.unlinkSync(configPath); } catch (err) { // Ignore cleanup errors } if (result.stdout) { const eslintResults = JSON.parse(result.stdout); // Parse ESLint results for (const file of eslintResults) { if (file.messages && file.messages.length > 0) { for (const message of file.messages) { const severity = this.mapESLintSeverity(message.severity); const cwe = this.extractCWE(message.ruleId); findings.push({ id: `eslint-${Date.now()}-${findings.length}`, type: 'sast', severity, title: message.ruleId || 'ESLint Security Issue', description: message.message, location: `${file.filePath}:${message.line}:${message.column}`, cwe, remediation: this.getESLintRemediation(message.ruleId) }); } } } } return { findings, scanType: 'eslint-security', duration: Date.now() - startTime, success: result.status === 0 || result.status === 1 // 0=clean, 1=issues found }; } catch (error) { return { findings, scanType: 'eslint-security', duration: Date.now() - startTime, success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Run Semgrep SAST scan */ async runSemgrepScan(target) { const startTime = Date.now(); const findings = []; try { // Run Semgrep with auto config (community rules) const result = (0, child_process_1.spawnSync)('npx', [ 'semgrep', '--config', 'auto', '--json', '--quiet', target ], { cwd: this.workingDir, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, timeout: 60000 // 60 second timeout }); if (result.stdout) { try { const semgrepOutput = JSON.parse(result.stdout); if (semgrepOutput.results && Array.isArray(semgrepOutput.results)) { for (const finding of semgrepOutput.results) { const severity = this.mapSemgrepSeverity(finding.extra?.severity); const cwe = finding.extra?.metadata?.cwe?.[0]; const cve = finding.extra?.metadata?.cve; findings.push({ id: `semgrep-${finding.check_id}-${findings.length}`, type: 'sast', severity, title: finding.check_id, description: finding.extra?.message || finding.check_id, location: `${finding.path}:${finding.start.line}`, cwe: cwe ? `CWE-${cwe}` : undefined, cve, remediation: finding.extra?.metadata?.remediation, references: finding.extra?.metadata?.references }); } } } catch (parseError) { // Semgrep output might not be valid JSON in some cases console.warn('Failed to parse Semgrep output:', parseError); } } return { findings, scanType: 'semgrep-sast', duration: Date.now() - startTime, success: result.status === 0 || result.status === 1 }; } catch (error) { return { findings, scanType: 'semgrep-sast', duration: Date.now() - startTime, success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Run NPM Audit scan */ async runNPMAuditScan() { const startTime = Date.now(); const findings = []; try { const result = (0, child_process_1.spawnSync)('npm', ['audit', '--json'], { cwd: this.workingDir, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 }); if (result.stdout) { try { const auditOutput = JSON.parse(result.stdout); if (auditOutput.vulnerabilities) { for (const [pkgName, vulnData] of Object.entries(auditOutput.vulnerabilities)) { const vuln = vulnData; // NPM audit v7+ format if (vuln.via && Array.isArray(vuln.via)) { for (const viaItem of vuln.via) { if (typeof viaItem === 'object' && viaItem.title) { const severity = this.mapNPMSeverity(viaItem.severity); findings.push({ id: `npm-${viaItem.cve || viaItem.source || Date.now()}-${findings.length}`, type: 'dependency', severity, title: `Vulnerable Dependency: ${pkgName}`, description: viaItem.title, location: 'package.json', cve: viaItem.cve, cvss: viaItem.cvss?.score, remediation: `Update ${pkgName} to a secure version (${vuln.fixAvailable ? 'fix available' : 'no fix available yet'})`, references: viaItem.url ? [viaItem.url] : [] }); } } } } } } catch (parseError) { console.warn('Failed to parse NPM audit output:', parseError); } } return { findings, scanType: 'npm-audit', duration: Date.now() - startTime, success: true // NPM audit can return non-zero even on success }; } catch (error) { return { findings, scanType: 'npm-audit', duration: Date.now() - startTime, success: false, error: error instanceof Error ? error.message : String(error) }; } } /** * Run comprehensive security scan */ async runComprehensiveScan(target) { const allFindings = []; // Run ESLint security scan const eslintResult = await this.runESLintScan(target); if (eslintResult.success) { allFindings.push(...eslintResult.findings); } // Run Semgrep scan (only if installed) if (this.isSemgrepAvailable()) { const semgrepResult = await this.runSemgrepScan(target); if (semgrepResult.success) { allFindings.push(...semgrepResult.findings); } } // Run NPM audit const npmResult = await this.runNPMAuditScan(); if (npmResult.success) { allFindings.push(...npmResult.findings); } return allFindings; } /** * Check if Semgrep is available */ isSemgrepAvailable() { try { const result = (0, child_process_1.spawnSync)('npx', ['semgrep', '--version'], { encoding: 'utf8', timeout: 5000 }); return result.status === 0; } catch { return false; } } /** * Map ESLint severity to our severity levels */ mapESLintSeverity(severity) { // ESLint: 2 = error, 1 = warning if (severity === 2) return 'high'; if (severity === 1) return 'medium'; return 'info'; } /** * Map Semgrep severity */ mapSemgrepSeverity(severity) { if (!severity) return 'medium'; const sev = severity.toLowerCase(); if (sev === 'error' || sev === 'critical') return 'critical'; if (sev === 'warning' || sev === 'high') return 'high'; if (sev === 'info' || sev === 'low') return 'low'; return 'medium'; } /** * Map NPM audit severity */ mapNPMSeverity(severity) { if (!severity) return 'medium'; const sev = severity.toLowerCase(); if (sev === 'critical') return 'critical'; if (sev === 'high') return 'high'; if (sev === 'moderate' || sev === 'medium') return 'medium'; if (sev === 'low') return 'low'; return 'info'; } /** * Extract CWE from ESLint rule ID */ extractCWE(ruleId) { if (!ruleId) return undefined; // Map ESLint security rules to CWE const cweMap = { 'security/detect-eval-with-expression': 'CWE-94', 'security/detect-unsafe-regex': 'CWE-1333', 'security/detect-buffer-noassert': 'CWE-119', 'security/detect-child-process': 'CWE-78', 'security/detect-non-literal-fs-filename': 'CWE-22', 'security/detect-non-literal-regexp': 'CWE-185', 'security/detect-non-literal-require': 'CWE-95', 'security/detect-object-injection': 'CWE-94', 'security/detect-possible-timing-attacks': 'CWE-208' }; return cweMap[ruleId]; } /** * Get remediation advice for ESLint rules */ getESLintRemediation(ruleId) { if (!ruleId) return undefined; const remediationMap = { 'security/detect-eval-with-expression': 'Avoid using eval() with user input. Use safer alternatives like JSON.parse() or Function constructor with proper validation.', 'security/detect-unsafe-regex': 'Refactor regex to avoid catastrophic backtracking. Use tools like safe-regex to validate.', 'security/detect-buffer-noassert': 'Use assert methods for buffer operations to prevent buffer overflows.', 'security/detect-child-process': 'Validate and sanitize all inputs to child_process methods. Avoid shell=true.', 'security/detect-non-literal-fs-filename': 'Validate and sanitize file paths. Use path.join() and check against whitelist.', 'security/detect-non-literal-regexp': 'Use literal regex patterns instead of constructing from user input.', 'security/detect-non-literal-require': 'Avoid dynamic require() with user input. Use static imports or whitelist allowed modules.', 'security/detect-object-injection': 'Validate object keys before dynamic access. Use Map instead of objects for user-controlled keys.', 'security/detect-possible-timing-attacks': 'Use constant-time comparison functions for sensitive data like passwords or tokens.' }; return remediationMap[ruleId]; } } exports.RealSecurityScanner = RealSecurityScanner; //# sourceMappingURL=SecurityScanner.js.map