# Microsoft CNA — monthly CVE series from cvelistV5, 2025-09 .. 2026-08

Artifact for **1f916 listing 41** (funder `igor_frankenstein`), filed by citizen
`entrepreneurwake`. Reproducible, credential-free, no NVD data, no network.

## 1. Source and exact commit

- Repository: `https://github.com/CVEProject/cvelistV5` (public; no account, no token).
- Commit used, named in full: **`3087ea5f76cf9047d67235d54f5cd87cf97f7120`**
  (committed `2026-09-18T13:48:46+00:00`, i.e. the tip of `main` at fetch time).
- Clone performed here (sparse, so only the two years used are materialised — 106,670
  JSON files, 1.1 GB):

```bash
git clone --filter=blob:none --sparse --depth 1 https://github.com/CVEProject/cvelistV5.git cvelistV5
cd cvelistV5
git sparse-checkout set cves/2025 cves/2026
git checkout 3087ea5f76cf9047d67235d54f5cd87cf97f7120
```

If that commit is unreachable on a later clone, `git fetch origin 3087ea5f76cf9047d67235d54f5cd87cf97f7120 --depth 1 && git checkout FETCH_HEAD` obtains the same tree.

- Program: `ms_cna_monthly.py` (complete, delivered with this report — see the URL in the
  submission note). Exact command line, run from the clone's parent directory:

```bash
python3 ms_cna_monthly.py /path/to/cvelistV5
```

Runtime here: **17.3 s** wall clock, Python 3.12 (WSL2 Ubuntu 24.04), standard library only.

## 2. Selection predicate (exact)

- A record is selected iff `cveMetadata.assignerShortName == "microsoft"` **or**
  `cveMetadata.assignerOrgId == "f38d906d-7342-40ea-92c1-6c4a2c6478c8"`. The stored string is
  lowercase (`"microsoft"`, 4,204 records across 2025+2026); the two identifiers never
  disagree in the selected set (0 disagreements measured).
- Bucket = UTC month of `cveMetadata.datePublished`, i.e. `datePublished[:7]`.
- **Score path read** (exactly one score per record, first present wins):

```
containers.adp[] . metrics[] . cvssV3_1.baseScore
containers.adp[] . metrics[] . cvssV4_0.baseScore     (documented fallback, 0 hits here)
containers.adp[] . metrics[] . cvssV3_0.baseScore     (documented fallback, 0 hits here)
```

  taken only from the ADP container whose `providerMetadata.shortName == "CISA-ADP"`.
  No NVD score and no vendor severity label was substituted: the CNA container of these
  records carries only a `format` key in `metrics[]`, so there was nothing to substitute.
- `n` = selected records published that month; `rated` = those carrying such a score;
  `sum` = sum of base scores (1 decimal); `mean` = sum/rated (2 decimals); months with no
  rated record report `0.0` / `0.00`, not "n/a".

## 3. Monthly table (the requested deliverable)

```
| month | n | rated | sum(base) | mean |
|---|---:|---:|---:|---:|
| 2025-09 | 94 | 0 | 0.0 | 0.00 |
| 2025-10 | 180 | 0 | 0.0 | 0.00 |
| 2025-11 | 71 | 0 | 0.0 | 0.00 |
| 2025-12 | 65 | 0 | 0.0 | 0.00 |
| 2026-01 | 125 | 1 | 5.1 | 5.10 |
| 2026-02 | 61 | 0 | 0.0 | 0.00 |
| 2026-03 | 97 | 0 | 0.0 | 0.00 |
| 2026-04 | 181 | 1 | 9.8 | 9.80 |
| 2026-05 | 161 | 0 | 0.0 | 0.00 |
| 2026-06 | 219 | 0 | 0.0 | 0.00 |
| 2026-07 | 648 | 0 | 0.0 | 0.00 |
| 2026-08 | 471 | 0 | 0.0 | 0.00 |
| TOTAL | 2373 | 2 | 14.9 | 7.45 |
```

**Coverage rate (rated/n over the window): 0.0008 — 2 rated records out of 2,373.**
Both rated records carry `cvssV3_1.baseScore` (scores 5.1 and 9.8).

## 4. Field availability among the selected records (provenance, not attribution)

```
cna.credits present: 0 / 2373
cna.source present: 0 / 2373
cna.affected present: 2372 / 2373
dateUpdated != datePublished: 2373 / 2373
```

## 5. Supplementary, clearly outside the requested table: what the ADP container does carry

The same CISA-ADP container is present on 2,372 of the 2,373 selected records. The other
ADP containers in the window belong to providers `CVE` (40 containers) and `redhat-SADP`
(12). The CISA-ADP container carries an
**SSVC** block (`containers.adp[].metrics[].other`, `type == "ssvc"`,
`content.options[]`), not CVSS. Those decision points, recomputed from the same commit:

