#!/usr/bin/env node import axios from 'axios'; import mysql from 'mysql2/promise'; import dotenv from 'dotenv'; import readline from 'readline'; 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 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}`; } function getLocalizedText(entry, fallback = '') { if (!entry || typeof entry !== 'object') return fallback; // Try to get English (US) first if (entry['en-US']?.Value) return entry['en-US'].Value; // Fallback: get first entry with a Value field 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 storeVulnerability(v, published, modified) { const cve = v.CVE; console.log(`⏳ Saving ${cve}...`); const title = getLocalizedText(v.Title, ''); const description = extractDescription(v.Notes, title); const affectedProducts = extractAffectedProducts(v); const severity = v.Threats?.find(t => t.Type === 'Severity')?.Description?.Value || ''; const impact = v.Threats?.find(t => t.Type === 'Impact')?.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 : []; if (!title) { console.warn(`⚠️ No title found for ${cve}`); console.debug('Title raw:', JSON.stringify(v.Title, null, 2)); } if (!description) { console.warn(`⚠️ No description for ${cve}`); console.debug('Notes raw:', JSON.stringify(v.Notes, null, 2)); } try { // Insert or update the main CVE entry (no cwe column now) 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] ); // Insert CWE entries and links for (const { ID: cweId, Value: cweName } of cweList) { if (!cweId) continue; // Insert into cwe_entries if not already present await DB.execute( `INSERT IGNORE INTO cwe_entries (cwe_id, cwe_name) VALUES (?, ?)`, [cweId, cweName || null] ); // Link CVE to CWE await DB.execute( `INSERT IGNORE INTO cve_cwe (cve_id, cwe_id) VALUES (?, ?)`, [cve, cweId] ); } console.log(`✅ Saved ${cve} — ${severity}, ${cvssScore ?? '?'}`); } catch (err) { console.error(`❌ DB error for ${cve}:`, err.message); } } function waitForKeypress() { return new Promise((resolve) => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.question('🔄 Press any key to continue to the next 10 CVEs...', () => { rl.close(); resolve(); }); }); } async function importPreviousMonth() { const cvrfId = getPreviousMonthID(); console.log(`📡 Fetching CVEs for ${cvrfId}...`); const data = await fetchCVRFDoc(cvrfId); if (!data) { console.log("❌ No data returned from MSRC."); return; } const published = formatDate(data.DocumentTracking?.InitialReleaseDate); const modified = formatDate(data.DocumentTracking?.CurrentReleaseDate); const vulns = data.Vulnerability || []; console.log(`📋 Found ${vulns.length} vulnerabilities to import.`); const batchSize = 10; for (let i = 0; i < vulns.length; i += batchSize) { const batch = vulns.slice(i, i + batchSize); console.log(`🔄 Processing batch ${i / batchSize + 1} (${batch.length} items)...`); for (const v of batch) { await storeVulnerability(v, published, modified); } } console.log(`✅ Finished importing ${vulns.length} CVEs for ${cvrfId}`); await DB.end(); } console.log("🚀 Starting MSRC sync now..."); await importPreviousMonth();