#!/usr/bin/env node import axios from 'axios'; import mysql from 'mysql2/promise'; import dotenv from 'dotenv'; dotenv.config({ path: '.env.local' }); const DB = await mysql.createConnection({ host: process.env.DB_HOST, port: process.env.DB_PORT || 3306, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, }); function formatDate(isoString) { if (!isoString) return null; const date = new Date(isoString); return date.toISOString().slice(0, 19).replace('T', ' '); } function getLocalizedText(entry, fallback = '') { if (!entry || typeof entry !== 'object') return fallback; // MSRC: sometimes the object directly has "Value" if ('Value' in entry && typeof entry.Value === 'string') { return entry.Value; } // If it's localized (e.g., { "en-US": { Value: ... } }) if (entry['en-US']?.Value) return entry['en-US'].Value; // Fallback: first valid value for (const key in entry) { if (entry[key]?.Value) return entry[key].Value; } return fallback; } function extractDescription(notes = [], fallback = '') { const note = notes.find(n => n.Title === 'Description' && n.Type === 2 && typeof n.Value === 'string' && n.Value.trim().length > 0 ); return note?.Value || fallback; } function extractAffectedProducts(vuln) { const fixed = vuln.ProductStatuses?.Fixed ?? []; const known = vuln.ProductStatuses?.KnownAffected ?? []; const underInvestigation = vuln.ProductStatuses?.UnderInvestigation ?? []; return [...new Set([...fixed, ...known, ...underInvestigation])].join(', '); } async function fetchCVRFDoc(cvrfId) { const url = `https://api.msrc.microsoft.com/cvrf/v3.0/cvrf/${cvrfId}`; try { const res = await axios.get(url, { headers: { Accept: 'application/json' }, }); return res.data; } catch (err) { console.error(`โŒ Failed to fetch CVRF for ${cvrfId}:`, err.message); return null; } } async function enrichCVE(cve, data) { const [rows] = await DB.execute( `SELECT title, severity, description, cvss_score, cvss_vector FROM cves WHERE id = ?`, [cve] ); if (rows.length === 0) return; const existing = rows[0]; const updateFields = { title: existing.title || data.title, severity: existing.severity || data.severity, description: (existing.description?.length ?? 0) < 20 ? data.description : existing.description, cvss_score: existing.cvss_score ?? data.cvssScore, cvss_vector: existing.cvss_vector || data.cvssVector, }; await DB.execute( `UPDATE cves SET title = ?, severity = ?, description = ?, cvss_score = ?, cvss_vector = ? WHERE id = ?`, [updateFields.title, updateFields.severity, updateFields.description, updateFields.cvss_score, updateFields.cvss_vector, cve] ); } async function storeVulnerability(v, published, modified) { const cve = v.CVE; console.log(`โณ Processing ${cve}...`); const title = getLocalizedText(v.Title, ''); console.debug(`๐Ÿงช Raw Title for ${cve}:`, JSON.stringify(v.Title, null, 2)); console.debug(`โžก๏ธ Parsed Title: "${title}"`); const description = extractDescription(v.Notes, title); const affectedProducts = extractAffectedProducts(v); const severity = v.Threats?.find(t => t.Type === 'Severity')?.Description?.Value || ''; const exploitability = v.Threats?.find(t => t.Type === 'Exploitability')?.Description?.Value || ''; const cvssSet = v.CVSSScoreSets?.[0]; const cvssScore = cvssSet?.BaseScore || null; const cvssVector = cvssSet?.Vector || ''; const cweList = Array.isArray(v.CWE) ? v.CWE : []; await DB.execute( `INSERT INTO microsoft_cves (cve_id, title, severity, published_date, last_modified_date, affected_products, description, exploitability, cvss_score, cvss_vector) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE last_modified_date = VALUES(last_modified_date), affected_products = VALUES(affected_products), severity = VALUES(severity), description = VALUES(description), exploitability = VALUES(exploitability), cvss_score = VALUES(cvss_score), cvss_vector = VALUES(cvss_vector)`, [cve, title, severity, published, modified, affectedProducts, description, exploitability, cvssScore, cvssVector] ); for (const { ID: cweId, Value: cweName } of cweList) { if (!cweId) continue; await DB.execute( `INSERT IGNORE INTO cwe_entries (cwe_id, cwe_name) VALUES (?, ?)`, [cweId, cweName || null] ); await DB.execute( `INSERT IGNORE INTO cve_cwe (cve_id, cwe_id) VALUES (?, ?)`, [cve, cweId] ); } await enrichCVE(cve, { title, severity, description, cvssScore, cvssVector }); console.log(`โœ… Updated ${cve} in master table`); } function getPreviousMonthID() { const now = new Date(); now.setMonth(now.getMonth() - 1); const year = now.getFullYear(); const month = now.toLocaleString('en-US', { month: 'short' }); // e.g., "Mar" return `${year}-${month}`; } async function enrichPreviousMonth() { const cvrfId = getPreviousMonthID(); console.log(`๐Ÿ“ก Fetching CVEs for ${cvrfId}...`); const data = await fetchCVRFDoc(cvrfId); if (!data) return; const published = formatDate(data.DocumentTracking?.InitialReleaseDate); const modified = formatDate(data.DocumentTracking?.CurrentReleaseDate); const vulns = data.Vulnerability || []; console.log(`๐Ÿ“‹ Found ${vulns.length} vulnerabilities.`); for (const v of vulns) { await storeVulnerability(v, published, modified); } console.log(`๐ŸŽ‰ Enrichment complete for ${cvrfId}`); await DB.end(); } console.log("๐Ÿš€ Starting MSRC enrichment of CVEs..."); await enrichPreviousMonth();