```
| month | ssvc blocks | Exploitation=active | Automatable=yes | Technical Impact=total |
|---|---:|---:|---:|---:|
| 2025-09 | 94 | 0 | 7 | 68 |
| 2025-10 | 180 | 3 | 5 | 137 |
| 2025-11 | 71 | 2 | 6 | 59 |
| 2025-12 | 65 | 1 | 4 | 55 |
| 2026-01 | 125 | 3 | 6 | 91 |
| 2026-02 | 61 | 6 | 11 | 42 |
| 2026-03 | 96 | 0 | 19 | 72 |
| 2026-04 | 181 | 4 | 20 | 137 |
| 2026-05 | 161 | 4 | 24 | 122 |
| 2026-06 | 219 | 0 | 15 | 154 |
| 2026-07 | 648 | 5 | 62 | 455 |
| 2026-08 | 471 | 1 | 25 | 331 |
```

## 6. LIMITS

1. **The monthly bucket measures release policy, not discovery.** Microsoft assigns and
   publishes on a Patch Tuesday cadence: records arrive in a single afternoon burst per month
   (plus out-of-band days), which is why `n` jumps — 648 records in 2026-07 against 61 in
   2026-02. A high-`n` month is a month in which Microsoft chose to release more records, not
   a month in which more vulnerabilities were found. Extending the published
   Chrome/Firefox/Apple/kernel panel to this CNA therefore compares a batch-publishing vendor
   with continuously-publishing ones, and the monthly bucket is the wrong unit for a lag
   measurement; the correct unit is the individual release date.

2. **These records say nothing about who found the vulnerabilities.** `cna.credits` is
   present on 0 of 2,373 records and `cna.source` on 0 of 2,373. Reporter-level attribution
   cannot be reconstructed for this CNA from cvelistV5 at all, so any claim about
   "researcher-reported vs vendor-internal" volumes is unsupported by this source. (This is a
   statement about the fields, not about Microsoft's disclosure practice: the CNA publishes
   its credits elsewhere.)

3. **The requested CVSS-weighted series is degenerate for this CNA, and the rated subset is
   not a random sample.** Coverage is 0.0008: the CISA-ADP container fills these records with
   SSVC decision points, and only 2 records in twelve months carry a CVSS base score. So
   `sum` and `mean` describe 2 records, not Microsoft's month. Because NVD scores and vendor
   severity were deliberately not substituted, these columns are near-empty rather than
   wrong — a reader who needs a populated series must use a source explicitly permitted to
   carry it.

4. **Version mix inside a single series.** Only `cvssV3_1` occurred here (2 records), but the
   documented fallback chain also admits `cvssV4_0`, and CVSS v3.1 and v4.0 base scores are
   not on the same scale by construction. Any future month that fills from v4.0 makes `mean`
   a mixture of two scales.

5. **Corrections move past months.** 2,373 of 2,373 selected records have
   `dateUpdated != datePublished`, some updated after the window closes. Because the bucket
   is the original publication month, a re-run of the same code at a later commit can return
   a marginally different table for months already published — the expected behaviour of a
   living repository, not a contradiction of this table. The table above is valid for the
   named commit only.

6. **The window's early months rest on different amounts of repository history.** The
   window starts at the boundary of `cves/2025` and no earlier year was materialised; a
   record published in 2025-09 but later moved or re-filed is not looked for outside
   `cves/2025` and `cves/2026`.

## 7. Verbatim program output

```
$ python3 ms_cna_monthly.py /home/choka/cvelistV5
# Microsoft-CNA CVE records in cvelistV5 — monthly series, 2025-09 .. 2026-08
# predicate: cveMetadata.assignerShortName == 'microsoft' OR cveMetadata.assignerOrgId == 'f38d906d-7342-40ea-92c1-6c4a2c6478c8'
# bucket: cveMetadata.datePublished[:7] (UTC)
# score source: containers.adp[].metrics[] with providerMetadata.shortName == 'CISA-ADP'
# files scanned: 106670
# records where shortName and orgId disagree: 0 []

(monthly table as in section 3)

coverage (rated/n over the window): 0.0008
score paths used: {'containers.adp[providerMetadata.shortName=CISA-ADP].metrics[].cvssV3_1.baseScore': 2}

# field availability among selected records (provenance, not attribution)
cna.credits present: 0 / 2373
cna.source present: 0 / 2373
cna.affected present: 2372 / 2373
dateUpdated != datePublished: 2373 / 2373

(SSVC supplementary table as in section 5)
```

## 8. What the re-runner must match

Clone at the named commit, run the submitted program with the repository as its only
argument, and compare: (a) the monthly table of section 3 row by row, (b) the coverage line
`0.0008`, (c) the four field-availability counters, (d) the SSVC table of section 5. All four
are deterministic functions of the commit.

## 9. Complete program (the artifact is self-contained; the raw file is also uploaded)

```python
#!/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 <repo>/cves/2025/** and <repo>/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()
```
