UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

254 lines (253 loc) 10.7 kB
"use strict"; /** * Vulnerability aggregation and deduplication utilities */ Object.defineProperty(exports, "__esModule", { value: true }); exports.VulnerabilityAggregator = void 0; class VulnerabilityAggregator { /** * Aggregate vulnerabilities from multiple detectors into a final result */ // Fix for VulnerabilityAggregator.aggregateResults() - Add missing dataFlowAnalysis static aggregateResults(allVulnerabilities) { // Deduplicate vulnerabilities const deduplicatedVulnerabilities = this.deduplicateVulnerabilities(allVulnerabilities); // Group vulnerabilities by file for metrics calculation only const vulnerabilitiesByFile = this.groupVulnerabilitiesByFile(deduplicatedVulnerabilities); // Calculate security metrics const metrics = this.calculateSecurityMetrics(deduplicatedVulnerabilities, vulnerabilitiesByFile); // Create minimal project analysis structure const projectAnalysis = this.createProjectAnalysis(deduplicatedVulnerabilities); return { vulnerabilities: deduplicatedVulnerabilities, metrics, projectAnalysis, }; } /** * Remove duplicate vulnerabilities based on location and type */ static deduplicateVulnerabilities(vulnerabilities) { const seen = new Set(); const deduplicated = []; for (const vuln of vulnerabilities) { // Create a unique key based on file, line, column, and type const key = `${vuln.filePath}:${vuln.location.line}:${vuln.location.column}:${vuln.type}`; if (!seen.has(key)) { seen.add(key); deduplicated.push(vuln); } else { // If we have a duplicate, keep the one with higher confidence const existingIndex = deduplicated.findIndex((existing) => existing.filePath === vuln.filePath && existing.location.line === vuln.location.line && existing.location.column === vuln.location.column && existing.type === vuln.type); if (existingIndex !== -1) { const existing = deduplicated[existingIndex]; if (this.getConfidenceScore(vuln.confidence) > this.getConfidenceScore(existing.confidence)) { deduplicated[existingIndex] = vuln; } } } } return deduplicated; } /** * Group vulnerabilities by file path */ static groupVulnerabilitiesByFile(vulnerabilities) { const grouped = {}; for (const vuln of vulnerabilities) { if (!grouped[vuln.filePath]) { grouped[vuln.filePath] = []; } grouped[vuln.filePath].push(vuln); } // Sort vulnerabilities within each file by line number for (const filePath in grouped) { grouped[filePath].sort((a, b) => { if (a.location.line !== b.location.line) { return a.location.line - b.location.line; } return a.location.column - b.location.column; }); } return grouped; } /** * Group vulnerabilities by type */ static groupVulnerabilitiesByType(vulnerabilities) { const grouped = {}; for (const vuln of vulnerabilities) { if (!grouped[vuln.type]) { grouped[vuln.type] = []; } grouped[vuln.type].push(vuln); } // Sort vulnerabilities within each type by severity (critical first) for (const type in grouped) { grouped[type].sort((a, b) => { const severityOrder = { critical: 4, high: 3, medium: 2, low: 1 }; return severityOrder[b.severity] - severityOrder[a.severity]; }); } return grouped; } /** * Calculate security metrics from vulnerabilities */ static calculateSecurityMetrics(vulnerabilities, vulnerabilitiesByFile) { const totalFiles = Object.keys(vulnerabilitiesByFile).length; const vulnerableFiles = Object.keys(vulnerabilitiesByFile).filter((filePath) => vulnerabilitiesByFile[filePath].length > 0).length; const cleanFiles = totalFiles - vulnerableFiles; // Count vulnerabilities by severity const vulnerabilitiesBySeverity = { critical: 0, high: 0, medium: 0, low: 0, }; const typeCount = {}; for (const vuln of vulnerabilities) { vulnerabilitiesBySeverity[vuln.severity]++; typeCount[vuln.type] = (typeCount[vuln.type] || 0) + 1; } // Find most common vulnerability type let mostCommonVulnerability = null; let maxCount = 0; for (const [type, count] of Object.entries(typeCount)) { if (count > maxCount) { maxCount = count; mostCommonVulnerability = type; } } // Calculate overall risk level const overallRisk = this.calculateOverallRisk(vulnerabilitiesBySeverity); // Calculate security score (0-100, higher is better) const securityScore = this.calculateSecurityScore(vulnerabilitiesBySeverity, totalFiles); return { overallRisk, securityScore, totalFiles, vulnerableFiles, cleanFiles, vulnerabilitiesBySeverity, mostCommonVulnerability, }; } /** * Calculate overall risk level based on severity distribution */ static calculateOverallRisk(vulnerabilitiesBySeverity) { if (vulnerabilitiesBySeverity.critical > 0) { return "critical"; } if (vulnerabilitiesBySeverity.high > 2) { return "high"; } if (vulnerabilitiesBySeverity.high > 0 || vulnerabilitiesBySeverity.medium > 5) { return "medium"; } if (vulnerabilitiesBySeverity.medium > 0 || vulnerabilitiesBySeverity.low > 10) { return "low"; } return "none"; } /** * Calculate security score (0-100, higher is better) */ static calculateSecurityScore(vulnerabilitiesBySeverity, totalFiles) { // Base score starts at 100 let score = 100; // Deduct points based on severity and count score -= vulnerabilitiesBySeverity.critical * 25; // Critical: -25 points each score -= vulnerabilitiesBySeverity.high * 10; // High: -10 points each score -= vulnerabilitiesBySeverity.medium * 5; // Medium: -5 points each score -= vulnerabilitiesBySeverity.low * 1; // Low: -1 point each // Additional penalty for high vulnerability density const totalVulnerabilities = Object.values(vulnerabilitiesBySeverity).reduce((a, b) => a + b, 0); if (totalFiles > 0) { const vulnerabilityDensity = totalVulnerabilities / totalFiles; if (vulnerabilityDensity > 2) { score -= (vulnerabilityDensity - 2) * 5; // Penalty for high density } } // Ensure score is between 0 and 100 return Math.max(0, Math.min(100, Math.round(score))); } /** * Create a minimal project analysis structure */ static createProjectAnalysis(vulnerabilities) { // Count API route related vulnerabilities const apiRouteVulns = vulnerabilities.filter((v) => v.filePath.includes("/api/") || v.filePath.includes("/pages/api/")); // Count configuration related vulnerabilities const configVulns = vulnerabilities.filter((v) => v.type === "missing-security-headers" || v.type === "environment-exposure" || v.filePath.includes("config") || v.filePath.includes(".env")); // Count dependency related vulnerabilities (simplified) const dependencyVulns = vulnerabilities.filter((v) => v.type === "insecure-random" || v.type === "dangerous-eval"); // Count AI-ready vulnerabilities const aiReadyVulns = vulnerabilities.filter((v) => v.metadata?.aiAnalysisReady === true); // Helper function to safely access data flow path const getDataFlowPath = (v) => { return v.metadata?.dataFlowPath; }; return { apiRouteSecurity: { totalRoutes: 0, // Would need scan result data vulnerableRoutes: apiRouteVulns.length, authenticatedRoutes: 0, // Would need deeper analysis validatedRoutes: 0, // Would need deeper analysis }, configurationSecurity: { securityHeadersConfigured: !configVulns.some((v) => v.type === "missing-security-headers"), envVarsSecure: !configVulns.some((v) => v.type === "environment-exposure"), missingConfigurations: configVulns.map((v) => v.description), }, dependencySecurity: { totalDependencies: 0, // Would need package.json analysis vulnerableDependencies: dependencyVulns.length, outdatedDependencies: 0, // Would need external vulnerability database }, }; } /** * Get numeric confidence score for comparison */ static getConfidenceScore(confidence) { const scores = { high: 3, medium: 2, low: 1 }; return scores[confidence]; } /** * Sort vulnerabilities by priority (severity + confidence) */ static sortVulnerabilitiesByPriority(vulnerabilities) { const severityOrder = { critical: 4, high: 3, medium: 2, low: 1 }; const confidenceOrder = { high: 3, medium: 2, low: 1 }; return [...vulnerabilities].sort((a, b) => { // First sort by severity const severityDiff = severityOrder[b.severity] - severityOrder[a.severity]; if (severityDiff !== 0) { return severityDiff; } // Then by confidence const confidenceDiff = confidenceOrder[b.confidence] - confidenceOrder[a.confidence]; if (confidenceDiff !== 0) { return confidenceDiff; } // Finally by file path and line number if (a.filePath !== b.filePath) { return a.filePath.localeCompare(b.filePath); } return a.location.line - b.location.line; }); } } exports.VulnerabilityAggregator = VulnerabilityAggregator;