dependency-guardian
Version:
A powerful dependency management and analysis tool for Node.js projects
189 lines (165 loc) • 5.88 kB
JavaScript
const ora = require('ora');
const chalk = require('chalk');
const fs = require('fs/promises');
const { exec } = require('child_process');
const util = require('util');
const logger = require('../utils/logger');
const execPromise = util.promisify(exec);
function auditCommand(program) {
program
.command('audit')
.description('Audit project dependencies for security vulnerabilities')
.option('-l, --level <level>', 'Minimum severity level to display (low, moderate, high, critical)', 'low')
.option('-f, --fix', 'Attempt to fix vulnerabilities')
.option('-r, --report <path>', 'Save report to file')
.action(async (options) => {
try {
const results = await runAudit(options);
displayResults(results, options.level);
if (options.fix) {
await fixVulnerabilities(results);
}
if (options.report) {
await saveReport(results, options.report);
}
} catch (error) {
logger.error('Audit error:', error);
process.exit(1);
}
});
}
async function runAudit(options) {
const severityLevels = ['low', 'moderate', 'high', 'critical'];
const minSeverityIndex = severityLevels.indexOf(options.level);
try {
// Run npm audit
const { stdout } = await execPromise('npm audit --json');
const npmAudit = JSON.parse(stdout);
// Run Snyk audit for additional security checks
const { stdout: snykOutput } = await execPromise('npx snyk test --json');
const snykAudit = JSON.parse(snykOutput);
// Combine and process results
return processAuditResults(npmAudit, snykAudit, minSeverityIndex);
} catch (error) {
// Handle case where command fails but returns valid JSON
if (error.stdout) {
try {
const npmAudit = JSON.parse(error.stdout);
return processAuditResults(npmAudit, {}, minSeverityIndex);
} catch (e) {
throw error;
}
}
throw error;
}
}
function processAuditResults(npmResults, snykResults, minSeverityIndex) {
const severityLevels = ['low', 'moderate', 'high', 'critical'];
const results = {
vulnerabilities: [],
summary: {
total: 0,
bySeverity: {}
}
};
// Process npm audit results
Object.entries(npmResults.vulnerabilities || {}).forEach(([name, vuln]) => {
const severityIndex = severityLevels.indexOf(vuln.severity);
if (severityIndex >= minSeverityIndex) {
results.vulnerabilities.push({
package: name,
severity: vuln.severity,
description: vuln.title,
fixAvailable: vuln.fixAvailable,
path: vuln.path,
range: vuln.range,
source: 'npm'
});
}
});
// Process Snyk results
(snykResults.vulnerabilities || []).forEach(vuln => {
const severity = vuln.severity.toLowerCase();
const severityIndex = severityLevels.indexOf(severity);
if (severityIndex >= minSeverityIndex) {
results.vulnerabilities.push({
package: vuln.package,
severity: severity,
description: vuln.title,
fixAvailable: Boolean(vuln.fixedIn),
path: vuln.from.join(' > '),
range: vuln.semver,
source: 'snyk'
});
}
});
// Generate summary
results.vulnerabilities.forEach(vuln => {
results.summary.total++;
results.summary.bySeverity[vuln.severity] = (results.summary.bySeverity[vuln.severity] || 0) + 1;
});
return results;
}
function displayResults(results, minLevel) {
const severityColors = {
critical: chalk.red.bold,
high: chalk.red,
moderate: chalk.yellow,
low: chalk.gray
};
console.log('\nSecurity Audit Results:\n');
// Display summary
console.log(chalk.bold('Summary:'));
console.log(`Total vulnerabilities: ${results.summary.total}`);
Object.entries(results.summary.bySeverity).forEach(([severity, count]) => {
const colorFn = severityColors[severity] || chalk.white;
console.log(`${colorFn(`${severity}:`)} ${count}`);
});
// Display detailed vulnerabilities
if (results.vulnerabilities.length > 0) {
console.log('\nVulnerabilities:\n');
results.vulnerabilities.forEach(vuln => {
const colorFn = severityColors[vuln.severity] || chalk.white;
console.log(colorFn(`${vuln.severity.toUpperCase()}: ${vuln.package}`));
console.log(` Description: ${vuln.description}`);
console.log(` Path: ${vuln.path}`);
console.log(` Fix available: ${vuln.fixAvailable ? chalk.green('Yes') : chalk.red('No')}`);
console.log(` Source: ${vuln.source}`);
console.log();
});
}
}
async function fixVulnerabilities(results) {
const spinner = ora('Fixing vulnerabilities...').start();
const fixes = [];
try {
// Try npm audit fix first
await execPromise('npm audit fix');
fixes.push('Applied npm audit fixes');
// Run Snyk wizard for remaining issues
await execPromise('npx snyk wizard');
fixes.push('Applied Snyk fixes');
spinner.succeed('Vulnerabilities fixed');
fixes.forEach(fix => console.log(chalk.green(`✓ ${fix}`)));
} catch (error) {
spinner.fail('Some fixes could not be applied');
logger.error('Fix error:', error);
}
}
async function saveReport(results, filePath) {
try {
const report = {
timestamp: new Date().toISOString(),
results: results,
environment: {
nodeVersion: process.version,
platform: process.platform
}
};
await fs.writeFile(filePath, JSON.stringify(report, null, 2));
console.log(chalk.green(`\n✓ Report saved to ${filePath}`));
} catch (error) {
logger.error('Failed to save report:', error);
}
}
module.exports = auditCommand;