#!/usr/bin/env python3 """Render SM-021 from summary.json. python3 make_page.py summary.json git_check.json census.jsonl OUT.html""" import json, sys, os, html, math S = json.load(open(sys.argv[1])); G = json.load(open(sys.argv[2])); CEN = sys.argv[3]; OUT = sys.argv[4] HERE = os.path.dirname(os.path.abspath(__file__)); P = os.path.join(HERE, "page_parts") esc = html.escape n = lambda x: f"{x:,}" if isinstance(x, int) else str(x) days = lambda h: f"{h / 24:.0f} days" if h >= 96 else f"{round(h)} hours" Y, A, B, M = S["year"], S["jan_mar"], S["apr_on"], S["months"] RB = S["robustness_with_db_labels"]; NEG = S["negative_wait_excluded"]; X = S["excluded"] MN = {"01": "January", "02": "February", "03": "March", "04": "April", "05": "May", "06": "June", "07": "July", "08": "August", "09": "September", "10": "October"} SUP = {"npm", "pip", "maven", "nuget", "go", "rubygems", "composer", "rust", "erlang", "actions", "pub", "swift"} counted = set() for l in open(CEN): r = json.loads(l) if (r.get("repo_published_at") or "")[:4] == "2026" and set(r.get("repo_ecosystems") or []) & SUP: counted.add(r["ghsa"]) gd = [x["commit_minus_api_min"] for x in G["sample"] if x["commit_minus_api_min"] is not None] gin = sum(1 for x in G["sample"] if x["ghsa"] in counted); earlier = sum(1 for x in G["sample"] if x["unreviewed_commits_before"]) notin = Y["not_in_database"] _dep = os.path.expanduser("~/second-measurements/zenodo/sm-021/deposit.json") DOI = json.load(open(_dep))["metadata"]["prereserve_doi"]["doi"] if os.path.exists(_dep) else "pending" title = (f"Dependabot doesn’t alert on a security advisory until GitHub reviews it, which GitHub’s documentation says “can take up to 72 hours”. Since April, " f"{n(B['reviewed_over_72h'])} of {n(B['reviewed'])} reviewed advisories took longer, and the median wait was {days(B['median_wait_h'])}") desc = (f"When a project publishes a security advisory on GitHub, GitHub reviews it into the GitHub Advisory Database, and Dependabot does not alert on an advisory until it is reviewed. " f"GitHub’s documentation says the review “can take up to 72 hours”. We read both timestamps for every CVE GitHub issued that NVD published from 1 January to 16 October 2026; " f"{n(S['rows_counted'])} have a 2026 advisory naming a package in an ecosystem the database supports. Of those reviewed, {A['reviewed_over_72h_pct']}% of January to March advisories took longer than 72 hours; from April on, {B['reviewed_over_72h_pct']}%.") LATE = {"2026-08", "2026-09", "2026-10"} rows = "".join(f"
{desc}
published_at) and when GitHub reviewed it into the database (github_reviewed_at). We read both for every CVE GitHub issued that NVD published from 1 January to 16 October 2026: {n(S['census_rows'])} CVEs, of which {n(S['rows_counted'])} have a 2026 advisory naming a package in a supported ecosystem.github-reviewed/ {min(gd):g} to {max(gd):g} minutes after the reviewed time the API gives (git_check_2026-10-17.json). This confirms the end of each wait; the start, published_at, rests on the repository advisory API alone.Disclosure: Markovian Protocol holds no financial position in GitHub or Microsoft, was paid by no one for this work, and showed it to no one before publication. How we work.
| Advisory published | In sample | Reviewed | Over 72 h | Share | Median wait, reviewed | Median if all not in database are waiting | Not in database | Days to NVD (median) |
|---|
Repository advisories behind CVEs issued by GitHub (NVD source identifier security-advisories@github.com) and published by NVD 1 January to 16 October 2026, whose advisory names a package in npm, pip, Maven, NuGet, Go, RubyGems, Composer, Rust, Erlang, Actions, Pub or Swift, the ecosystems the database supports.3 Wait = github_reviewed_at minus the repository advisory’s published_at, compared to the second. The second median counts each advisory not in the database at its age when we read it. It is a floor only if every one of them is still waiting; some were probably declined (see the short version), and those pull it up. Read from {S['read_start'][11:16]} UTC on {S['read_start'][:10]}.
Why the recent months look better than they are (†). The sample is chosen by the date NVD published the CVE, and GitHub usually reviews the advisory before its CVE appears on NVD: {n(Y['reviewed_before_nvd'])} of the {n(Y['reviewed'])} reviewed advisories. As the queue grew, the gap from a project’s advisory to its CVE on NVD grew too, to a median of {M['2026-06']['median_repo_to_nvd_d']:g} days for June’s advisories. So an advisory published in August, September or October that is still waiting mostly has no CVE on NVD yet and is not in our sample at all. That makes the counts for those months too low and their medians too short, so the falling medians after July cannot be read as a recovery.
| Ecosystem | Reviewed in 2026 | Over 72 h | Median wait |
|---|
Every ecosystem with 100 or more reviewed advisories, by the ecosystems the project named in its own advisory; one naming packages in two ecosystems counts in both. {n(RB['added_rows'])} reviewed advisories whose project left the ecosystem blank or unsupported, but which GitHub reviewed into a supported one, are not counted. Adding them changes little: {RB['jan_mar']['reviewed_over_72h_pct']}% over 72 hours for January to March, {RB['apr_on']['reviewed_over_72h_pct']}% from April.
A project that publishes an advisory has already told the world about the flaw. The projects that depend on it hear from Dependabot, and Dependabot does not alert on an unreviewed advisory.3 GitHub’s blog said so for the backlog: “New advisories may take longer to trigger, with critical issues prioritized.”2 Our numbers do not separate advisories by severity, so critical ones may move faster than the median. For the median advisory published since April that has been reviewed, the gap was {days(B['median_wait_h'])}.
A study of 2019 to 2025 found repository advisories were the database’s fast path.5 Our January to March numbers agree; the change starts in April.
GitHub can close it either way: by clearing the queue until reviews are back inside 72 hours, or by changing the documentation to say what to expect now. The blog promised, “If things improve, we’ll tell you,” and the September staff reply, about CVE request turnaround, said “Feedback around updating our turnaround time was passed along to our curation team.”4 We recheck monthly and print the table here.
What we told whom, and when.
| 17 Oct 2026 | Measured. Nobody has seen the page. |
published_at as the moment the advisory became public. For five CVEs NVD’s date is 8 to 65 days earlier, probably because the advisory was republished; one of them, {neg}, is in the counted set with a negative wait and is left out.gunzip -k exhibits/census_2026-10-17.jsonl.gz python3 analyze.py exhibits/census_2026-10-17.jsonl 2026-10-17T11:55:00Z summary_2026-10-17.json python3 git_check.py exhibits/census_2026-10-17.jsonl 40 20261017 git_check_2026-10-17.json python3 ghsa_census.py census_new.jsonl 2026-01-01 2026-10-16 # a fresh census, about four hours
Needs Python 3 and the GitHub CLI logged in; NVD’s API is anonymous and paced. The census makes about 24,000 GitHub API calls, roughly four hours at the standard rate limit. Scripts and exhibits: sm-021/.
sourceIdentifier=security-advisories@github.com; GitHub REST API, repository security advisories and global advisories; github/advisory-database commit history.