95 lines
3.4 KiB
JavaScript
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;
|