sicua
Version:
A tool for analyzing project structure and dependencies
254 lines (253 loc) • 10.7 kB
JavaScript
;
/**
* 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;