Ninety-One Million Downloads a Week, Zero Results in OSV: The Four fast-uri Parser CVEs
Between 22:50:47Z and 23:09:45Z on 19 October 2026, the fastify organisation published three parallel releases of fast-uri — v4.2.3, v3.1.9 and v2.4.8 — each carrying the same four security fixes to its own major line. The four GitHub advisories followed between 23:35:07Z and 23:45:38Z, and NVD published all four identifiers in a single burst at 07:17:08Z on 20 October: CVE-2026-110306 (6.1 medium), CVE-2026-110180 (4.8 medium), CVE-2026-110095 (3.7 low) and CVE-2026-110725 (3.7 low).
Those are unremarkable scores. The number that makes this a supply-chain story is a different one: 91,375,712 downloads in the week of 12–18 October 2026. The repository behind that figure has 128 stars. Almost nobody installs fast-uri on purpose — ajv, the JSON Schema validator that pulled 233,123,786 downloads in the same week, declares fast-uri: ^3.0.1 as a direct dependency, and ajv in turn sits under the schema-validation path of a large share of the Node ecosystem. If you validate JSON Schema in production, this parser is in your tree whether or not you have heard of it.
Four ways to disagree with every other parser
All four defects share one shape: fast-uri accepts, rewrites or compares a URI in a way that no other parser does, so an application that checks one representation and uses another gets a policy/use desync. That is the parser-differential class, and it is worth naming because the fix for each one is the same architectural rule.
CVE-2026-110306 is the sharpest. fast-uri percent-decodes the scheme component with the legacy global unescape() before validating it. RFC 3986 section 3.1 does not permit percent-encoding in a scheme at all, so no RFC or WHATWG parser reads %6Aavascript:alert(1) as having a scheme — but fast-uri decodes it and emits a live one. The advisory states the results plainly: normalize('%6Aavascript:alert(1)') returns javascript:alert(1), parse('%6Aavascript:alert(1)').scheme returns javascript, the %uXXXX form behaves identically, and equal('%68ttp://a/', 'http://a/') returns true. An application that screens dangerous schemes on the raw input and then stores or renders the normalised result converts an inert string into an executable javascript:, data: or vbscript: URL — stored or reflected cross-site scripting in the consuming application, which is why this one carries S:C, scope changed.
CVE-2026-110180 is the one most likely to matter to infrastructure rather than browsers. For HTTP-family schemes, canonicalizeHost() hands the parsed host to the WHATWG URL parser and adopts its .hostname without checking that the second parser read the whole input as a host. RFC 3986 permits @ inside the host group and the WHATWG parser strips TAB, LF and CR, so the authority comes back rewritten with no error set: http://a@b@evil.example/ parses to host evil.example, and whitespace-smuggled hosts compare equal to clean ones. The advisory names the exact consumers at risk — denylists, loopback filtering, redirect validation, outbound proxy routing. That is an SSRF and open-redirect primitive sitting underneath host-based policy code, and it is the reason the low CVSS numbers understate this batch.
The remaining two are narrower. CVE-2026-110095 applies RFC 3986 dot-segment removal to a relative reference itself rather than to the merged target path during resolution, where section 5.2.4 actually defines it: normalize('a/../b') returns /b and normalize('../x') returns x, so a document-relative reference silently becomes root-relative and resolve(base, normalize(ref)) points somewhere resolve(base, ref) does not. CVE-2026-110725 affects only callers using the non-default reference: 'suffix' option, where the emitted relative reference keeps a bare colon in its first path segment — the thing RFC 3986 section 3.3 forbids precisely because it makes the string reparse as an absolute URI — so normalize('javascript:alert(1)', { reference: 'suffix' }) returns a live scheme.
The part your scanner does not know yet
We measured the detection gap directly. Eleven hours after the advisories published, querying OSV for fast-uri at 2.4.7, 3.1.8 and 4.2.2 — the last vulnerable release of each line — returned zero vulnerabilities for all three, and all four GHSA identifiers returned HTTP 404 from the OSV API. That will resolve on its own schedule, and the point is not that OSV is broken. The point is operational: a dependency this deep is reached by scanners, not by humans reading release notes, and for the window between publication and ingestion the scanner is the only thing looking — and it is reporting clean. If your upgrade decision is gated on a dashboard turning red, this batch is a demonstration that the dashboard is a lagging indicator. We saw the same lag measured against a renamed module path in the Gitea 28.2.0 batch, and the same reliance on ecosystem plumbing in the Malfex npm dependency gap.
Credit is recorded in the release notes: @zekth reported the scheme, host and relative-reference issues; @UlisesGascon found the suffix-reference issue and reviewed the remediations. The maintainers did this well — three majors patched within nineteen minutes of each other, a release body that names each CVE with its GHSA link and a one-line statement of what changed, and advisories that include reproducible one-liners and honest scope limits. The friction in this story is entirely downstream of the project.
What to do
Upgrade to 2.4.8, 3.1.9 or 4.2.3 depending on your line; all three are published and the fix content is identical. Because almost no one depends on fast-uri directly, the practical step is npm ls fast-uri rather than a search of your manifest — expect to find it under ajv, and resolve it there. Caret ranges on ^3.0.1 and ^4.x will take the patch on a fresh install, but a committed lockfile will not move until you make it, which is the usual reason a transitive fix sits unapplied for months.
If you cannot upgrade immediately, the advisories give precise workarounds: reject input whose scheme part contains %; reject authorities containing more than one @ or any TAB, CR or LF; treat any non-empty parse(x).error as fatal; resolve a relative reference against its absolute base before normalising, never after; and avoid reference: 'suffix' on untrusted input. The durable rule underneath all four is one sentence: screen the parsed result you are going to use, not the raw string you were given — and never let one parser validate what a second parser will re-read. None of the four are in the CISA KEV catalogue (checked directly, catalogue version 2026.10.16, 1,748 entries, zero hits); no exploitation has been reported.
Our verification was read-only. The four CVE identifiers, their NVD publication timestamp of 2026-10-20T07:17:08Z, vulnStatus values, CVSS 3.1 scores and vectors and CWE assignments were read first-hand from the NVD 2.0 API. The four GHSA identifiers, severities, affected version ranges, publication timestamps and full advisory bodies — including every quoted example expression and workaround — were read first-hand from the GitHub security-advisory API for fastify/fast-uri. The v4.2.3, v3.1.9 and v2.4.8 release timestamps and the complete v4.2.3 release body, including the per-CVE summaries and the reporter credits, were read first-hand from the GitHub releases API. Weekly download counts for fast-uri (91,375,712) and ajv (233,123,786) for 12–18 October 2026, the npm publish timestamps for each patched version, and ajv 8.17.1’s declared dependency on fast-uri ^3.0.1 were read first-hand from the npm registry and downloads APIs. Repository metadata (128 stars, 78 forks) was read from the GitHub API. The OSV results — zero vulnerabilities returned for versions 2.4.7, 3.1.8 and 4.2.2, and HTTP 404 for all four GHSA identifiers — were measured directly against the OSV API at the time of writing. KEV status was checked directly against the published catalogue. Brave Search was used for discovery only. We installed nothing, ran no version of fast-uri, evaluated none of the quoted expressions and sent no third-party request beyond reading the cited public records.
Sources:
- GHSA-w74x-v6f9-j3xg — CVE-2026-110306, URI scheme spoofing via percent-encoded scheme decoding (6.1 medium)
- GHSA-wjr9-j795-jxcr — CVE-2026-110180, host confusion via unchecked WHATWG host canonicalization (4.8 medium)
- GHSA-xhq7-xmqj-769h — CVE-2026-110095, improper path equivalence in relative reference normalization (3.7 low)
- GHSA-333x-w4r6-6crp — CVE-2026-110725, scheme injection via unescaped first-segment colon in suffix references (3.7 low)
- fastify/fast-uri — v4.2.3 release notes (19 October 2026; per-CVE summaries and reporter credits), with v3.1.9 and v2.4.8
- NVD — CVE-2026-110306 record (published 2026-10-20T07:17:08Z; CWE-172, CWE-180)
- CISA — Known Exploited Vulnerabilities catalogue (none of the four listed; checked 20 October 2026, 1,748 entries)
- al-ice.ai — Gitea 28.2.0, twelve CVEs and the advisories that did not follow the module rename
- al-ice.ai — Malfex and the fourteen-month npm postinstall nobody advised on
- al-ice.ai — Trail of Bits on twenty-five package managers and repository integrity