#!/usr/bin/env python3 """ms_cna_monthly.py — monthly series of Microsoft-CNA CVE records from cvelistV5. Usage: python3 ms_cna_monthly.py /path/to/cvelistV5 Reads every record under /cves/2025/** and /cves/2026/** (the only years covering the requested window), selects records whose `cveMetadata.assignerShortName` equals "Microsoft", buckets them by the UTC month of `cveMetadata.datePublished`, and reports per month: n, rated, sum(base scores), mean. Score source = the CISA-ADP (Vulnrichment) container inside the same record: containers.adp[].metrics[].cvssV3_1.baseScore (providerMetadata.shortName == "CISA-ADP") containers.adp[].metrics[].cvssV4_0.baseScore (fallback, same container) Exactly one score per record is used: cvssV3_1 first, then cvssV4_0. No NVD data, no vendor severity labels. Python 3, standard library only, no network. """ import json import os import sys from collections import defaultdict WINDOW = ["2025-%02d" % m for m in range(9, 13)] + ["2026-%02d" % m for m in range(1, 9)] CNA_SHORT = "microsoft" # exact string, case-sensitive, as stored in cvelistV5 CNA_ORG = "f38d906d-7342-40ea-92c1-6c4a2c6478c8" # CVE Program org id of the Microsoft CNA SCORE_PATHS = [ ("cvssV3_1", "baseScore"), ("cvssV4_0", "baseScore"), ("cvssV3_0", "baseScore"), ] def iter_records(repo): for year in ("2025", "2026"): base = os.path.join(repo, "cves", year) for root, _dirs, files in os.walk(base): for fn in files: if fn.endswith(".json"): yield os.path.join(root, fn) def is_cna(rec): meta = rec.get("cveMetadata") or {} short = (meta.get("assignerShortName") or "").strip() org = (meta.get("assignerOrgId") or "").strip() return short == CNA_SHORT or org == CNA_ORG, short, org def published_month(rec): dp = (rec.get("cveMetadata") or {}).get("datePublished") if not dp or len(dp) < 7: return None return dp[:7] def adp_score(rec): """Return (score, path_used) or (None, None).""" for cont in (rec.get("containers") or {}).get("adp") or []: short = (((cont.get("providerMetadata") or {}).get("shortName")) or "").upper() if short != "CISA-ADP": continue for metric in cont.get("metrics") or []: if not isinstance(metric, dict): continue for key, fieldname in SCORE_PATHS: block = metric.get(key) if isinstance(block, dict) and isinstance(block.get(fieldname), (int, float)): return float(block[fieldname]), "containers.adp[providerMetadata.shortName=CISA-ADP].metrics[].%s.%s" % (key, fieldname) return None, None def adp_ssvc(rec): """Supplementary: CISA-ADP SSVC decision points (same container, metrics[].other).""" for cont in (rec.get("containers") or {}).get("adp") or []: short = (((cont.get("providerMetadata") or {}).get("shortName")) or "").upper() if short != "CISA-ADP": continue for metric in cont.get("metrics") or []: other = (metric or {}).get("other") or {} if other.get("type") != "ssvc": continue content = other.get("content") or {} pts = {} for opt in content.get("options") or []: if isinstance(opt, dict): pts.update({k: v for k, v in opt.items()}) return pts return None def main(): repo = sys.argv[1] if len(sys.argv) > 1 else "." n_by_month = defaultdict(int) rated_by_month = defaultdict(int) sum_by_month = defaultdict(float) paths = defaultdict(int) scanned = 0 n_credits = [0] n_source = [0] n_updated = [0] n_affected = [0] mismatch = set() n_ssvc = defaultdict(int) ssvc_opts = defaultdict(int) for path in iter_records(repo): scanned += 1 try: with open(path, encoding="utf-8") as fh: rec = json.load(fh) except Exception: continue ok, short, org = is_cna(rec) if not ok: continue if short != CNA_SHORT or org != CNA_ORG: mismatch.add((short, org)) month = published_month(rec) if month not in WINDOW: continue n_by_month[month] += 1 cna = (rec.get("containers") or {}).get("cna") or {} if cna.get("credits"): n_credits[0] += 1 if cna.get("source"): n_source[0] += 1 meta = rec.get("cveMetadata") or {} if meta.get("dateUpdated") and meta.get("dateUpdated") != meta.get("datePublished"): n_updated[0] += 1 if cna.get("affected"): n_affected[0] += 1 score, used = adp_score(rec) ssvc = adp_ssvc(rec) if ssvc is not None: n_ssvc[month] += 1 for k in ("Exploitation", "Automatable", "Technical Impact"): if ssvc.get(k) is not None: ssvc_opts[(month, k, str(ssvc.get(k)))] += 1 if score is not None: rated_by_month[month] += 1 sum_by_month[month] += score paths[used] += 1 print("# Microsoft-CNA CVE records in cvelistV5 — monthly series, 2025-09 .. 2026-08") print("# predicate: cveMetadata.assignerShortName == %r OR cveMetadata.assignerOrgId == %r" % (CNA_SHORT, CNA_ORG)) print("# bucket: cveMetadata.datePublished[:7] (UTC)") print("# score source: containers.adp[].metrics[] with providerMetadata.shortName == 'CISA-ADP'") print("# files scanned: %d" % scanned) print("# records where shortName and orgId disagree: %d %s" % (len(mismatch), sorted(mismatch)[:3])) print() print("| month | n | rated | sum(base) | mean |") print("|---|---:|---:|---:|---:|") tot_n = tot_r = 0 tot_s = 0.0 for m in WINDOW: n = n_by_month.get(m, 0) r = rated_by_month.get(m, 0) s = sum_by_month.get(m, 0.0) mean = (s / r) if r else 0.0 tot_n += n tot_r += r tot_s += s print("| %s | %d | %d | %.1f | %.2f |" % (m, n, r, s, mean)) print("| TOTAL | %d | %d | %.1f | %.2f |" % (tot_n, tot_r, tot_s, (tot_s / tot_r) if tot_r else 0.0)) print() print("coverage (rated/n over the window): %.4f" % ((tot_r / tot_n) if tot_n else 0.0)) print("score paths used:", dict(paths)) print() print("# field availability among selected records (provenance, not attribution)") print("cna.credits present: %d / %d" % (n_credits[0], tot_n)) print("cna.source present: %d / %d" % (n_source[0], tot_n)) print("cna.affected present: %d / %d" % (n_affected[0], tot_n)) print("dateUpdated != datePublished: %d / %d" % (n_updated[0], tot_n)) print() print("# SUPPLEMENTARY (not part of the requested table): CISA-ADP SSVC decision points") print("| month | ssvc blocks | Exploitation=active | Automatable=yes | Technical Impact=total |") print("|---|---:|---:|---:|---:|") for m in WINDOW: print("| %s | %d | %d | %d | %d |" % ( m, n_ssvc.get(m, 0), ssvc_opts.get((m, "Exploitation", "active"), 0), ssvc_opts.get((m, "Automatable", "yes"), 0), ssvc_opts.get((m, "Technical Impact", "total"), 0))) if __name__ == "__main__": main()