Fix: CVE Fetch via Two-Step Pagination statt URL-Filter
NVD API unterstützt keine kombinierten Filter (Timeout bei cvssV3Severity+pubStartDate). Lösung: Erst totalResults holen, dann die letzten 60 Einträge mit startIndex abrufen und lokal nach Score >= 5 / High+Critical filtern. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,50 +4,70 @@ const https = require('https');
|
|||||||
// In-memory CVE cache — refresh every hour
|
// In-memory CVE cache — refresh every hour
|
||||||
let cveCache = { ts: 0, data: [] };
|
let cveCache = { ts: 0, data: [] };
|
||||||
|
|
||||||
function fetchCVEs() {
|
function nvdGet(path) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve, reject) => {
|
||||||
const opts = {
|
const req = https.get({
|
||||||
hostname: 'services.nvd.nist.gov',
|
hostname: 'services.nvd.nist.gov',
|
||||||
path: '/rest/json/cves/2.0?resultsPerPage=8&sortBy=published&sortOrder=desc',
|
path,
|
||||||
headers: { 'User-Agent': 'IT-Nexus-TV/1.0' },
|
headers: { 'User-Agent': 'IT-Nexus-TV/1.0' },
|
||||||
timeout: 8000,
|
}, (res) => {
|
||||||
};
|
|
||||||
const req = https.get(opts, (res) => {
|
|
||||||
let raw = '';
|
let raw = '';
|
||||||
res.on('data', c => raw += c);
|
res.on('data', c => raw += c);
|
||||||
res.on('end', () => {
|
res.on('end', () => { try { resolve(JSON.parse(raw)); } catch (e) { reject(e); } });
|
||||||
try {
|
|
||||||
const json = JSON.parse(raw);
|
|
||||||
const items = (json.vulnerabilities || []).map(v => {
|
|
||||||
const cve = v.cve;
|
|
||||||
const m31 = cve.metrics?.cvssMetricV31 || [];
|
|
||||||
const m30 = cve.metrics?.cvssMetricV30 || [];
|
|
||||||
const m = m31[0] || m30[0];
|
|
||||||
const score = m?.cvssData?.baseScore || 0;
|
|
||||||
const sevRaw = (m?.cvssData?.baseSeverity || 'medium').toLowerCase();
|
|
||||||
const sev = ['critical','high','medium','low'].includes(sevRaw) ? sevRaw : 'medium';
|
|
||||||
const desc = cve.descriptions?.find(d => d.lang === 'en')?.value || '';
|
|
||||||
const firstSentence = desc.split(/\.\s/)[0].replace(/\s+/g, ' ').trim();
|
|
||||||
const title = firstSentence.length > 110 ? firstSentence.substring(0, 110) + '…' : firstSentence || cve.id;
|
|
||||||
const pub = new Date(cve.published);
|
|
||||||
const diffH = Math.round((Date.now() - pub.getTime()) / 3600000);
|
|
||||||
const published = diffH < 1 ? '< 1 Std.' : diffH < 24 ? `${diffH} Std.` : `${Math.round(diffH / 24)} Tag${Math.round(diffH / 24) !== 1 ? 'e' : ''}`;
|
|
||||||
// Vendor from CPE or references
|
|
||||||
let vendor = 'NVD';
|
|
||||||
const cpe = cve.configurations?.[0]?.nodes?.[0]?.cpeMatch?.[0]?.criteria || '';
|
|
||||||
const m2 = cpe.match(/cpe:2\.3:[ao]:([^:]+):/);
|
|
||||||
if (m2) vendor = m2[1].charAt(0).toUpperCase() + m2[1].slice(1).replace(/_/g,' ');
|
|
||||||
return { id: cve.id, sev, cvss: score, title, vendor, published };
|
|
||||||
}).filter(c => c.cvss >= 5).slice(0, 5);
|
|
||||||
resolve(items.length >= 3 ? items : null);
|
|
||||||
} catch { resolve(null); }
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
req.on('error', () => resolve(null));
|
req.on('error', reject);
|
||||||
req.on('timeout', () => { req.destroy(); resolve(null); });
|
setTimeout(() => { req.destroy(); reject(new Error('timeout')); }, 15000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mapCVE(v) {
|
||||||
|
const cve = v.cve;
|
||||||
|
const m31 = cve.metrics?.cvssMetricV31 || [];
|
||||||
|
const m30 = cve.metrics?.cvssMetricV30 || [];
|
||||||
|
const m2 = cve.metrics?.cvssMetricV2 || [];
|
||||||
|
const m = m31[0] || m30[0] || m2[0];
|
||||||
|
const score = m?.cvssData?.baseScore || 0;
|
||||||
|
let sevRaw = (m?.cvssData?.baseSeverity || m?.baseSeverity || 'medium').toLowerCase();
|
||||||
|
if (!['critical','high','medium','low'].includes(sevRaw)) {
|
||||||
|
sevRaw = score >= 9 ? 'critical' : score >= 7 ? 'high' : score >= 4 ? 'medium' : 'low';
|
||||||
|
}
|
||||||
|
const desc = cve.descriptions?.find(d => d.lang === 'en')?.value || '';
|
||||||
|
const sentence = desc.split(/\.\s/)[0].replace(/\s+/g, ' ').trim();
|
||||||
|
const title = sentence.length > 110 ? sentence.substring(0, 110) + '…' : sentence || cve.id;
|
||||||
|
const pub = new Date(cve.published);
|
||||||
|
const diffH = Math.round((Date.now() - pub.getTime()) / 3600000);
|
||||||
|
const published = diffH < 1 ? '< 1 Std.' : diffH < 24 ? `${diffH} Std.` : `${Math.round(diffH / 24)} Tag${Math.round(diffH / 24) !== 1 ? 'e' : ''}`;
|
||||||
|
let vendor = 'NVD';
|
||||||
|
const cpe = cve.configurations?.[0]?.nodes?.[0]?.cpeMatch?.[0]?.criteria || '';
|
||||||
|
const cm = cpe.match(/cpe:2\.3:[ao]:([^:]+):/);
|
||||||
|
if (cm) vendor = cm[1].charAt(0).toUpperCase() + cm[1].slice(1).replace(/_/g, ' ');
|
||||||
|
return { id: cve.id, sev: sevRaw, cvss: score, title, vendor, published };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCVEs() {
|
||||||
|
try {
|
||||||
|
// Step 1: get total count (fast, no filters)
|
||||||
|
const r1 = await nvdGet('/rest/json/cves/2.0?resultsPerPage=1');
|
||||||
|
const total = r1.totalResults || 0;
|
||||||
|
if (total < 5) return null;
|
||||||
|
|
||||||
|
// Step 2: fetch the last ~60 entries (newest published CVEs)
|
||||||
|
const startIndex = Math.max(0, total - 60);
|
||||||
|
const r2 = await nvdGet(`/rest/json/cves/2.0?resultsPerPage=60&startIndex=${startIndex}`);
|
||||||
|
const vulns = (r2.vulnerabilities || []).reverse(); // newest first
|
||||||
|
|
||||||
|
const mapped = vulns.map(mapCVE).filter(c => c.cvss >= 5);
|
||||||
|
// Prefer high/critical, then fill with medium
|
||||||
|
const high = mapped.filter(c => ['critical','high'].includes(c.sev));
|
||||||
|
const medium = mapped.filter(c => c.sev === 'medium');
|
||||||
|
const merged = [...high, ...medium].slice(0, 5);
|
||||||
|
|
||||||
|
return merged.length >= 3 ? merged : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getStats(req, res) {
|
async function getStats(req, res) {
|
||||||
try {
|
try {
|
||||||
const db = getDatabase();
|
const db = getDatabase();
|
||||||
|
|||||||
Reference in New Issue
Block a user