// Usage: node kev_stats.js const fs = require('fs'); const crypto = require('crypto'); const [file, asOf] = process.argv.slice(2); const buf = fs.readFileSync(file); console.log('sha256', crypto.createHash('sha256').update(buf).digest('hex'), 'bytes', buf.length); const d = JSON.parse(buf.toString('utf8')); const v = d.vulnerabilities; console.log('catalogVersion', d.catalogVersion, '| dateReleased', d.dateReleased, '| count field', d.count, '| array length', v.length); console.log('keys of first entry:', Object.keys(v[0]).join(', ')); const keyset = {}; v.forEach(e => Object.keys(e).forEach(k => keyset[k] = (keyset[k] || 0) + 1)); console.log('key presence:', JSON.stringify(keyset)); console.log('unique cveIDs', new Set(v.map(e => e.cveID)).size); const pct = (n, t) => (100 * n / t).toFixed(1) + '%'; const day = s => Date.parse(s + 'T00:00:00Z') / 86400000; const bad = v.filter(e => !/^\d{4}-\d{2}-\d{2}$/.test(e.dateAdded) || !/^\d{4}-\d{2}-\d{2}$/.test(e.dueDate)); console.log('entries with malformed dateAdded/dueDate:', bad.length); const dates = v.map(e => e.dateAdded).sort(); console.log('earliest dateAdded', dates[0], '| latest dateAdded', dates[dates.length - 1]); console.log('\n== Added per calendar year (dateAdded) =='); const yr = {}; v.forEach(e => { const y = e.dateAdded.slice(0, 4); yr[y] = (yr[y] || 0) + 1; }); Object.keys(yr).sort().forEach(y => console.log(y, yr[y], pct(yr[y], v.length))); const a = day(asOf); const within = n => v.filter(e => { const x = a - day(e.dateAdded); return x >= 0 && x < n; }).length; console.log('\nasOf', asOf); console.log('added in 2026:', yr['2026'] || 0); console.log('added in last 30 days (dateAdded >= asOf-29d, i.e. 30 calendar days incl. asOf):', within(30)); console.log('added in last 90 days (dateAdded >= asOf-89d):', within(90)); const since = n => v.filter(e => { const x = a - day(e.dateAdded); return x >= 0 && x <= n; }).length; console.log('alt definition, dateAdded >= asOf-30d:', since(30), '| dateAdded >= asOf-90d:', since(90)); console.log('\n== Top 15 vendorProject (exact string) =='); const ven = {}; v.forEach(e => ven[e.vendorProject] = (ven[e.vendorProject] || 0) + 1); const vs = Object.entries(ven).sort((x, y) => y[1] - x[1] || x[0].localeCompare(y[0])); console.log('distinct vendorProject values:', vs.length); vs.slice(0, 18).forEach(([k, n], i) => console.log(i + 1, k, n, pct(n, v.length))); console.log('\n== knownRansomwareCampaignUse =='); const r = {}; v.forEach(e => r[e.knownRansomwareCampaignUse] = (r[e.knownRansomwareCampaignUse] || 0) + 1); Object.entries(r).forEach(([k, n]) => console.log(JSON.stringify(k), n, pct(n, v.length))); const r26 = v.filter(e => e.dateAdded.startsWith('2026')); console.log('2026 additions flagged Known:', r26.filter(e => e.knownRansomwareCampaignUse === 'Known').length, 'of', r26.length); function dist(label, arr) { const g = arr.map(e => day(e.dueDate) - day(e.dateAdded)).sort((x, y) => x - y); const n = g.length; const med = n % 2 ? g[(n - 1) / 2] : (g[n / 2 - 1] + g[n / 2]) / 2; const c = f => g.filter(f).length; console.log(`\n== dueDate - dateAdded, days: ${label} (n=${n}) ==`); console.log('min', g[0], '| median', med, '| max', g[n - 1], '| mean', (g.reduce((s, x) => s + x, 0) / n).toFixed(1)); [['<=7', x => x <= 7], ['8-14', x => x >= 8 && x <= 14], ['15-21', x => x >= 15 && x <= 21], ['>21', x => x > 21]] .forEach(([l, f]) => console.log(l, c(f), pct(c(f), n))); const h = {}; g.forEach(x => h[x] = (h[x] || 0) + 1); console.log('exact-value histogram (days:count):', Object.entries(h).sort((x, y) => y[1] - x[1]).slice(0, 12).map(([k, n]) => `${k}:${n}`).join(' ')); } dist('overall', v); Object.keys(yr).sort().forEach(y => dist('added in ' + y, v.filter(e => e.dateAdded.startsWith(y)))); console.log('\n== 10 most recent entries (sort dateAdded desc, then cveID desc) =='); [...v].sort((x, y) => y.dateAdded.localeCompare(x.dateAdded) || y.cveID.localeCompare(x.cveID)).slice(0, 10) .forEach(e => console.log([e.cveID, e.vendorProject, e.product, e.dateAdded, e.dueDate, e.knownRansomwareCampaignUse].join(' | '))); console.log('\n== Product groups (case-insensitive; rules as written) =='); const vp = s => e => e.vendorProject.toLowerCase() === s.toLowerCase(); const vpHas = s => e => e.vendorProject.toLowerCase().includes(s.toLowerCase()); const prHas = s => e => e.product.toLowerCase().includes(s.toLowerCase()); const and = (...f) => e => f.every(g => g(e)); const or = (...f) => e => f.some(g => g(e)); const ms = vp('Microsoft'); const rules = [ ['Microsoft (vendorProject = Microsoft), all products', ms], ['Microsoft: product contains "Windows"', and(ms, prHas('Windows'))], ['Microsoft: product contains "Win32k"', and(ms, prHas('Win32k'))], ['Microsoft: product contains "Office"', and(ms, prHas('Office'))], ['Microsoft: product contains "Exchange"', and(ms, prHas('Exchange'))], ['Microsoft: product contains "Outlook"', and(ms, prHas('Outlook'))], ['Microsoft: product contains "Word" or "Excel" or "PowerPoint"', and(ms, or(prHas('Word'), prHas('Excel'), prHas('PowerPoint')))], ['Microsoft: product contains "SharePoint"', and(ms, prHas('SharePoint'))], ['Microsoft: product contains "Internet Explorer"', and(ms, prHas('Internet Explorer'))], ['Microsoft: product contains "Edge"', and(ms, prHas('Edge'))], ['Microsoft: product contains "Defender"', and(ms, prHas('Defender'))], ['Microsoft: product contains "Teams"', and(ms, prHas('Teams'))], ['Microsoft: product contains "Entra" or "Azure"', and(ms, or(prHas('Entra'), prHas('Azure')))], ['Fortinet (vendorProject = Fortinet)', vp('Fortinet')], ['Ivanti (vendorProject = Ivanti)', vp('Ivanti')], ['Ivanti or Pulse Secure or MobileIron (vendorProject contains)', or(vpHas('Ivanti'), vpHas('Pulse Secure'), vpHas('MobileIron'))], ['Cisco (vendorProject = Cisco)', vp('Cisco')], ['SonicWall (vendorProject = SonicWall)', vp('SonicWall')], ['Citrix (vendorProject = Citrix)', vp('Citrix')], ['VMware (vendorProject = VMware)', vp('VMware')], ['VMware or Broadcom (vendorProject contains)', or(vpHas('VMware'), vpHas('Broadcom'))], ['Apple (vendorProject = Apple)', vp('Apple')], ['Google (vendorProject = Google), all products', vp('Google')], ['Google: product contains "Chromium"', and(vp('Google'), prHas('Chromium'))], ['Google: product contains "Chrome"', and(vp('Google'), prHas('Chrome'))], ['Adobe (vendorProject = Adobe)', vp('Adobe')], ['ConnectWise (vendorProject = ConnectWise)', vp('ConnectWise')], ['Kaseya (vendorProject = Kaseya)', vp('Kaseya')], ['N-able (vendorProject = N-able)', vp('N-able')], ['SolarWinds (vendorProject = SolarWinds)', vp('SolarWinds')], ['Veeam (vendorProject = Veeam)', vp('Veeam')], ['QNAP (vendorProject = QNAP)', vp('QNAP')], ['Synology (vendorProject = Synology)', vp('Synology')], ['Zyxel (vendorProject = Zyxel)', vp('Zyxel')], ['D-Link (vendorProject = D-Link)', vp('D-Link')], ['TP-Link (vendorProject = TP-Link)', vp('TP-Link')], ]; rules.forEach(([l, f]) => { const m = v.filter(f); console.log(`${l} => ${m.length} (${pct(m.length, v.length)}); ransomware Known ${m.filter(e => e.knownRansomwareCampaignUse === 'Known').length}; added 2026 ${m.filter(e => e.dateAdded.startsWith('2026')).length}`); }); console.log('\n== vendorProject spellings containing the listed names (to expose variants) =='); ['microsoft', 'fortinet', 'ivanti', 'pulse', 'mobileiron', 'cisco', 'sonicwall', 'citrix', 'vmware', 'broadcom', 'apple', 'google', 'adobe', 'connectwise', 'kaseya', 'n-able', 'solarwinds', 'veeam', 'qnap', 'synology', 'zyxel', 'd-link', 'tp-link'] .forEach(s => console.log(s, '=>', vs.filter(([k]) => k.toLowerCase().includes(s)).map(([k, n]) => `${k}:${n}`).join('; ') || 'none')); console.log('\n== Microsoft product values =='); const mp = {}; v.filter(ms).forEach(e => mp[e.product] = (mp[e.product] || 0) + 1); console.log(Object.entries(mp).sort((x, y) => y[1] - x[1]).map(([k, n]) => `${k}:${n}`).join('; ')); console.log('\n== Google product values =='); const gp = {}; v.filter(vp('Google')).forEach(e => gp[e.product] = (gp[e.product] || 0) + 1); console.log(Object.entries(gp).sort((x, y) => y[1] - x[1]).map(([k, n]) => `${k}:${n}`).join('; '));