On 17 October 2026, CVE-2024-58398 was published. It describes an open redirect on Coder's login page that the project disclosed in GitHub advisory GHSA-wcx9-ccpj-hx3c on 28 October 2024, and fixed in versions 2.16.1, 2.15.3 and 2.14.4 that month. A dashboard sorted by CVE publication date shows a two-year-old bug as this week's news. That isn't a one-off. In 2026, 373 CVEs have been published three months or more after GitHub had already published an advisory for the same bug, more than in any earlier year we measured.
This post is for teams that triage new CVEs from a feed, track findings by CVE ID, or measure remediation SLAs from a CVE date. It covers what happened this week, what the 2026 data shows, why CVE IDs can arrive late and carry an old year, and a script that tells you whether a "new" CVE is a new bug or an old one with a new name.
The short answer
A CVE's publication date is when the CVE record appeared, not when the bug was disclosed or fixed. Of 10,240 CVEs published in 2026 to 17 October that match a GitHub-reviewed advisory, 373 (3.6%) arrived 90 days or more after the advisory, and 89 more than a year after it, against 57 and 29 in all of 2024. VulnCheck, a CVE Numbering Authority, published 311 of the 373, most of them since June. 358 of the 373 advisories list a fixed version. Before treating a new CVE as new, look up the advisories it links to: if one is months old and names a fix, you're checking whether you upgraded, not responding to a fresh disclosure.
What happened on 17 October
VulnCheck published 53 CVE records that day, and every one links to an existing GitHub repository security advisory. They cover six open-source projects. We compared each CVE's publication time with the date its advisory was published on GitHub:
| 17 October batch | |
|---|---|
| CVE records published by VulnCheck | 53 |
| Linking a GitHub security advisory | 53 |
| Median time since the advisory | 14 days |
| Advisory 30 or more days old | 11 |
| Advisory a year or more old | 2 (from October 2024 and August 2025) |
| Advisories also in GitHub's reviewed database | 10 of 53 |
The two oldest are both Coder bugs. CVE-2024-58398 is the login-page open redirect from 2024. CVE-2025-71435 covers a session-expiry issue disclosed in August 2025: with OIDC sign-in and no refresh token from the identity provider, affected versions let a web session outlive the provider's token. It was fixed in 2.23.0. Both CVE IDs were reserved and published on 17 October 2026, according to the CVE records' own metadata.
By the next morning, OSV's API listed the new CVE IDs as aliases on the Coder and Vikunja advisories, in records updated at 10:55 UTC on 18 October.
What the data shows for 2026
We took every GitHub-reviewed advisory in OSV's data dumps for eight ecosystems (npm, PyPI, Go, Maven, NuGet, RubyGems, crates.io and Packagist) on 18 October, matched each CVE alias to its record in the CVE Program's published snapshot from the same day, and measured the time between the advisory entering GitHub's database and the CVE record being published.
| CVE published in | CVEs matching a reviewed advisory | 90+ days after the advisory | A year or more after |
|---|---|---|---|
| 2022 | 3,165 | 24 (0.8%) | 2 |
| 2023 | 3,008 | 32 (1.1%) | 11 |
| 2024 | 2,793 | 57 (2.0%) | 29 |
| 2025 | 3,191 | 43 (1.3%) | 29 |
| 2026, to 17 October | 10,240 | 373 (3.6%) | 89 |
For the 373 late CVEs published in 2026:
| Measure | Result |
|---|---|
| Published by | VulnCheck 311, GitHub 38, MITRE 9, others 15 |
| When | 13 from January to May; 74 in June, 59 in July, 105 in August, 96 in September, 26 in October to the 17th |
| Median gap | 166 days |
| Longest gap | 2,124 days (advisory November 2020, CVE September 2026) |
| Advisory already lists a fixed version | 358 of 373 |
| GitHub severity | critical 29, high 131, moderate 175, low 38 |
| Ecosystem | PyPI 95, npm 86, Go 55, Packagist 54, crates.io 44, NuGet 21, Maven 10, RubyGems 8 |
| CVE ID year earlier than 2026 | 149 of 373 |
The change lines up with VulnCheck's overall output. It published 8,300 CVE records in 2026 to 17 October, 3,332 of them linking a GitHub advisory, and its monthly count rose from between about 470 and 600 a month from January to May to 1,241 in August, 1,589 in September and 1,499 in the first 17 days of October.
Three limits are worth knowing. We measured from the date an advisory entered GitHub's reviewed database, which can be weeks after the project first published it, so the gaps are lower bounds. And the 2026 table only counts advisories that GitHub has reviewed. Most of the 17 October batch, 43 of 53, links advisories that are still repository-only, so the real number of late CVEs is higher. And OSV's downloadable dumps on 18 October hadn't yet picked up the aliases added that morning, so the 17 October batch is missing from the 2026 table, including both Coder CVEs above.
Why a CVE arrives late and carries an old year
Two rules in the CVE Program's CNA Operational Rules (version 4.1.0) explain most of what you see.
Another CNA can assign. For a vulnerability that's already public, if the CNA with the most appropriate scope documents that it won't assign, says no, or doesn't respond within 72 hours, rule 4.2.1.2 has a Root decide and direct another CNA to assign. We don't know which path applied to any record in this batch. The effect is the same either way: a CVE can appear long after the advisory, from a CNA other than the project or GitHub.
The year isn't the publication year. Rule 4.2.21 says CNAs should pick the year from when the vulnerability was first publicly disclosed, when the CVE record was first published, or when the ID was reserved. That's why a record published in October 2026 is called CVE-2024-58398. Across all 81,905 CVE records published in 2026 to 17 October, 2,270 carry a year of 2024 or earlier.
So neither the ID nor the publication date tells you how old the bug is. The linked advisory does.
What a late CVE changes in your pipeline
The bug and the fix haven't changed. What changes is how it shows up in your tools:
- "New this week" views. Feeds and dashboards sorted by CVE publication date list the old bug as new. Someone opens a ticket for a bug that was fixed two years ago.
- Findings keyed on one ID. If you suppressed or accepted a finding under its GHSA ID, or wrote a VEX statement for it, and your tool starts reporting the CVE ID once one exists, the suppression may stop matching and the finding comes back.
- SLA clocks. A clock that starts at CVE publication starts years late. A clock that starts when your scanner first flagged the advisory reflects your actual exposure.
- CVE-only consumers. Tools, reports and questionnaires that only know CVE IDs see the bug for the first time. That's the useful side of late assignment, covered below.
Tell an old bug from a new one
This script takes CVE IDs, as arguments or one per line on standard input, and for each one prints when the CVE record was published, every GitHub advisory the record links to, when OSV first had that advisory, and the fixed versions it records. If the oldest advisory is 90 days or more older than the CVE, it says so. It uses only the Python standard library, the CVE Services API and the OSV API:
#!/usr/bin/env python3
"""For each CVE ID, report when the CVE record was published, the oldest
linked GitHub advisory, and the fixed versions OSV knows about.
Usage: python3 old_bug_new_id.py CVE-2024-58398 CVE-2026-111919 ...
python3 old_bug_new_id.py < new_cves.txt
"""
import datetime as dt
import json
import re
import sys
import urllib.error
import urllib.request
UA = {"User-Agent": "old-bug-new-id/1.0"}
GHSA = re.compile(r"GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}")
OLD_DAYS = 90
def get(url):
req = urllib.request.Request(url, headers=UA)
try:
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
except urllib.error.HTTPError as e:
if e.code == 404:
return None
raise
def when(s):
return dt.datetime.fromisoformat(s.replace("Z", "+00:00"))
def fixed_versions(osv):
out = []
for a in osv.get("affected", []):
name = a.get("package", {}).get("name", "?")
for r in a.get("ranges", []):
for ev in r.get("events", []):
if "fixed" in ev:
out.append(f"{name} fixed in {ev['fixed']}")
elif "last_affected" in ev:
out.append(f"{name} affected through {ev['last_affected']}, no fixed version")
return out
def check(cve):
rec = get(f"https://cveawg.mitre.org/api/cve/{cve}")
if not rec:
return f"{cve}: no published CVE record"
meta = rec["cveMetadata"]
cna = rec["containers"]["cna"]
published = when(meta["datePublished"])
refs = " ".join(r.get("url", "") for r in cna.get("references", []))
ghsas = sorted(set(GHSA.findall(refs)))
lines = [f"{cve}: CVE published {published:%Y-%m-%d} by {meta.get('assignerShortName')}"]
oldest = None
for g in ghsas:
osv = get(f"https://api.osv.dev/v1/vulns/{g}")
if not osv:
lines.append(f" {g}: not in OSV (repository advisory only; read it on GitHub)")
continue
pub = when(osv["published"])
oldest = pub if oldest is None or pub < oldest else oldest
fixes = ", ".join(fixed_versions(osv)) or "no version range"
lines.append(f" {g}: in OSV since {pub:%Y-%m-%d}; {fixes}")
if not ghsas:
lines.append(" no GitHub advisory linked from the CVE record")
if oldest is not None:
gap = (published - oldest).days
verdict = "OLD BUG, NEW ID" if gap >= OLD_DAYS else "new"
lines.append(f" gap: {gap} days -> {verdict}")
return "\n".join(lines)
def main():
ids = sys.argv[1:] or sys.stdin.read().split()
for cve in ids:
if re.fullmatch(r"CVE-\d{4}-\d{4,}", cve):
print(check(cve))
if __name__ == "__main__":
main()
On four CVEs published since September:
CVE-2024-58398: CVE published 2026-10-17 by VulnCheck
GHSA-wcx9-ccpj-hx3c: in OSV since 2024-10-28; github.com/coder/coder/v2 fixed in 2.16.1, github.com/coder/coder/v2 fixed in 2.15.3, github.com/coder/coder/v2 fixed in 2.14.4
gap: 719 days -> OLD BUG, NEW ID
CVE-2020-37277: CVE published 2026-09-06 by VulnCheck
GHSA-8jq6-w5cg-wm45: in OSV since 2020-11-11; pocketmine/pocketmine-mp fixed in 3.15.4
gap: 2124 days -> OLD BUG, NEW ID
CVE-2026-111919: CVE published 2026-10-17 by VulnCheck
GHSA-hjx8-qv73-f7cm: in OSV since 2026-10-09; code.vikunja.io/api affected through 2.6.0, no fixed version
gap: 7 days -> new
CVE-2026-111882: CVE published 2026-10-17 by VulnCheck
GHSA-43g3-pp5p-fg96: not in OSV (repository advisory only; read it on GitHub)
GHSA-8fvf-747v-qjr6: not in OSV (repository advisory only; read it on GitHub)
The first two are old bugs with new IDs, and both have long-standing fixes: the question for each is whether you're running a fixed version, which your dependency scanner has probably answered already. The third is new, and GitHub's record has no fixed version yet. The CVE record's own title for it says "before 2.7.0", so it's worth reading both records rather than one. The fourth links two repository advisories that aren't in OSV, so the script can't date them. Open them on GitHub, where each shows its publication date.
When the answer is "old bug, fix exists", the work is confirming you've upgraded. Here's Safeguard's interactive walkthrough of finding the dependencies where a fix is already available:
What to do
- Run new CVE IDs through the script before opening tickets, or add the same lookup to whatever turns your CVE feed into work.
- Record every alias on a finding. Store the GHSA ID and the CVE ID, and match suppressions, risk acceptances and VEX statements on any of them. Our OSV schema explainer covers the
aliasesfield. - Start SLA clocks at first detection, not at CVE publication. For a late CVE, that's usually close to the advisory date, or the day you added the dependency.
- Sort triage queues by advisory age as well as CVE date, so a two-year-old bug with a fix doesn't jump the queue ahead of last week's unpatched one.
- Don't read the year in a CVE ID as a date. Under the CNA rules it can be the disclosure year, the reservation year or the publication year.
The fair view
Late CVEs are mostly good news. A bug that only had a GitHub advisory was invisible to every tool, list and report that only speaks CVE, and a CVE ID fixes that. Giving IDs to already-public, already-fixed bugs is exactly what the CNA rules allow, and the records in this batch link straight back to the projects' own advisories, so nothing is hidden. For 358 of the 373 late CVEs this year, the advisory already names a fixed version.
The cost is confusion, and it lands on teams that treat the CVE record as the start of the story. Most of it goes away once a finding carries all its aliases and triage looks at the advisory date. The CVE ID tells you what to call a bug. The advisory tells you when it started.
Check yourself
A CVE published this week has a 2024 year in its ID. Does that mean the record was reserved in 2024?
No. Under CNA rule 4.2.21 the year can be when the bug was first disclosed, when the record was published, or when the ID was reserved. CVE-2024-58398 was reserved and published on 17 October 2026, and carries 2024 because the bug was disclosed then.
Your scanner suddenly reports CVE-2024-58398 on a service that already runs a fixed version. What happened?
Probably nothing in the code. A new CVE ID joined an old advisory, and a tool or suppression keyed on one ID no longer matched. Check the fixed versions in the linked advisory against what you run, then record both IDs on the finding.
Why are the gaps in this post lower bounds?
They're measured from when an advisory entered GitHub's reviewed database, which can be weeks after the project first published it. And advisories that GitHub hasn't reviewed aren't counted at all: 43 of the 53 in the 17 October batch are repository-only.