sicua
Version:
A tool for analyzing project structure and dependencies
234 lines (233 loc) • 8.46 kB
JavaScript
;
/**
* Abstract base class for security vulnerability detectors
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseDetector = void 0;
const PatternMatcher_1 = require("../utils/PatternMatcher");
const SecurityContext_1 = require("../utils/SecurityContext");
const pathUtils_1 = require("../../../utils/common/pathUtils");
const general_constants_1 = require("../constants/general.constants");
class BaseDetector {
constructor(detectorName, vulnerabilityType, defaultSeverity, patterns = []) {
this.detectorName = detectorName;
this.vulnerabilityType = vulnerabilityType;
this.defaultSeverity = defaultSeverity;
this.patterns = patterns;
}
/**
* Apply text-based pattern matching to file content
*/
applyPatternMatching(content, filePath, patterns = this.patterns) {
const results = [];
for (const pattern of patterns) {
if (this.shouldApplyPattern(pattern, filePath)) {
const result = PatternMatcher_1.PatternMatcher.applyPattern(pattern, content, filePath);
if (result.matches.length > 0) {
results.push(result);
}
}
}
return results;
}
/**
* Apply AST-based analysis to TypeScript source files
*/
applyASTAnalysis(sourceFile, filePath, customAnalyzer) {
if (customAnalyzer) {
return customAnalyzer(sourceFile, filePath);
}
return [];
}
/**
* Create a vulnerability instance with proper metadata
*/
createVulnerability(filePath, location, context, description, severity = this.defaultSeverity, confidence = "high", metadata) {
return {
id: this.generateVulnerabilityId(filePath, location, this.vulnerabilityType),
type: this.vulnerabilityType,
severity,
confidence,
description,
filePath,
location,
context,
metadata,
detectedAt: Date.now(),
};
}
/**
* Convert pattern match results to vulnerabilities
*/
convertPatternMatchesToVulnerabilities(patternResults, additionalValidator) {
const vulnerabilities = [];
for (const result of patternResults) {
// Apply additional validation if provided
if (additionalValidator && !additionalValidator(result)) {
continue;
}
for (const match of result.matches) {
const location = {
line: match.line,
column: match.column,
endLine: match.line,
endColumn: match.column + match.match.length,
};
const context = {
code: match.match,
surroundingContext: match.context,
functionName: this.extractFunctionName(match.context || ""),
componentName: this.extractComponentName(result.filePath),
};
const vulnerability = this.createVulnerability(result.filePath, location, context, result.pattern.description, result.pattern.severity, result.pattern.confidence, {
patternId: result.pattern.id,
matchedGroups: match.groups,
});
vulnerabilities.push(vulnerability);
}
}
return vulnerabilities;
}
/**
* Filter files that should be analyzed by this detector
*/
filterRelevantFiles(scanResult, fileExtensions, excludePatterns) {
let relevantFiles = scanResult.filePaths;
// Filter by file extensions
if (fileExtensions && fileExtensions.length > 0) {
relevantFiles = relevantFiles.filter((filePath) => fileExtensions.some((ext) => filePath.endsWith(ext)));
}
// Exclude files matching patterns
if (excludePatterns && excludePatterns.length > 0) {
relevantFiles = relevantFiles.filter((filePath) => !excludePatterns.some((pattern) => {
const regex = new RegExp(pattern);
return regex.test(filePath);
}));
}
return relevantFiles;
}
/**
* Get file context information for security analysis
*/
getFileContext(filePath, content) {
return SecurityContext_1.SecurityContext.analyzeFileContext(filePath, content);
}
/**
* Check if a pattern should be applied to a specific file
*/
shouldApplyPattern(pattern, filePath) {
if (pattern.fileTypes && pattern.fileTypes.length > 0) {
const fileExtension = filePath.split(".").pop();
return pattern.fileTypes.includes(`.${fileExtension}`);
}
return pattern.enabled;
}
/**
* Generate a unique ID for a vulnerability
*/
generateVulnerabilityId(filePath, location, type) {
const input = `${filePath}:${location.line}:${location.column}:${type}`;
return pathUtils_1.PathUtils.generateHash(input).substring(0, 16);
}
/**
* Extract function name from context
*/
extractFunctionName(context) {
// Simple heuristic to find function names in context
const functionPatterns = [
/function\s+(\w+)/,
/const\s+(\w+)\s*=/,
/(\w+)\s*:\s*\(/,
/(\w+)\s*\(/,
];
for (const pattern of functionPatterns) {
const match = context.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return undefined;
}
/**
* Extract component name from file path
*/
extractComponentName(filePath) {
const fileName = filePath.split("/").pop();
if (!fileName)
return undefined;
const nameWithoutExtension = fileName.replace(/\.(tsx?|jsx?)$/, "");
// Return component name if it starts with uppercase (React convention)
if (/^[A-Z]/.test(nameWithoutExtension)) {
return nameWithoutExtension;
}
return undefined;
}
/**
* Validate vulnerability before adding to results
*/
validateVulnerability(vulnerability) {
// Basic validation checks
if (!vulnerability.filePath || !vulnerability.description) {
return false;
}
if (vulnerability.location.line < 1 || vulnerability.location.column < 1) {
return false;
}
if (!vulnerability.context.code.trim()) {
return false;
}
return true;
}
/**
* Apply confidence adjustments based on context
*/
adjustConfidenceBasedOnContext(vulnerability, fileContext) {
let confidence = vulnerability.confidence;
// Lower confidence for test files
if (fileContext.fileType === "test") {
confidence = this.lowerConfidence(confidence);
}
// Higher confidence for security-sensitive contexts
if (fileContext.riskContexts.includes("authentication") ||
fileContext.riskContexts.includes("authorization")) {
confidence = this.raiseConfidence(confidence);
}
return confidence;
}
/**
* Helper to lower confidence level
*/
lowerConfidence(confidence) {
const levels = ["low", "medium", "high"];
const currentIndex = levels.indexOf(confidence);
return levels[Math.max(0, currentIndex - 1)];
}
/**
* Helper to raise confidence level
*/
raiseConfidence(confidence) {
const levels = ["low", "medium", "high"];
const currentIndex = levels.indexOf(confidence);
return levels[Math.min(levels.length - 1, currentIndex + 1)];
}
/**
* Check if context suggests this is test code
*/
isInTestContext(context) {
return general_constants_1.TEST_INDICATORS.some((indicator) => context.includes(indicator));
}
/**
* Check if text is in a comment
*/
isInComment(context, text) {
const lines = context.split("\n");
for (const line of lines) {
if (line.includes(text) &&
(line.trim().startsWith("//") || line.trim().startsWith("*"))) {
return true;
}
}
return false;
}
}
exports.BaseDetector = BaseDetector;