Files
cve-dashboard/backend/routes/nvdLookup.js

95 lines
3.4 KiB
JavaScript

// NVD CVE Lookup Routes
const express = require('express');
const CVE_ID_PATTERN = /^CVE-\d{4}-\d{4,}$/;
function createNvdLookupRouter(db, requireAuth) {
const router = express.Router();
// All routes require authentication
router.use(requireAuth(db));
// Lookup CVE details from NVD API 2.0
router.get('/lookup/:cveId', async (req, res) => {
const { cveId } = req.params;
if (!CVE_ID_PATTERN.test(cveId)) {
return res.status(400).json({ error: 'Invalid CVE ID format. Expected CVE-YYYY-NNNNN.' });
}
const url = `https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=${encodeURIComponent(cveId)}`;
const headers = {};
if (process.env.NVD_API_KEY) {
headers['apiKey'] = process.env.NVD_API_KEY;
}
try {
const response = await fetch(url, {
headers,
signal: AbortSignal.timeout(10000)
});
if (response.status === 404) {
return res.status(404).json({ error: 'CVE not found in NVD.' });
}
if (response.status === 429) {
return res.status(429).json({ error: 'NVD API rate limit exceeded. Try again later.' });
}
if (!response.ok) {
return res.status(502).json({ error: `NVD API returned status ${response.status}.` });
}
const data = await response.json();
if (!data.vulnerabilities || data.vulnerabilities.length === 0) {
return res.status(404).json({ error: 'CVE not found in NVD.' });
}
const vuln = data.vulnerabilities[0].cve;
// Extract English description
const descriptionEntry = vuln.descriptions?.find(d => d.lang === 'en');
const description = descriptionEntry ? descriptionEntry.value : '';
// Extract severity with cascade: CVSS v3.1 → v3.0 → v2.0
let severity = null;
const metrics = vuln.metrics || {};
if (metrics.cvssMetricV31 && metrics.cvssMetricV31.length > 0) {
severity = metrics.cvssMetricV31[0].cvssData?.baseSeverity;
} else if (metrics.cvssMetricV30 && metrics.cvssMetricV30.length > 0) {
severity = metrics.cvssMetricV30[0].cvssData?.baseSeverity;
} else if (metrics.cvssMetricV2 && metrics.cvssMetricV2.length > 0) {
severity = metrics.cvssMetricV2[0].baseSeverity;
}
// Map NVD severity strings to app levels
const severityMap = {
'CRITICAL': 'Critical',
'HIGH': 'High',
'MEDIUM': 'Medium',
'LOW': 'Low'
};
severity = severity ? (severityMap[severity.toUpperCase()] || 'Medium') : 'Medium';
// Extract published date (YYYY-MM-DD)
const publishedRaw = vuln.published;
const published_date = publishedRaw ? publishedRaw.split('T')[0] : '';
res.json({ description, severity, published_date });
} catch (err) {
if (err.name === 'TimeoutError' || err.name === 'AbortError') {
return res.status(504).json({ error: 'NVD API request timed out.' });
}
console.error('NVD lookup error:', err);
res.status(502).json({ error: 'Failed to reach NVD API.' });
}
});
return router;
}
module.exports = createNvdLookupRouter;