bugnitor-security-scanner
Version:
AI-Era Security Scanner: Intelligent automated security review agent specializing in AI-generated vulnerability patterns
605 lines ⢠28.3 kB
JavaScript
"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;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecurityScanner = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const glob_1 = require("glob");
const secrets_1 = require("./secrets");
const enhanced_secrets_1 = require("./enhanced-secrets");
const dangerous_api_detector_1 = require("./dangerous-api-detector");
const code_quality_analyzer_1 = require("./code-quality-analyzer");
const ai_vulnerability_detector_1 = require("./ai-vulnerability-detector");
const vulnerabilities_1 = require("./vulnerabilities");
const advanced_vulnerabilities_1 = require("./advanced-vulnerabilities");
const dependency_analyzer_1 = require("./dependency-analyzer");
const cicd_analyzer_1 = require("./cicd-analyzer");
const security_grader_1 = require("./security-grader");
class SecurityScanner {
constructor() {
this.defaultExcludePatterns = [
'node_modules/**',
'.git/**',
'dist/**',
'build/**',
'*.min.js',
'*.map',
'.env.example',
'package-lock.json',
'yarn.lock',
'*.log'
];
this.dependencyAnalyzer = new dependency_analyzer_1.DependencyAnalyzer();
this.cicdAnalyzer = new cicd_analyzer_1.CICDAnalyzer();
this.securityGrader = new security_grader_1.SecurityGrader();
this.enhancedSecretDetector = new enhanced_secrets_1.EnhancedSecretDetector();
this.dangerousAPIDetector = new dangerous_api_detector_1.DangerousAPIDetector();
this.codeQualityAnalyzer = new code_quality_analyzer_1.CodeQualityAnalyzer();
this.aiVulnerabilityDetector = new ai_vulnerability_detector_1.AIVulnerabilityDetector();
}
getLineNumber(content, index) {
return content.substring(0, index).split('\n').length;
}
getColumnNumber(content, index) {
const lines = content.substring(0, index).split('\n');
return lines[lines.length - 1].length + 1;
}
async scan(options) {
const findings = [];
const fileAnalyses = [];
const excludePatterns = [...this.defaultExcludePatterns, ...(options.excludePatterns || [])];
const pattern = options.includePatterns?.length
? `{${options.includePatterns.join(',')}}`
: '**/*';
const files = await (0, glob_1.glob)(pattern, {
cwd: options.path,
absolute: true,
ignore: excludePatterns,
dot: false
});
console.log(`\nš Scanning ${files.length} files...`);
let processedFiles = 0;
for (const file of files) {
try {
const stats = fs.statSync(file);
if (!stats.isFile())
continue;
// Skip very large files (over 10MB) to avoid performance issues
const maxFileSize = 10 * 1024 * 1024; // 10MB
if (stats.size > maxFileSize) {
console.warn(`\nSkipping large file: ${path.relative(options.path, file)} (${Math.round(stats.size / 1024 / 1024)}MB)`);
continue;
}
// Skip binary files that shouldn't contain secrets
const fileExtension = path.extname(file).toLowerCase();
const binaryExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.ico', '.pdf', '.zip', '.exe', '.dll', '.so', '.dylib', '.woff', '.woff2', '.ttf', '.eot', '.mp4', '.mp3', '.avi', '.mov'];
if (binaryExtensions.includes(fileExtension)) {
continue;
}
processedFiles++;
// Check if file is binary by reading first few bytes
const buffer = fs.readFileSync(file);
if (this.isBinaryFile(buffer, file)) {
continue;
}
const content = buffer.toString('utf-8');
const relativePath = path.relative(options.path, file);
const absolutePath = path.resolve(file);
// Show progress
if (processedFiles % 10 === 0 || processedFiles === files.length) {
process.stdout.write(`\rš Processed: ${processedFiles}/${files.length} files`);
}
const fileFindings = [];
if (!options.vulnerabilitiesOnly) {
const secretFindings = this.scanForSecrets(content, relativePath, absolutePath);
const enhancedSecretFindings = this.scanForEnhancedSecrets(content, relativePath, absolutePath);
fileFindings.push(...secretFindings);
fileFindings.push(...enhancedSecretFindings);
}
if (!options.secretsOnly) {
const vulnerabilityFindings = this.scanForVulnerabilities(content, relativePath, absolutePath);
const advancedVulnFindings = this.scanForAdvancedVulnerabilities(content, relativePath, absolutePath);
const dangerousAPIFindings = this.scanForDangerousAPIs(content, relativePath, absolutePath);
const aiVulnFindings = this.scanForAIVulnerabilities(content, relativePath, absolutePath);
fileFindings.push(...vulnerabilityFindings);
fileFindings.push(...advancedVulnFindings);
fileFindings.push(...dangerousAPIFindings);
fileFindings.push(...aiVulnFindings);
}
// Analyze code quality
const qualityAnalysis = this.codeQualityAnalyzer.analyzeCodeQuality(content, relativePath, content.split('\n').length);
fileFindings.push(...qualityAnalysis.findings);
// Create file analysis
const fileAnalysis = {
filePath: relativePath,
absolutePath: absolutePath,
relativePath: relativePath,
size: stats.size,
findings: fileFindings,
linesOfCode: content.split('\n').length,
fileType: path.extname(file).substring(1) || 'unknown',
qualityScore: qualityAnalysis.qualityScore,
summary: this.generateSummary(fileFindings)
};
fileAnalyses.push(fileAnalysis);
findings.push(...fileFindings);
}
catch (error) {
console.warn(`\nWarning: Could not scan file ${file}: ${error}`);
}
}
console.log('\n'); // New line after progress
// Analyze dependencies
console.log('š Analyzing dependencies...');
const dependencyResult = await this.dependencyAnalyzer.analyzeDependencies(options.path);
findings.push(...dependencyResult.findings);
// Analyze CI/CD configurations
console.log('š Analyzing CI/CD configurations...');
const cicdResult = await this.cicdAnalyzer.analyzeCICD(options.path);
findings.push(...cicdResult.findings);
const filteredFindings = this.filterBySeverity(findings, options.minSeverity);
const filteredFileAnalyses = fileAnalyses.map(fa => ({
...fa,
findings: this.filterBySeverity(fa.findings, options.minSeverity),
summary: this.generateSummary(this.filterBySeverity(fa.findings, options.minSeverity))
}));
const folderStructure = this.buildFolderStructure(filteredFileAnalyses, options.path);
// Calculate security grade
console.log('š Calculating security grade...');
const securityGrade = this.securityGrader.calculateSecurityGrade(filteredFindings, fileAnalyses.length);
const nextSteps = this.securityGrader.generateNextSteps(filteredFindings, securityGrade);
return {
projectPath: options.path,
scanTime: new Date(),
findings: filteredFindings,
fileAnalyses: filteredFileAnalyses,
folderStructure,
securityGrade,
summary: {
...this.generateSummary(filteredFindings),
filesScanned: fileAnalyses.length,
filesWithIssues: filteredFileAnalyses.filter(fa => fa.findings.length > 0).length,
foldersScanned: this.countFolders(folderStructure),
byCategory: this.generateCategorySummary(filteredFindings),
averageConfidence: this.calculateAverageConfidence(filteredFindings)
},
nextSteps
};
}
scanForSecrets(content, filename, absolutePath) {
const findings = [];
const results = (0, secrets_1.detectSecrets)(content, filename);
for (const result of results) {
for (const match of result.matches) {
const line = this.getLineNumber(content, match.index || 0);
const column = this.getColumnNumber(content, match.index || 0);
const codeContext = this.getCodeContext(content, match.index || 0);
// Calculate context-aware confidence
const contextAwareConfidence = this.calculateContextAwareConfidence(result.pattern.confidence, codeContext.matchedCode, codeContext.contextBefore, codeContext.contextAfter, filename);
// Skip findings with very low confidence
if (contextAwareConfidence < 0.5)
continue;
findings.push({
type: 'sensitive_data',
category: 'Exposed Secrets',
severity: contextAwareConfidence > 0.8 ? 'critical' : 'high',
title: result.pattern.name,
description: result.pattern.description,
file: filename,
line,
column,
code: codeContext.matchedCode,
codeContext: {
before: codeContext.contextBefore,
after: codeContext.contextAfter
},
recommendation: 'Remove hardcoded secrets and use environment variables or secure configuration management',
confidence: contextAwareConfidence,
cwe: 'CWE-798',
owasp: 'A02:2021 ā Cryptographic Failures',
impact: 'Credential compromise, unauthorized access',
effort: 'low'
});
}
}
return findings;
}
scanForEnhancedSecrets(content, filename, absolutePath) {
const findings = [];
const results = this.enhancedSecretDetector.detectSecrets(content, filename);
for (const result of results) {
for (const match of result.matches) {
const line = this.getLineNumber(content, match.match.index || 0);
const column = this.getColumnNumber(content, match.match.index || 0);
const codeContext = this.getCodeContext(content, match.match.index || 0);
findings.push({
type: 'sensitive_data',
category: result.pattern.name,
severity: result.pattern.severity,
title: result.pattern.name,
description: result.pattern.description,
file: filename,
line,
column,
code: codeContext.matchedCode,
codeContext: {
before: codeContext.contextBefore,
after: codeContext.contextAfter
},
recommendation: result.pattern.remediation.description,
confidence: match.confidence,
cwe: result.pattern.cwe,
owasp: result.pattern.owasp,
impact: 'Credential compromise, unauthorized access',
effort: result.pattern.remediation.effort
});
}
}
return findings;
}
scanForDangerousAPIs(content, filename, absolutePath) {
const findings = [];
const results = this.dangerousAPIDetector.detectDangerousAPIs(content, filename);
for (const result of results) {
for (const match of result.matches) {
const line = this.getLineNumber(content, match.match.index || 0);
const column = this.getColumnNumber(content, match.match.index || 0);
const codeContext = this.getCodeContext(content, match.match.index || 0);
findings.push({
type: 'injection',
category: result.pattern.category,
severity: result.pattern.severity,
title: result.pattern.name,
description: result.pattern.description,
file: filename,
line,
column,
code: codeContext.matchedCode,
codeContext: {
before: codeContext.contextBefore,
after: codeContext.contextAfter
},
recommendation: result.pattern.remediation.description,
confidence: match.confidence,
cwe: result.pattern.cwe,
owasp: result.pattern.owasp,
impact: result.pattern.impact,
effort: result.pattern.remediation.effort
});
}
}
return findings;
}
scanForAIVulnerabilities(content, filename, absolutePath) {
const findings = [];
const results = this.aiVulnerabilityDetector.detectAIVulnerabilities(content, filename);
for (const result of results) {
for (const match of result.matches) {
const line = this.getLineNumber(content, match.match.index || 0);
const column = this.getColumnNumber(content, match.match.index || 0);
const codeContext = this.getCodeContext(content, match.match.index || 0);
findings.push({
type: 'injection',
category: `AI-Generated: ${result.pattern.category}`,
severity: result.pattern.severity,
title: `š¤ ${result.pattern.name}`,
description: `${result.pattern.description} | AI Context: ${result.pattern.aiContext}`,
file: filename,
line,
column,
code: codeContext.matchedCode,
codeContext: {
before: codeContext.contextBefore,
after: codeContext.contextAfter
},
recommendation: result.pattern.remediation.description,
confidence: match.confidence,
cwe: result.pattern.cwe,
owasp: result.pattern.owasp,
impact: result.pattern.impact,
effort: result.pattern.remediation.effort
});
}
}
return findings;
}
scanForVulnerabilities(content, filename, absolutePath) {
const findings = [];
const results = (0, vulnerabilities_1.checkVulnerabilities)(content, filename);
for (const result of results) {
for (const match of result.matches) {
const line = this.getLineNumber(content, match.index || 0);
const column = this.getColumnNumber(content, match.index || 0);
const codeContext = this.getCodeContext(content, match.index || 0);
findings.push({
type: 'config',
category: 'Legacy Vulnerability',
severity: result.rule.severity,
title: result.rule.name,
description: result.rule.description,
file: filename,
line,
column,
code: codeContext.matchedCode,
codeContext: {
before: codeContext.contextBefore,
after: codeContext.contextAfter
},
recommendation: result.rule.recommendation,
confidence: 0.8,
impact: 'Security vulnerability',
effort: 'medium'
});
}
}
return findings;
}
scanForAdvancedVulnerabilities(content, filename, absolutePath) {
const findings = [];
const results = (0, advanced_vulnerabilities_1.checkAdvancedVulnerabilities)(content, filename);
for (const result of results) {
for (const match of result.matches) {
const line = this.getLineNumber(content, match.index || 0);
const column = this.getColumnNumber(content, match.index || 0);
const codeContext = this.getCodeContext(content, match.index || 0);
findings.push({
type: result.rule.type,
category: result.rule.category,
severity: result.rule.severity,
title: result.rule.name,
description: result.rule.description,
file: filename,
line,
column,
code: codeContext.matchedCode,
codeContext: {
before: codeContext.contextBefore,
after: codeContext.contextAfter
},
recommendation: result.rule.recommendation,
confidence: result.rule.confidence,
cwe: result.rule.cwe,
owasp: result.rule.owasp,
impact: result.rule.impact,
effort: result.rule.effort
});
}
}
return findings;
}
getCodeContext(content, index) {
const lines = content.split('\n');
const currentLineIndex = this.getLineNumber(content, index) - 1;
const contextBefore = currentLineIndex > 0 ? lines[currentLineIndex - 1] : '';
const currentLine = lines[currentLineIndex] || '';
const contextAfter = currentLineIndex < lines.length - 1 ? lines[currentLineIndex + 1] : '';
return {
matchedCode: currentLine.trim(),
contextBefore: contextBefore.trim(),
contextAfter: contextAfter.trim()
};
}
buildFolderStructure(fileAnalyses, basePath) {
const folderMap = new Map();
// Initialize root folder
const rootFolder = {
folderPath: basePath,
files: [],
subFolders: [],
summary: { critical: 0, high: 0, medium: 0, low: 0, total: 0, filesScanned: 0, filesWithIssues: 0 }
};
folderMap.set(basePath, rootFolder);
// Process each file
for (const fileAnalysis of fileAnalyses) {
const filePath = path.resolve(basePath, fileAnalysis.relativePath);
const folderPath = path.dirname(filePath);
// Create folder structure if it doesn't exist
this.ensureFolderExists(folderMap, folderPath, basePath);
// Add file to its folder
const folder = folderMap.get(folderPath);
folder.files.push(fileAnalysis);
// Update folder statistics
this.updateFolderSummary(folder, fileAnalysis);
}
// Build folder hierarchy
this.buildFolderHierarchy(folderMap, basePath);
return rootFolder;
}
ensureFolderExists(folderMap, folderPath, basePath) {
if (folderMap.has(folderPath))
return;
const folder = {
folderPath,
files: [],
subFolders: [],
summary: { critical: 0, high: 0, medium: 0, low: 0, total: 0, filesScanned: 0, filesWithIssues: 0 }
};
folderMap.set(folderPath, folder);
// Recursively create parent folders
const parentPath = path.dirname(folderPath);
if (parentPath !== folderPath && parentPath.startsWith(basePath)) {
this.ensureFolderExists(folderMap, parentPath, basePath);
}
}
buildFolderHierarchy(folderMap, basePath) {
for (const [folderPath, folder] of folderMap) {
if (folderPath === basePath)
continue;
const parentPath = path.dirname(folderPath);
const parent = folderMap.get(parentPath);
if (parent && !parent.subFolders.includes(folder)) {
parent.subFolders.push(folder);
// Update parent summary with child folder data
parent.summary.critical += folder.summary.critical;
parent.summary.high += folder.summary.high;
parent.summary.medium += folder.summary.medium;
parent.summary.low += folder.summary.low;
parent.summary.total += folder.summary.total;
parent.summary.filesScanned += folder.summary.filesScanned;
parent.summary.filesWithIssues += folder.summary.filesWithIssues;
}
}
}
updateFolderSummary(folder, fileAnalysis) {
folder.summary.filesScanned++;
if (fileAnalysis.findings.length > 0) {
folder.summary.filesWithIssues++;
}
folder.summary.critical += fileAnalysis.summary.critical;
folder.summary.high += fileAnalysis.summary.high;
folder.summary.medium += fileAnalysis.summary.medium;
folder.summary.low += fileAnalysis.summary.low;
folder.summary.total += fileAnalysis.summary.total;
}
countFolders(folder) {
let count = 1;
for (const subFolder of folder.subFolders) {
count += this.countFolders(subFolder);
}
return count;
}
filterBySeverity(findings, minSeverity) {
if (!minSeverity)
return findings;
const severityLevels = { low: 0, medium: 1, high: 2, critical: 3 };
const minLevel = severityLevels[minSeverity];
return findings.filter(finding => severityLevels[finding.severity] >= minLevel);
}
generateSummary(findings) {
const summary = { critical: 0, high: 0, medium: 0, low: 0, total: findings.length };
for (const finding of findings) {
summary[finding.severity]++;
}
return summary;
}
generateCategorySummary(findings) {
const categorySummary = {};
for (const finding of findings) {
const category = finding.category || finding.type;
categorySummary[category] = (categorySummary[category] || 0) + 1;
}
return categorySummary;
}
calculateAverageConfidence(findings) {
if (findings.length === 0)
return 0;
const totalConfidence = findings.reduce((sum, finding) => sum + finding.confidence, 0);
return Math.round((totalConfidence / findings.length) * 100) / 100;
}
isBinaryFile(buffer, filename) {
// Check for common binary file signatures
const signatures = [
[0xFF, 0xD8, 0xFF], // JPEG
[0x89, 0x50, 0x4E, 0x47], // PNG
[0x47, 0x49, 0x46], // GIF
[0x25, 0x50, 0x44, 0x46], // PDF
[0x50, 0x4B], // ZIP/Office files
[0x4D, 0x5A] // Windows executable
];
// Check file signatures
for (const sig of signatures) {
if (buffer.length >= sig.length) {
let matches = true;
for (let i = 0; i < sig.length; i++) {
if (buffer[i] !== sig[i]) {
matches = false;
break;
}
}
if (matches)
return true;
}
}
// Check for high percentage of non-printable characters in first 1KB
const sampleSize = Math.min(1024, buffer.length);
let nonPrintableCount = 0;
for (let i = 0; i < sampleSize; i++) {
const byte = buffer[i];
// Count bytes that are not printable ASCII or common whitespace
if (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13) {
nonPrintableCount++;
}
else if (byte > 126) {
nonPrintableCount++;
}
}
// If more than 30% non-printable, likely binary
return (nonPrintableCount / sampleSize) > 0.3;
}
calculateContextAwareConfidence(baseConfidence, matchedCode, contextBefore, contextAfter, filename) {
let confidence = baseConfidence;
// Reduce confidence for test files
if (filename.includes('test') || filename.includes('spec') || filename.includes('mock')) {
confidence *= 0.7;
}
// Reduce confidence for example/demo files
if (filename.includes('example') || filename.includes('demo') || filename.includes('sample')) {
confidence *= 0.6;
}
// Increase confidence if in config/environment files
if (filename.includes('config') || filename.includes('.env') || filename.includes('settings')) {
confidence *= 1.2;
}
// Look for context indicators that suggest it's a real secret
const secretIndicators = ['api_key', 'secret', 'token', 'password', 'credential', 'auth'];
const testIndicators = ['example', 'test', 'dummy', 'fake', 'mock', 'placeholder'];
const fullContext = (contextBefore + ' ' + matchedCode + ' ' + contextAfter).toLowerCase();
// Increase confidence if secret indicators are present
for (const indicator of secretIndicators) {
if (fullContext.includes(indicator)) {
confidence *= 1.1;
break;
}
}
// Decrease confidence if test indicators are present
for (const indicator of testIndicators) {
if (fullContext.includes(indicator)) {
confidence *= 0.5;
break;
}
}
// Reduce confidence for very short or very common patterns
if (matchedCode.length < 10) {
confidence *= 0.8;
}
// Check for common placeholder patterns
if (/^[a-z]{1,3}$|^test|^example|^placeholder|^your-|^my-/.test(matchedCode.toLowerCase())) {
confidence *= 0.3;
}
return Math.min(1.0, Math.max(0.0, confidence));
}
}
exports.SecurityScanner = SecurityScanner;
//# sourceMappingURL=scanner.js.map