UNPKG

mcp-siber-security-audit

Version:

MCP server for security code audit with auto-fix capabilities

175 lines (156 loc) 6.53 kB
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;