@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
1,208 lines • 56.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;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecurityScanner = void 0;
exports.createSecurityRule = createSecurityRule;
exports.scanSecurity = scanSecurity;
const events_1 = require("events");
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const child_process_1 = require("child_process");
const fast_glob_1 = __importDefault(require("fast-glob"));
class SecurityScanner extends events_1.EventEmitter {
constructor(config) {
super();
this.config = {
scanners: [
{ name: 'npm-audit', enabled: true },
{ name: 'eslint-security', enabled: true }
],
generateReport: true,
outputPath: './security-reports',
includePatterns: ['**/*.{js,ts,jsx,tsx,json,yml,yaml,dockerfile}'],
excludePatterns: ['**/node_modules/**', '**/dist/**', '**/coverage/**'],
thresholds: {
critical: 0,
high: 0,
medium: 5,
low: 10,
total: 20
},
...config
};
}
async scan(projectPath) {
this.emit('scan:start', { project: projectPath });
const result = {
projectPath,
timestamp: new Date(),
summary: this.createEmptySummary(),
vulnerabilities: [],
dependencies: this.createEmptyDependencyAnalysis(),
codeAnalysis: this.createEmptyCodeAnalysis(),
configurationAnalysis: this.createEmptyConfigAnalysis(),
scanners: [],
metrics: this.createEmptyMetrics()
};
try {
// Run dependency analysis
result.dependencies = await this.analyzeDependencies(projectPath);
// Run code security analysis
result.codeAnalysis = await this.analyzeCode(projectPath);
// Run configuration analysis
result.configurationAnalysis = await this.analyzeConfiguration(projectPath);
// Run external scanners
for (const scanner of this.config.scanners.filter(s => s.enabled)) {
const scannerResult = await this.runScanner(scanner, projectPath);
result.scanners.push(scannerResult);
}
// Aggregate vulnerabilities
result.vulnerabilities = await this.aggregateVulnerabilities(result);
// Calculate metrics
result.metrics = this.calculateSecurityMetrics(result);
result.summary = this.generateSummary(result);
// Calculate trends if historical data exists
result.trends = await this.calculateTrends(result);
this.emit('scan:complete', result);
return result;
}
catch (error) {
this.emit('scan:error', error);
throw error;
}
}
async analyzeDependencies(projectPath) {
this.emit('analyze:dependencies:start');
const analysis = {
totalDependencies: 0,
vulnerableDependencies: 0,
outdatedDependencies: 0,
licenseIssues: [],
dependencyTree: [],
recommendations: []
};
try {
// Read package.json
const packageJsonPath = path.join(projectPath, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
const deps = {
...packageJson.dependencies || {},
...packageJson.devDependencies || {}
};
analysis.totalDependencies = Object.keys(deps).length;
// Run npm audit
try {
const auditOutput = (0, child_process_1.execSync)('npm audit --json', {
cwd: projectPath,
encoding: 'utf-8',
stdio: 'pipe'
});
const auditData = JSON.parse(auditOutput);
analysis.vulnerableDependencies = Object.keys(auditData.vulnerabilities || {}).length;
// Process vulnerabilities
for (const [name, vuln] of Object.entries(auditData.vulnerabilities || {})) {
const vulnData = vuln;
if (vulnData.severity === 'high' || vulnData.severity === 'critical') {
analysis.recommendations.push({
dependency: name,
currentVersion: vulnData.range || 'unknown',
recommendedVersion: vulnData.fixAvailable?.name || 'latest',
reason: `Security vulnerability: ${vulnData.title}`,
urgency: vulnData.severity === 'critical' ? 'critical' : 'high'
});
}
}
}
catch (auditError) {
this.emit('audit:error', auditError);
}
// Check for outdated dependencies
try {
const outdatedOutput = (0, child_process_1.execSync)('npm outdated --json', {
cwd: projectPath,
encoding: 'utf-8',
stdio: 'pipe'
});
const outdatedData = JSON.parse(outdatedOutput);
analysis.outdatedDependencies = Object.keys(outdatedData).length;
}
catch (outdatedError) {
// npm outdated returns non-zero exit code when outdated packages found
if (outdatedError.stdout) {
try {
const outdatedData = JSON.parse(outdatedError.stdout);
analysis.outdatedDependencies = Object.keys(outdatedData).length;
}
catch {
// Ignore parsing errors
}
}
}
// Analyze licenses
analysis.licenseIssues = await this.analyzeLicenses(deps);
}
}
catch (error) {
this.emit('dependencies:error', error);
}
return analysis;
}
async analyzeLicenses(dependencies) {
const issues = [];
// Define problematic licenses
const problematicLicenses = [
'GPL-2.0', 'GPL-3.0', 'AGPL-1.0', 'AGPL-3.0',
'CPAL-1.0', 'EPL-1.0', 'EPL-2.0'
];
// This would typically integrate with a license checking service
// For now, we'll use a simplified approach
for (const [name, version] of Object.entries(dependencies)) {
// Mock license checking
const mockLicense = Math.random() > 0.95 ? 'GPL-3.0' : 'MIT';
if (problematicLicenses.includes(mockLicense)) {
issues.push({
dependency: name,
version,
license: mockLicense,
risk: 'high',
reason: 'Copyleft license may require code disclosure'
});
}
}
return issues;
}
async analyzeCode(projectPath) {
this.emit('analyze:code:start');
const analysis = {
totalFiles: 0,
vulnerableFiles: 0,
patterns: [],
hotspots: [],
recommendations: []
};
try {
// Find source files
const files = await (0, fast_glob_1.default)(this.config.includePatterns, {
cwd: projectPath,
absolute: true,
ignore: this.config.excludePatterns
});
analysis.totalFiles = files.length;
// Analyze each file
for (const file of files) {
const fileAnalysis = await this.analyzeSourceFile(file);
if (fileAnalysis.vulnerabilities.length > 0) {
analysis.vulnerableFiles++;
for (const vuln of fileAnalysis.vulnerabilities) {
analysis.hotspots.push({
file: path.relative(projectPath, file),
line: vuln.line,
category: vuln.category,
severity: vuln.severity,
description: vuln.message,
remediation: vuln.remediation || 'Review and fix security issue'
});
}
}
}
// Identify common patterns
analysis.patterns = this.identifySecurityPatterns(analysis.hotspots);
analysis.recommendations = this.generateCodeRecommendations(analysis);
}
catch (error) {
this.emit('code:error', error);
}
return analysis;
}
async analyzeSourceFile(filePath) {
const content = await fs.readFile(filePath, 'utf-8');
const vulnerabilities = [];
// Apply built-in security rules
const builtInRules = this.getBuiltInSecurityRules();
for (const rule of builtInRules) {
const fileContent = {
path: filePath,
content,
type: this.getFileType(filePath),
language: this.getLanguage(filePath)
};
const violations = rule.check(fileContent);
vulnerabilities.push(...violations);
}
// Apply custom rules
if (this.config.customRules) {
for (const rule of this.config.customRules) {
const fileContent = {
path: filePath,
content,
type: this.getFileType(filePath),
language: this.getLanguage(filePath)
};
const violations = rule.check(fileContent);
vulnerabilities.push(...violations);
}
}
return { vulnerabilities };
}
getBuiltInSecurityRules() {
return [
{
id: 'hardcoded-password',
name: 'Hardcoded Password',
description: 'Detects hardcoded passwords in source code',
severity: 'high',
category: 'authentication',
cwe: 'CWE-798',
pattern: /password\s*[=:]\s*["'][^"']+["']/i,
check: (file) => {
const violations = [];
const lines = file.content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/password\s*[=:]\s*["'][^"']+["']/i.test(line)) {
violations.push({
line: i + 1,
message: 'Hardcoded password detected',
severity: 'high',
category: 'authentication',
cwe: 'CWE-798',
evidence: line.trim(),
remediation: 'Use environment variables or secure configuration for passwords'
});
}
}
return violations;
},
remediation: 'Use environment variables or secure configuration management'
},
{
id: 'sql-injection',
name: 'SQL Injection',
description: 'Detects potential SQL injection vulnerabilities',
severity: 'critical',
category: 'injection',
cwe: 'CWE-89',
check: (file) => {
const violations = [];
const lines = file.content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Look for string concatenation in SQL queries
if (/(?:SELECT|INSERT|UPDATE|DELETE).*\+.*\$\{/i.test(line)) {
violations.push({
line: i + 1,
message: 'Potential SQL injection vulnerability',
severity: 'critical',
category: 'injection',
cwe: 'CWE-89',
evidence: line.trim(),
remediation: 'Use parameterized queries or prepared statements'
});
}
}
return violations;
},
remediation: 'Use parameterized queries, prepared statements, or ORM frameworks'
},
{
id: 'xss-vulnerability',
name: 'Cross-Site Scripting (XSS)',
description: 'Detects potential XSS vulnerabilities',
severity: 'high',
category: 'xss',
cwe: 'CWE-79',
check: (file) => {
const violations = [];
const lines = file.content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Look for direct innerHTML usage with user input
if (/innerHTML\s*=.*\$\{/i.test(line)) {
violations.push({
line: i + 1,
message: 'Potential XSS vulnerability in innerHTML usage',
severity: 'high',
category: 'xss',
cwe: 'CWE-79',
evidence: line.trim(),
remediation: 'Use textContent or proper escaping for user input'
});
}
}
return violations;
},
remediation: 'Sanitize user input and use safe DOM manipulation methods'
},
{
id: 'insecure-random',
name: 'Insecure Random Number Generation',
description: 'Detects use of insecure random number generators',
severity: 'medium',
category: 'cryptography',
cwe: 'CWE-338',
check: (file) => {
const violations = [];
const lines = file.content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/Math\.random\(\)/.test(line) && /password|token|salt|key/i.test(line)) {
violations.push({
line: i + 1,
message: 'Insecure random number generation for security-sensitive value',
severity: 'medium',
category: 'cryptography',
cwe: 'CWE-338',
evidence: line.trim(),
remediation: 'Use crypto.randomBytes() for cryptographically secure random values'
});
}
}
return violations;
},
remediation: 'Use cryptographically secure random number generators'
}
];
}
getFileType(filePath) {
const filename = path.basename(filePath).toLowerCase();
if (filename === 'package.json' || filename === 'dockerfile' || filename.endsWith('.yml') || filename.endsWith('.yaml')) {
return 'config';
}
if (filename.includes('build') || filename.includes('webpack') || filename.includes('rollup')) {
return 'build';
}
if (filename === 'package-lock.json' || filename === 'yarn.lock') {
return 'dependency';
}
return 'source';
}
getLanguage(filePath) {
const ext = path.extname(filePath).toLowerCase();
const languageMap = {
'.js': 'javascript',
'.jsx': 'javascript',
'.ts': 'typescript',
'.tsx': 'typescript',
'.py': 'python',
'.java': 'java',
'.go': 'go',
'.rs': 'rust',
'.php': 'php',
'.rb': 'ruby',
'.cs': 'csharp',
'.cpp': 'cpp',
'.c': 'c'
};
return languageMap[ext] || 'unknown';
}
identifySecurityPatterns(hotspots) {
const patterns = new Map();
for (const hotspot of hotspots) {
const key = `${hotspot.category}-${hotspot.severity}`;
if (patterns.has(key)) {
const pattern = patterns.get(key);
pattern.occurrences++;
pattern.files.push(hotspot.file);
}
else {
patterns.set(key, {
pattern: `${hotspot.category} vulnerabilities`,
category: hotspot.category,
occurrences: 1,
files: [hotspot.file],
severity: hotspot.severity
});
}
}
return Array.from(patterns.values()).sort((a, b) => b.occurrences - a.occurrences);
}
generateCodeRecommendations(analysis) {
const recommendations = [];
if (analysis.vulnerableFiles > 0) {
recommendations.push(`Review ${analysis.vulnerableFiles} files with security vulnerabilities`);
}
const criticalHotspots = analysis.hotspots.filter(h => h.severity === 'critical');
if (criticalHotspots.length > 0) {
recommendations.push(`Address ${criticalHotspots.length} critical security issues immediately`);
}
const injectionIssues = analysis.hotspots.filter(h => h.category === 'injection');
if (injectionIssues.length > 0) {
recommendations.push('Implement input validation and parameterized queries to prevent injection attacks');
}
const xssIssues = analysis.hotspots.filter(h => h.category === 'xss');
if (xssIssues.length > 0) {
recommendations.push('Implement proper output encoding to prevent XSS vulnerabilities');
}
return recommendations;
}
async analyzeConfiguration(projectPath) {
this.emit('analyze:config:start');
const analysis = {
files: [],
issues: [],
recommendations: []
};
try {
// Find configuration files
const configPatterns = [
'package.json',
'Dockerfile',
'*.yml',
'*.yaml',
'.env*',
'nginx.conf',
'webpack.config.*',
'tsconfig.json'
];
const configFiles = await (0, fast_glob_1.default)(configPatterns, {
cwd: projectPath,
absolute: true,
ignore: this.config.excludePatterns
});
for (const file of configFiles) {
const configFile = await this.analyzeConfigFile(file, projectPath);
analysis.files.push(configFile);
analysis.issues.push(...configFile.issues);
}
analysis.recommendations = this.generateConfigRecommendations(analysis);
}
catch (error) {
this.emit('config:error', error);
}
return analysis;
}
async analyzeConfigFile(filePath, projectPath) {
const content = await fs.readFile(filePath, 'utf-8');
const relativePath = path.relative(projectPath, filePath);
const configFile = {
path: relativePath,
type: this.getConfigFileType(filePath),
issues: [],
securityScore: 100
};
// Analyze based on file type
switch (configFile.type) {
case 'package.json':
configFile.issues = this.analyzePackageJson(content, relativePath);
break;
case 'dockerfile':
configFile.issues = this.analyzeDockerfile(content, relativePath);
break;
case 'env':
configFile.issues = this.analyzeEnvFile(content, relativePath);
break;
default:
configFile.issues = this.analyzeGenericConfig(content, relativePath);
}
// Calculate security score
const criticalIssues = configFile.issues.filter(i => i.severity === 'critical').length;
const highIssues = configFile.issues.filter(i => i.severity === 'high').length;
const mediumIssues = configFile.issues.filter(i => i.severity === 'medium').length;
configFile.securityScore = Math.max(0, 100 - (criticalIssues * 30 + highIssues * 20 + mediumIssues * 10));
return configFile;
}
getConfigFileType(filePath) {
const filename = path.basename(filePath).toLowerCase();
if (filename === 'package.json')
return 'package.json';
if (filename === 'dockerfile' || filename.startsWith('dockerfile.'))
return 'dockerfile';
if (filename.startsWith('.env'))
return 'env';
if (filename.includes('nginx'))
return 'nginx';
if (filename.includes('ci') || filename.includes('workflow'))
return 'ci';
return 'other';
}
analyzePackageJson(content, filePath) {
const issues = [];
try {
const packageJson = JSON.parse(content);
// Check for dependencies with known vulnerabilities
if (packageJson.dependencies) {
for (const [dep, version] of Object.entries(packageJson.dependencies)) {
if (typeof version === 'string' && version.includes('*')) {
issues.push({
file: filePath,
setting: `dependencies.${dep}`,
issue: 'Wildcard version dependency',
severity: 'medium',
remediation: 'Use specific version ranges to avoid unexpected updates'
});
}
}
}
// Check scripts for potentially dangerous commands
if (packageJson.scripts) {
for (const [script, command] of Object.entries(packageJson.scripts)) {
if (typeof command === 'string' && /rm\s+-rf|sudo|curl.*\|/.test(command)) {
issues.push({
file: filePath,
setting: `scripts.${script}`,
issue: 'Potentially dangerous script command',
severity: 'high',
remediation: 'Review script for security implications'
});
}
}
}
}
catch (error) {
issues.push({
file: filePath,
setting: 'JSON syntax',
issue: 'Invalid JSON format',
severity: 'medium',
remediation: 'Fix JSON syntax errors'
});
}
return issues;
}
analyzeDockerfile(content, filePath) {
const issues = [];
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const lineNum = i + 1;
// Check for running as root
if (/^USER\s+root$/i.test(line)) {
issues.push({
file: filePath,
line: lineNum,
setting: 'USER',
issue: 'Running container as root user',
severity: 'high',
remediation: 'Create and use a non-root user'
});
}
// Check for --privileged flag
if (/--privileged/.test(line)) {
issues.push({
file: filePath,
line: lineNum,
setting: 'privileged',
issue: 'Using privileged mode',
severity: 'critical',
remediation: 'Avoid privileged mode unless absolutely necessary'
});
}
// Check for ADD with remote URLs
if (/^ADD\s+https?:\/\//.test(line)) {
issues.push({
file: filePath,
line: lineNum,
setting: 'ADD',
issue: 'Using ADD with remote URL',
severity: 'medium',
remediation: 'Use COPY for local files or RUN with curl for remote content'
});
}
}
return issues;
}
analyzeEnvFile(content, filePath) {
const issues = [];
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const lineNum = i + 1;
// Check for hardcoded secrets
if (/(?:password|secret|key|token)\s*=.*[^=\s]/i.test(line)) {
issues.push({
file: filePath,
line: lineNum,
setting: 'environment variable',
issue: 'Potential hardcoded secret in environment file',
severity: 'high',
remediation: 'Use placeholder values and set real secrets at runtime'
});
}
}
return issues;
}
analyzeGenericConfig(content, filePath) {
const issues = [];
// Basic checks for any configuration file
if (/password\s*[:=]\s*[^#\s]/i.test(content)) {
issues.push({
file: filePath,
setting: 'configuration',
issue: 'Potential hardcoded password',
severity: 'high',
remediation: 'Use external configuration or environment variables'
});
}
return issues;
}
generateConfigRecommendations(analysis) {
const recommendations = [];
const criticalIssues = analysis.issues.filter(i => i.severity === 'critical');
if (criticalIssues.length > 0) {
recommendations.push(`Fix ${criticalIssues.length} critical configuration security issues`);
}
const dockerIssues = analysis.issues.filter(i => i.file.toLowerCase().includes('dockerfile'));
if (dockerIssues.length > 0) {
recommendations.push('Review Docker configuration for security best practices');
}
const envIssues = analysis.issues.filter(i => i.file.includes('.env'));
if (envIssues.length > 0) {
recommendations.push('Review environment files for hardcoded secrets');
}
return recommendations;
}
async runScanner(scanner, projectPath) {
this.emit('scanner:start', { name: scanner.name });
const result = {
scanner: scanner.name,
success: false,
duration: 0,
vulnerabilities: 0,
errors: []
};
const startTime = Date.now();
try {
switch (scanner.name) {
case 'npm-audit':
await this.runNpmAudit(projectPath, result);
break;
case 'snyk':
await this.runSnyk(projectPath, result);
break;
case 'semgrep':
await this.runSemgrep(projectPath, result);
break;
case 'eslint-security':
await this.runESLintSecurity(projectPath, result);
break;
default:
result.errors = [`Unknown scanner: ${scanner.name}`];
}
result.success = result.errors?.length === 0;
result.duration = Date.now() - startTime;
}
catch (error) {
result.errors = [error.message];
result.duration = Date.now() - startTime;
}
this.emit('scanner:complete', result);
return result;
}
async runNpmAudit(projectPath, result) {
try {
const output = (0, child_process_1.execSync)('npm audit --json', {
cwd: projectPath,
encoding: 'utf-8',
stdio: 'pipe'
});
const auditData = JSON.parse(output);
result.vulnerabilities = Object.keys(auditData.vulnerabilities || {}).length;
result.rawOutput = output;
}
catch (error) {
if (error.stdout) {
try {
const auditData = JSON.parse(error.stdout);
result.vulnerabilities = Object.keys(auditData.vulnerabilities || {}).length;
result.rawOutput = error.stdout;
}
catch {
result.errors = ['Failed to parse npm audit output'];
}
}
else {
result.errors = ['npm audit command failed'];
}
}
}
async runSnyk(projectPath, result) {
try {
const output = (0, child_process_1.execSync)('snyk test --json', {
cwd: projectPath,
encoding: 'utf-8',
stdio: 'pipe'
});
const snykData = JSON.parse(output);
result.vulnerabilities = snykData.vulnerabilities?.length || 0;
result.rawOutput = output;
}
catch (error) {
result.errors = ['Snyk not available or authentication required'];
}
}
async runSemgrep(projectPath, result) {
try {
const output = (0, child_process_1.execSync)('semgrep --config=auto --json', {
cwd: projectPath,
encoding: 'utf-8',
stdio: 'pipe'
});
const semgrepData = JSON.parse(output);
result.vulnerabilities = semgrepData.results?.length || 0;
result.rawOutput = output;
}
catch (error) {
result.errors = ['Semgrep not available'];
}
}
async runESLintSecurity(projectPath, result) {
try {
const output = (0, child_process_1.execSync)('npx eslint . --ext .js,.ts,.jsx,.tsx --format json', {
cwd: projectPath,
encoding: 'utf-8',
stdio: 'pipe'
});
const eslintData = JSON.parse(output);
let securityIssues = 0;
for (const file of eslintData) {
for (const message of file.messages) {
if (message.ruleId && message.ruleId.includes('security')) {
securityIssues++;
}
}
}
result.vulnerabilities = securityIssues;
result.rawOutput = output;
}
catch (error) {
if (error.stdout) {
result.rawOutput = error.stdout;
}
// ESLint returns non-zero exit code when issues found
}
}
async aggregateVulnerabilities(scanResult) {
const vulnerabilities = [];
// Convert code analysis hotspots to vulnerabilities
for (const hotspot of scanResult.codeAnalysis.hotspots) {
vulnerabilities.push({
id: `code-${vulnerabilities.length}`,
title: hotspot.description,
description: hotspot.description,
severity: hotspot.severity,
category: hotspot.category,
affected: [{
type: 'code',
name: hotspot.file,
path: hotspot.file
}],
discovered: new Date(),
source: 'static-analysis',
references: [],
remediation: {
priority: hotspot.severity === 'critical' ? 'immediate' :
hotspot.severity === 'high' ? 'high' : 'medium',
effort: '1-2h',
steps: [hotspot.remediation],
automatedFix: false
}
});
}
// Convert configuration issues to vulnerabilities
for (const issue of scanResult.configurationAnalysis.issues) {
if (issue.severity === 'critical' || issue.severity === 'high') {
vulnerabilities.push({
id: `config-${vulnerabilities.length}`,
title: issue.issue,
description: issue.issue,
severity: issue.severity,
category: 'configuration',
affected: [{
type: 'configuration',
name: issue.file,
path: issue.file
}],
discovered: new Date(),
source: 'config-analysis',
references: [],
remediation: {
priority: issue.severity === 'critical' ? 'immediate' : 'high',
effort: '30min',
steps: [issue.remediation],
automatedFix: false
}
});
}
}
return vulnerabilities;
}
calculateSecurityMetrics(scanResult) {
const vulnerabilities = scanResult.vulnerabilities;
const riskDistribution = {
critical: 0,
high: 0,
medium: 0,
low: 0,
info: 0
};
const categoryDistribution = {
injection: 0,
authentication: 0,
authorization: 0,
cryptography: 0,
configuration: 0,
'sensitive-data': 0,
dependency: 0,
xss: 0,
csrf: 0,
xxe: 0,
deserialization: 0,
other: 0
};
for (const vuln of vulnerabilities) {
riskDistribution[vuln.severity]++;
categoryDistribution[vuln.category]++;
}
return {
riskDistribution,
categoryDistribution,
coverageMetrics: {
codeScanned: scanResult.codeAnalysis.totalFiles,
dependenciesScanned: scanResult.dependencies.totalDependencies,
configurationScanned: scanResult.configurationAnalysis.files.length,
totalCoverage: 85 // Mock coverage percentage
},
complianceMetrics: {
owasp: {
top10Coverage: this.calculateOWASPCoverage(vulnerabilities),
issuesFound: this.getOWASPIssues(vulnerabilities)
},
cwe: {
top25Coverage: this.calculateCWECoverage(vulnerabilities),
issuesFound: this.getCWEIssues(vulnerabilities)
},
pci: vulnerabilities.filter(v => v.category === 'sensitive-data').length === 0,
hipaa: vulnerabilities.filter(v => v.category === 'sensitive-data').length === 0,
gdpr: vulnerabilities.filter(v => v.category === 'sensitive-data').length === 0
}
};
}
calculateOWASPCoverage(vulnerabilities) {
const owaspCategories = ['injection', 'authentication', 'sensitive-data', 'xxe', 'authorization', 'configuration', 'xss', 'deserialization', 'dependency'];
const foundCategories = new Set(vulnerabilities.map(v => v.category));
const coveredCategories = owaspCategories.filter(cat => foundCategories.has(cat));
return (coveredCategories.length / owaspCategories.length) * 100;
}
getOWASPIssues(vulnerabilities) {
const owaspMap = {
injection: 'A03:2021 - Injection',
authentication: 'A07:2021 - Identification and Authentication Failures',
'sensitive-data': 'A02:2021 - Cryptographic Failures',
xss: 'A03:2021 - Injection',
authorization: 'A01:2021 - Broken Access Control',
configuration: 'A05:2021 - Security Misconfiguration'
};
const issues = {};
for (const vuln of vulnerabilities) {
const owaspCategory = owaspMap[vuln.category];
if (owaspCategory) {
issues[owaspCategory] = (issues[owaspCategory] || 0) + 1;
}
}
return Object.entries(issues).map(([category, count]) => ({ category, count }));
}
calculateCWECoverage(vulnerabilities) {
const cweCount = vulnerabilities.filter(v => v.cwe).length;
return vulnerabilities.length > 0 ? (cweCount / vulnerabilities.length) * 100 : 0;
}
getCWEIssues(vulnerabilities) {
const cweMap = {};
for (const vuln of vulnerabilities) {
if (vuln.cwe) {
cweMap[vuln.cwe] = (cweMap[vuln.cwe] || 0) + 1;
}
}
return Object.entries(cweMap).map(([cwe, count]) => ({ cwe, count }));
}
generateSummary(scanResult) {
const vulnerabilities = scanResult.vulnerabilities;
const summary = {
totalVulnerabilities: vulnerabilities.length,
criticalCount: vulnerabilities.filter(v => v.severity === 'critical').length,
highCount: vulnerabilities.filter(v => v.severity === 'high').length,
mediumCount: vulnerabilities.filter(v => v.severity === 'medium').length,
lowCount: vulnerabilities.filter(v => v.severity === 'low').length,
riskScore: this.calculateRiskScore(vulnerabilities),
complianceScore: this.calculateComplianceScore(scanResult.metrics),
recommendations: this.generateSummaryRecommendations(scanResult)
};
return summary;
}
calculateRiskScore(vulnerabilities) {
let score = 0;
for (const vuln of vulnerabilities) {
switch (vuln.severity) {
case 'critical':
score += 10;
break;
case 'high':
score += 7;
break;
case 'medium':
score += 4;
break;
case 'low':
score += 1;
break;
}
}
// Normalize to 0-100 scale (100 being highest risk)
return Math.min(100, score);
}
calculateComplianceScore(metrics) {
let score = 100;
// Deduct points for compliance failures
if (!metrics.complianceMetrics.pci)
score -= 20;
if (!metrics.complianceMetrics.hipaa)
score -= 15;
if (!metrics.complianceMetrics.gdpr)
score -= 15;
// Deduct points based on OWASP coverage
if (metrics.complianceMetrics.owasp.top10Coverage < 80) {
score -= (80 - metrics.complianceMetrics.owasp.top10Coverage) / 2;
}
return Math.max(0, score);
}
generateSummaryRecommendations(scanResult) {
const recommendations = [];
if (scanResult.summary.criticalCount > 0) {
recommendations.push(`Address ${scanResult.summary.criticalCount} critical vulnerabilities immediately`);
}
if (scanResult.dependencies.vulnerableDependencies > 0) {
recommendations.push(`Update ${scanResult.dependencies.vulnerableDependencies} vulnerable dependencies`);
}
if (scanResult.codeAnalysis.vulnerableFiles > 0) {
recommendations.push(`Review ${scanResult.codeAnalysis.vulnerableFiles} files with security issues`);
}
const configIssues = scanResult.configurationAnalysis.issues.filter(i => i.severity === 'critical' || i.severity === 'high');
if (configIssues.length > 0) {
recommendations.push(`Fix ${configIssues.length} configuration security issues`);
}
return recommendations;
}
async calculateTrends(scanResult) {
// Load historical data and calculate trends
// This would typically involve comparing with previous scan results
return [];
}
async generateReport(scanResult) {
const actionPlan = this.generateActionPlan(scanResult);
const compliance = this.generateComplianceReport(scanResult);
const trends = this.generateTrendAnalysis(scanResult);
const summary = {
riskLevel: this.determineRiskLevel(scanResult.summary.riskScore),
totalVulnerabilities: scanResult.summary.totalVulnerabilities,
criticalIssues: scanResult.summary.criticalCount,
complianceScore: scanResult.summary.complianceScore,
trend: 'stable',
lastScan: scanResult.timestamp
};
const report = {
summary,
scan: scanResult,
actionPlan,
compliance,
trends,
timestamp: new Date()
};
if (this.config.generateReport) {
await this.saveReport(report);
}
return report;
}
determineRiskLevel(riskScore) {
if (riskScore >= 70)
return 'critical';
if (riskScore >= 50)
return 'high';
if (riskScore >= 25)
return 'medium';
return 'low';
}
generateActionPlan(scanResult) {
const actionItems = [];
// Critical vulnerabilities first
const criticalVulns = scanResult.vulnerabilities.filter(v => v.severity === 'critical');
if (criticalVulns.length > 0) {
actionItems.push({
priority: 'critical',
category: 'other',
title: 'Fix Critical Vulnerabilities',
description: `Address ${criticalVulns.length} critical security vulnerabilities`,
effort: '1-2d',
impact: 'High',
vulnerabilities: criticalVulns.map(v => v.id),
automatedFix: false
});
}
// Dependency vulnerabilities
if (scanResult.dependencies.vulnerableDependencies > 0) {
actionItems.push({
priority: 'high',
category: 'dependency',
title: 'Update Vulnerable Dependencies',
description: `Update ${scanResult.dependencies.vulnerableDependencies} dependencies with known vulnerabilities`,
effort: '4-8h',
impact: 'Medium',
vulnerabilities: [],
automatedFix: true
});
}
// Configuration issues
const configIssues = scanResult.configurationAnalysis.issues.filter(i => i.severity === 'critical' || i.severity === 'high');
if (configIssues.length > 0) {
actionItems.push({
priority: 'medium',
category: 'configuration',
title: 'Fix Configuration Issues',
description: `Address ${configIssues.length} security configuration issues`,
effort: '2-4h',
impact: 'Medium',
vulnerabilities: [],
automatedFix: false
});
}
return actionItems;
}
generateComplianceReport(scanResult) {
const frameworks = [
{
name: 'OWASP Top 10',
score: scanResult.metrics.complianceMetrics.owasp.top10Coverage,
requirements: this.generateOWASPRequirements(scanResult)
},
{
name: 'CWE Top 25',
score: scanResult.metrics.complianceMetrics.cwe.top25Coverage,
requirements: this.generateCWERequirements(scanResult)
}
];
const overallScore = frameworks.reduce((sum, f) => sum + f.score, 0) / frameworks.length;
return {
frameworks,
overallScore,
gaps: this.identifyComplianceGaps(scanResult)
};
}
generateOWASPRequirements(scanResult) {
const requirements = [
{
id: 'A01',
description: 'Broken Access Control',
status: scanResult.vulnerabilities.some(v => v.category === 'authorization') ? 'non-compliant' : 'compliant',
issues: []
},
{
id: 'A02',
description: 'Cryptographic Failures',
status: scanResult.vulnerabilities.some(v => v.category === 'cryptography') ? 'non-compliant' : 'compliant',
issues: []
},
{
id: 'A03',
description: 'Injection',
status: scanResult.vulnerabilities.some(v => v.category === 'injection') ? 'non-compliant' : 'compliant',
issues: []
}
];
return requirements;
}
generateCWERequirements(scanResult) {
// Simplified CWE requirements
return [
{
id: 'CWE-79',
description: 'Cross-site Scripting',
status: scanResult.vulnerabilities.some(v => v.cwe === 'CWE-79') ? 'non-compliant' : 'compliant',
issues: []
},
{
id: 'CWE-89',
description: 'SQL Injection',
status: scanResult.vulnerabilities.some(v => v.cwe === 'CWE-89') ? 'non-compliant' : 'compliant',
issues: []
}
];
}
identifyComplianceGaps(scanResult) {
const gaps = [];
// Check for common compliance gaps
const injectionVulns = scanResult.vulnerabilities.filter(v => v.category === 'injection');
if (injectionVulns.length > 0) {
gaps.push({
framework: 'OWASP Top 10',
requirement: 'A03:2021 - Injection',
severity: 'high',
description: 'Application vulnerable to injection attacks',
remediation: 'Implement input validation and parameterized queries'
});
}
return gaps;
}
generateTrendAnalysis(scanResult) {
return {
historical: scanResult.trends || [],
predictions: [],
patterns: []
};
}
async saveReport(report) {
await fs.ensureDir(this.config.outputPath);
// Save JSON report
const jsonPath = path.join(this.config.outputPath, 'security-report.json');
await fs.writeJson(jsonPath, report, { spaces: 2 });
// Save HTML report
const htmlPath = path.join(this.config.outputPath, 'security-report.html');
const html = this.generateHtmlReport(report);
await fs.writeFile(htmlPath, html);
this.emit('report:saved', { json: jsonPath, html: htmlPath });
}
generateHtmlReport(report) {
return `<!DOCTYPE html>
<html>
<head>
<title>Security Scan Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #f44336; color: white; padding: 20px; border-radius: 5px; }
.summary { background: #f5f5f5; padding: 15px; border-radius: 5px; margin: 20px 0; }
.metric { display: inline-block; margin: 10px; padding: 10px; background: white; border-radius: 3px; }
.critical { color: #d32f2f; font-weight: bold; }
.high { color: #f57c00; fo