mcp-siber-security-audit
Version:
MCP server for security code audit with auto-fix capabilities
175 lines (156 loc) • 6.53 kB
JavaScript
const fs = require('fs');
const path = require('path');
class DependencyScanner {
constructor() {
// Load severity data from CWD first with fallback to bundled copy
const severityDataCandidates = [
path.join(process.cwd(), 'Severity-response.json'),
path.join(__dirname, '..', '..', 'Severity-response.json')
];
const severityDataPath = severityDataCandidates.find(candidate => {
try {
return fs.existsSync(candidate);
} catch (error) {
return false;
}
});
try {
if (!severityDataPath) {
throw new Error('Severity-response.json not found');
}
const rawData = JSON.parse(fs.readFileSync(severityDataPath, 'utf8'));
this.severityData = rawData.advisories || {};
this.actions = rawData.actions || [];
} catch (error) {
console.warn('Warning: Severity-response.json not found or invalid. Dependency scanning will be limited.');
this.severityData = {};
this.actions = [];
}
}
async scanFile(filePath) {
if (!this.severityData) {
console.log('No severity data available for dependency scanning');
return [];
}
// Only scan package.json files
if (path.basename(filePath) !== 'package.json') return [];
try {
console.log(`Scanning dependencies in ${filePath}`);
const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
const dependencies = {
...packageJson.dependencies || {},
...packageJson.devDependencies || {}
};
const foundIssues = [];
for (const [name, version] of Object.entries(dependencies)) {
const advisories = this._findAdvisories(name, version);
if (advisories.length > 0) {
foundIssues.push(...advisories.map(advisory => ({
scanner: 'dependency-scanner',
file: filePath,
line: 1,
column: 1,
type: 'VULNERABLE_DEPENDENCY',
severity: advisory.severity || 'high',
description: `${name}@${version} has known vulnerability: ${advisory.title}. ${advisory.overview || ''}\nRecommendation: ${advisory.recommendation || `Upgrade to ${advisory.patched_versions}`}`,
metadata: {
packageName: name,
version,
advisoryId: advisory.id,
cves: advisory.cves,
cwes: advisory.cwe,
cvss: advisory.cvss,
references: advisory.references
}
})));
}
}
console.log(`Found ${foundIssues.length} dependency issues in ${filePath}`);
return foundIssues;
} catch (error) {
console.error(`Error scanning dependencies in ${filePath}: ${error.message}`);
return [];
}
}
_checkDependencies(packageJson, filePath) {
const dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies
};
const foundIssues = [];
for (const [name, version] of Object.entries(dependencies || {})) {
const advisories = this._findAdvisories(name, version);
foundIssues.push(...this._createIssuesFromAdvisories(advisories, name, version, filePath));
}
return foundIssues;
}
_findAdvisories(packageName, version) {
if (!this.severityData) return [];
// Remove version range operators and get the actual version number
const actualVersion = version.replace(/[\^~>=<]/g, '');
return Object.values(this.severityData).filter(advisory => {
console.log(`Checking ${packageName}@${actualVersion} against advisory for ${advisory.module_name} (${advisory.vulnerable_versions})`);
return advisory.module_name === packageName &&
this._isVersionVulnerable(actualVersion, advisory.vulnerable_versions);
});
}
_isVersionVulnerable(version, vulnerableRange) {
if (!vulnerableRange) return false;
// Simple version comparison for now
// TODO: Implement proper semver comparison
const [major, minor, patch] = version.split('.').map(Number);
const ranges = vulnerableRange.split(' ').map(range => {
const match = range.match(/([<>]=?)?(\d+\.\d+\.\d+)/);
if (!match) return null;
const [, operator, ver] = match;
const [majV, minV, patV] = ver.split('.').map(Number);
return { operator, version: [majV, minV, patV] };
}).filter(Boolean);
return ranges.some(range => {
const compareVersion = range.version;
switch (range.operator) {
case '<':
return major < compareVersion[0] ||
(major === compareVersion[0] && minor < compareVersion[1]) ||
(major === compareVersion[0] && minor === compareVersion[1] && patch < compareVersion[2]);
case '<=':
return major < compareVersion[0] ||
(major === compareVersion[0] && minor < compareVersion[1]) ||
(major === compareVersion[0] && minor === compareVersion[1] && patch <= compareVersion[2]);
case '>':
return major > compareVersion[0] ||
(major === compareVersion[0] && minor > compareVersion[1]) ||
(major === compareVersion[0] && minor === compareVersion[1] && patch > compareVersion[2]);
case '>=':
return major > compareVersion[0] ||
(major === compareVersion[0] && minor > compareVersion[1]) ||
(major === compareVersion[0] && minor === compareVersion[1] && patch >= compareVersion[2]);
default:
return major === compareVersion[0] &&
minor === compareVersion[1] &&
patch === compareVersion[2];
}
});
}
_createIssuesFromAdvisories(advisories, packageName, version, filePath) {
return advisories.map(advisory => ({
scanner: 'dependency-scanner',
file: filePath,
line: 1, // package.json dependency location (could be improved)
column: 1,
type: 'VULNERABLE_DEPENDENCY',
severity: advisory.severity || 'high',
description: `${packageName}@${version} has known vulnerability: ${advisory.title}. ${advisory.overview || ''}\nRecommendation: ${advisory.recommendation || `Upgrade to ${advisory.patched_versions}`}`,
metadata: {
packageName,
version,
advisoryId: advisory.id,
cves: advisory.cves,
cwes: advisory.cwe,
cvss: advisory.cvss,
references: advisory.references
}
}));
}
}
module.exports = DependencyScanner;