#!/usr/bin/env node import axios from 'axios'; import mysql from 'mysql2/promise'; function log(msg) { const now = new Date().toLocaleString('en-AU', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }).replace(/\b(AM|PM)\b/, m => m.toLowerCase()); const line = `[${now}] ${msg}`; console.log(line); } async function getGitHubCVEStats() { log('šŸ“” Fetching CVE statistics from GitHub API...'); try { // Get repository tree (cves directory structure) const response = await axios.get( 'https://api.github.com/repos/CVEProject/cvelistV5/git/trees/main?recursive=1', { headers: { 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'CVE-Verification-Script' } } ); const tree = response.data.tree; // Count .json files in cves/ directory const cveFiles = tree.filter(item => item.path.startsWith('cves/') && item.path.endsWith('.json') && item.type === 'blob' ); // Group by year const byYear = {}; cveFiles.forEach(file => { const match = file.path.match(/cves\/(\d{4})\//); if (match) { const year = match[1]; byYear[year] = (byYear[year] || 0) + 1; } }); log(`āœ… Found ${cveFiles.length} CVE files in GitHub repository`); return { total: cveFiles.length, byYear, lastCommit: response.data.sha }; } catch (err) { log(`āŒ GitHub API error: ${err.message}`); if (err.response?.status === 403) { log('āš ļø GitHub API rate limit exceeded. Try again later or clone the repository.'); } throw err; } } async function countCVEsInDatabase() { log('šŸ—„ļø Counting CVEs in database...'); 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, }); // Total count const [totalRows] = await db.query('SELECT COUNT(*) as total FROM cves'); const total = totalRows[0].total; // Count by year const [yearRows] = await db.query(` SELECT YEAR(published_date) as year, COUNT(*) as count FROM cves WHERE published_date IS NOT NULL GROUP BY YEAR(published_date) ORDER BY year `); const byYear = {}; yearRows.forEach(row => { byYear[row.year] = row.count; }); // Get date range const [rangeRows] = await db.query(` SELECT MIN(published_date) as earliest, MAX(published_date) as latest FROM cves `); await db.end(); log(`āœ… Found ${total} CVEs in database`); return { total, byYear, earliest: rangeRows[0].earliest, latest: rangeRows[0].latest }; } function compareResults(github, db) { log('\nšŸ“‹ Comparison Report:'); log('━'.repeat(70)); log(`GitHub Repository: ${github.total.toLocaleString()} CVEs`); log(`Your Database: ${db.total.toLocaleString()} CVEs`); log(`Difference: ${(db.total - github.total).toLocaleString()}`); log('━'.repeat(70)); log(`\nDatabase Date Range:`); log(` Earliest: ${db.earliest ? new Date(db.earliest).toLocaleDateString() : 'N/A'}`); log(` Latest: ${db.latest ? new Date(db.latest).toLocaleDateString() : 'N/A'}`); // Get all years const allYears = new Set([ ...Object.keys(github.byYear), ...Object.keys(db.byYear) ]); const sortedYears = Array.from(allYears).sort(); log('\nšŸ“… Year-by-Year Breakdown:'); log('━'.repeat(70)); log('Year | GitHub | Database | Difference | % Complete'); log('━'.repeat(70)); const significantDiffs = []; for (const year of sortedYears) { const githubCount = github.byYear[year] || 0; const dbCount = db.byYear[year] || 0; const diff = dbCount - githubCount; const pctComplete = githubCount > 0 ? ((dbCount / githubCount) * 100).toFixed(1) : '0.0'; const diffStr = diff >= 0 ? `+${diff}` : diff.toString(); log(`${year} | ${githubCount.toString().padStart(10)} | ${dbCount.toString().padStart(10)} | ${diffStr.padStart(10)} | ${pctComplete.padStart(6)}%`); if (Math.abs(diff) > 100) { significantDiffs.push({ year, githubCount, dbCount, diff }); } } log('━'.repeat(70)); if (significantDiffs.length > 0) { log('\nāš ļø Years with significant differences (>100):'); significantDiffs.forEach(({ year, githubCount, dbCount, diff }) => { if (diff < 0) { log(` ${year}: Missing ${Math.abs(diff)} CVEs (${dbCount}/${githubCount})`); } else { log(` ${year}: Extra ${diff} CVEs (database has more than GitHub)`); } }); } const percentComplete = github.total > 0 ? ((db.total / github.total) * 100).toFixed(2) : '0.00'; log(`\nšŸ“Š Overall Completion: ${percentComplete}%`); if (db.total >= github.total) { log('šŸŽ‰ Your database has all CVEs from the official GitHub repository!'); if (db.total > github.total) { log(' (You may have older CVEs or modified entries not in the current repository)'); } } else { const missing = github.total - db.total; log(`āš ļø Missing ${missing.toLocaleString()} CVEs from GitHub repository`); log(` Run the backfill script to sync missing CVEs.`); } } async function main() { try { log('šŸš€ Starting CVE verification using GitHub API...\n'); // Step 1: Get stats from GitHub API const githubStats = await getGitHubCVEStats(); // Step 2: Count CVEs in database const dbStats = await countCVEsInDatabase(); // Step 3: Compare compareResults(githubStats, dbStats); log('\nāœ… Verification complete!'); } catch (err) { log(`āŒ Error: ${err.message}`); console.error(err); process.exit(1); } } main();