crapifyme
Version:
Ultra-fast developer productivity CLI tools - remove comments, logs, and more
367 lines • 16.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecurityScanner = void 0;
const child_process_1 = require("child_process");
const util_1 = require("util");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
class SecurityScanner {
constructor(cwd = process.cwd(), timeout = 60000) {
this.cwd = cwd;
this.timeout = timeout;
}
async scanVulnerabilities(packageManager) {
try {
let command;
let parser;
switch (packageManager.type) {
case 'npm':
command = 'npm audit --json';
parser = this.parseNpmAudit.bind(this);
break;
case 'yarn':
const isYarnV1 = packageManager.version.startsWith('1.');
command = isYarnV1 ? 'yarn audit --json --level moderate' : 'yarn audit --json';
parser = this.parseYarnAudit.bind(this);
break;
case 'pnpm':
command = 'pnpm audit --json';
parser = this.parsePnpmAudit.bind(this);
break;
default:
throw new Error(`Unsupported package manager: ${packageManager.type}`);
}
const { stdout, stderr } = await execAsync(command, {
cwd: this.cwd,
timeout: this.timeout,
maxBuffer: 10 * 1024 * 1024,
env: { ...process.env, NO_UPDATE_NOTIFIER: 'true' }
});
if (stdout.trim()) {
const result = parser(stdout);
return {
vulnerabilities: result.vulnerabilities,
summary: result.summary,
auditOutput: stdout
};
}
return {
vulnerabilities: [],
summary: { critical: 0, high: 0, moderate: 0, low: 0 }
};
}
catch (error) {
if (error.code === 1) {
try {
const result = this.parseAuditError(error.stdout || error.stderr, packageManager.type);
if (result.vulnerabilities.length > 0) {
return result;
}
}
catch (parseError) { }
}
// Only warn for actual command failures, not normal audit findings
if (error.code !== 1) {
if (packageManager.type === 'yarn' && error.message.includes('audit')) {
console.warn(`Note: Yarn v1 audit has limited compatibility. Security analysis skipped.`);
}
else {
console.warn(`Warning: Security audit failed: ${error.message}`);
}
}
return {
vulnerabilities: [],
summary: { critical: 0, high: 0, moderate: 0, low: 0 }
};
}
}
parseNpmAudit(output) {
const vulnerabilities = [];
let summary = { critical: 0, high: 0, moderate: 0, low: 0 };
try {
const auditData = JSON.parse(output);
if (auditData.auditReportVersion === 2 && auditData.vulnerabilities) {
for (const [packageName, vulnData] of Object.entries(auditData.vulnerabilities)) {
const vuln = vulnData;
if (vuln.via && Array.isArray(vuln.via)) {
for (const advisory of vuln.via) {
if (typeof advisory === 'object' && advisory.source) {
const vulnerability = {
id: advisory.source?.toString() || packageName,
packageName: packageName,
title: `${packageName}: ${advisory.title || 'Security vulnerability'}`,
description: advisory.overview || advisory.description || '',
severity: this.normalizeSeverity(advisory.severity),
references: advisory.references || [],
vulnerable_versions: advisory.range || vuln.range || '',
patched_versions: advisory.patched_versions,
recommendation: `Update ${packageName} to version ${vuln.fixAvailable ? vuln.fixAvailable.version || 'latest' : 'latest'}`
};
vulnerabilities.push(vulnerability);
switch (vulnerability.severity) {
case 'critical':
summary.critical++;
break;
case 'high':
summary.high++;
break;
case 'moderate':
summary.moderate++;
break;
case 'low':
summary.low++;
break;
}
}
}
}
}
}
else if (auditData.advisories) {
if (auditData.metadata?.vulnerabilities) {
summary = {
critical: auditData.metadata.vulnerabilities.critical || 0,
high: auditData.metadata.vulnerabilities.high || 0,
moderate: auditData.metadata.vulnerabilities.moderate || 0,
low: auditData.metadata.vulnerabilities.low || 0
};
}
for (const [id, advisory] of Object.entries(auditData.advisories)) {
const adv = advisory;
const packageName = adv.module_name || adv.package_name || 'unknown';
const vulnerability = {
id: id,
packageName: packageName,
title: `${packageName}: ${adv.title || 'Security vulnerability'}`,
description: adv.overview || adv.description || '',
severity: this.normalizeSeverity(adv.severity),
references: adv.references || [],
vulnerable_versions: adv.vulnerable_versions || adv.range || '',
patched_versions: adv.patched_versions,
recommendation: adv.recommendation || `Update ${packageName}`
};
vulnerabilities.push(vulnerability);
}
}
}
catch (error) {
console.warn(`Warning: Failed to parse npm audit output: ${error.message}`);
}
return { vulnerabilities, summary };
}
parseYarnAudit(output) {
const vulnerabilities = [];
let summary = { critical: 0, high: 0, moderate: 0, low: 0 };
try {
const lines = output.split('\n').filter(line => line.trim());
for (const line of lines) {
const data = JSON.parse(line);
if (data.type === 'auditSummary' && data.data) {
summary = {
critical: data.data.vulnerabilities?.critical || 0,
high: data.data.vulnerabilities?.high || 0,
moderate: data.data.vulnerabilities?.moderate || 0,
low: data.data.vulnerabilities?.low || 0
};
}
if (data.type === 'auditAdvisory' && data.data) {
const advisory = data.data.advisory;
const packageName = advisory.module_name || advisory.package_name || 'unknown';
const vulnerability = {
id: advisory.id?.toString() || '',
packageName: packageName,
title: advisory.title || 'Unknown vulnerability',
description: advisory.overview || advisory.description || '',
severity: this.normalizeSeverity(advisory.severity),
references: advisory.references || [],
vulnerable_versions: advisory.vulnerable_versions || advisory.range || '',
patched_versions: advisory.patched_versions,
recommendation: advisory.recommendation || this.generateRecommendation(advisory)
};
vulnerabilities.push(vulnerability);
}
}
}
catch (error) {
console.warn(`Warning: Failed to parse yarn audit output: ${error.message}`);
}
return { vulnerabilities, summary };
}
parsePnpmAudit(output) {
const vulnerabilities = [];
let summary = { critical: 0, high: 0, moderate: 0, low: 0 };
try {
const auditData = JSON.parse(output);
if (auditData.metadata?.vulnerabilities) {
summary = {
critical: auditData.metadata.vulnerabilities.critical || 0,
high: auditData.metadata.vulnerabilities.high || 0,
moderate: auditData.metadata.vulnerabilities.moderate || 0,
low: auditData.metadata.vulnerabilities.low || 0
};
}
if (auditData.advisories) {
for (const [id, advisory] of Object.entries(auditData.advisories)) {
const advisoryData = advisory;
const packageName = advisoryData.module_name || advisoryData.package_name || 'unknown';
const vulnerability = {
id: id,
packageName: packageName,
title: advisoryData.title || 'Unknown vulnerability',
description: advisoryData.overview || advisoryData.description || '',
severity: this.normalizeSeverity(advisoryData.severity),
references: advisoryData.references || [],
vulnerable_versions: advisoryData.vulnerable_versions || advisoryData.range || '',
patched_versions: advisoryData.patched_versions,
recommendation: advisoryData.recommendation || this.generateRecommendation(advisoryData)
};
vulnerabilities.push(vulnerability);
}
}
}
catch (error) {
console.warn(`Warning: Failed to parse pnpm audit output: ${error.message}`);
}
return { vulnerabilities, summary };
}
parseAuditError(output, pmType) {
try {
switch (pmType) {
case 'npm':
return this.parseNpmAudit(output);
case 'yarn':
return this.parseYarnAudit(output);
case 'pnpm':
return this.parsePnpmAudit(output);
default:
throw new Error(`Unsupported package manager: ${pmType}`);
}
}
catch (error) {
return {
vulnerabilities: [],
summary: { critical: 0, high: 0, moderate: 0, low: 0 }
};
}
}
normalizeSeverity(severity) {
const normalized = severity?.toLowerCase();
switch (normalized) {
case 'critical':
return 'critical';
case 'high':
return 'high';
case 'moderate':
case 'medium':
return 'moderate';
case 'low':
case 'info':
default:
return 'low';
}
}
generateRecommendation(vuln) {
if (vuln.patched_versions) {
return `Update to version ${vuln.patched_versions}`;
}
if (vuln.vulnerable_versions && vuln.vulnerable_versions.includes('<')) {
const match = vuln.vulnerable_versions.match(/< ?([0-9.]+)/);
if (match) {
return `Update to version ${match[1]} or higher`;
}
}
return 'Review package for security updates';
}
async checkPackageVulnerabilities(packageName, version) {
try {
const { realPackageName, realVersion } = this.parsePackageAlias(packageName, version);
const { stdout } = await execAsync(`npm view "${realPackageName}@${realVersion}" deprecated --json`, {
cwd: this.cwd,
timeout: 15000,
maxBuffer: 10 * 1024 * 1024,
env: { ...process.env, NO_UPDATE_NOTIFIER: 'true' }
});
const deprecatedMessage = stdout.trim();
if (deprecatedMessage && deprecatedMessage !== 'undefined' && deprecatedMessage !== 'null') {
const cleanMessage = deprecatedMessage.replace(/^"|"$/g, '');
return [
{
id: `deprecated-${packageName}`,
packageName: packageName,
title: `Package ${packageName} is deprecated`,
description: cleanMessage,
severity: 'moderate',
references: [],
vulnerable_versions: version,
recommendation: 'Find an alternative package'
}
];
}
}
catch (error) {
console.warn(`Warning: Failed to check package ${packageName}: ${error.message}`);
}
return [];
}
async batchCheckVulnerabilities(packages) {
const results = new Map();
const batchSize = 10;
for (let i = 0; i < packages.length; i += batchSize) {
const batch = packages.slice(i, i + batchSize);
const promises = batch.map(pkg => this.checkPackageVulnerabilities(pkg.name, pkg.version)
.then(vulns => ({ package: pkg.name, vulnerabilities: vulns }))
.catch(() => ({ package: pkg.name, vulnerabilities: [] })));
const batchResults = await Promise.all(promises);
for (const result of batchResults) {
if (result.vulnerabilities.length > 0) {
results.set(result.package, result.vulnerabilities);
}
}
if (i + batchSize < packages.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return results;
}
getSeverityColor(severity) {
switch (severity) {
case 'critical':
return '🔴';
case 'high':
return '🟠';
case 'moderate':
return '🟡';
case 'low':
default:
return '🔵';
}
}
formatVulnerabilitySummary(summary) {
const parts = [];
if (summary.critical > 0)
parts.push(`${summary.critical} critical`);
if (summary.high > 0)
parts.push(`${summary.high} high`);
if (summary.moderate > 0)
parts.push(`${summary.moderate} moderate`);
if (summary.low > 0)
parts.push(`${summary.low} low`);
return parts.length > 0 ? parts.join(', ') : 'No vulnerabilities found';
}
parsePackageAlias(packageName, version) {
if (version.startsWith('npm:')) {
const match = version.match(/^npm:(.+?)@(.+)$/);
if (match) {
return {
realPackageName: match[1],
realVersion: match[2]
};
}
}
return {
realPackageName: packageName,
realVersion: version
};
}
}
exports.SecurityScanner = SecurityScanner;
//# sourceMappingURL=security-scanner.js.map