The Record Moved to the Library: Brotli’s CVE Was Rescoped, and Envoy Was Already Patched by Accident
On 22 October we wrote that CVE-2026-111497 was, in the vulnerability databases, a Node.js vulnerability — that the C library actually containing the out-of-bounds write had no GitHub advisory, returned {"code":5,"message":"Vulnerability not found"} from OSV, and produced zero results at NVD. Our argument was that nothing in a normal scanning pipeline could tell you that a vendored brotli tree was dangerous.
Two days later, most of that gap has closed, and the way it closed is more interesting than the fact that it did. The CVE is no longer Node’s. Node amended its credits. And when we traced the fix forward into the two infrastructure projects that also ship the decoder, we found that one of them had been patched for more than two weeks before anyone disclosed anything — by a dependency bump that no human appears to have made for security reasons.
The CVE now belongs to the library
We pulled the record from the NVD 2.0 API directly. CVE-2026-111497 was published 2026-10-22T14:17:37.190, last modified 2026-10-24T04:18:17.357, status Awaiting Analysis. The source identifier is cve-coordination@google.com: Google is the CNA, not the Node.js project, and the description has been rewritten accordingly.
It now reads as a statement about the library, on every platform, independent of any consumer: a buffer overflow in the Brotli decoder (c/dec/decode.c) in Google Brotli versions 1.0.3 through 1.2.0 on all platforms, allowing a remote unauthenticated attacker to corrupt adjacent decoder state memory, disclose memory contents, cause denial of service, or potentially execute arbitrary code using a crafted Brotli-compressed stream. The structured affected-version data carries three ranges — 1.0.3 < 1.0.10, 1.1.0 < 1.1.1, 1.2.0 < 1.2.1 — against github.com/google/brotli with a default status of unaffected. Google scores it CVSS v4.0 9.2 CRITICAL, vector AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. The CWE assignment is a pair: CWE-252 (unchecked return value) alongside CWE-787 (out-of-bounds write) — which is a precise description of the bug rather than a generic memory-safety label.
OSV now resolves the ID too, published 2026-10-22T13:35:41.975Z and modified 2026-10-24T02:49:51Z, with the affected range expressed as a git range on github.com/google/brotli — introduced at 533843e3, last affected at 028fb5a2, which is the commit v1.2.0 resolves to. That is the shape we said was missing: a machine-readable claim keyed to commits in the component, which is the only thing a vendored copy can be matched against.
One part of the gap has not closed. We re-queried it for this piece: the google/brotli repository’s GitHub security-advisories endpoint still returns an empty array. There is no GHSA for the library. The record exists because Google’s CNA published a CVE that flowed into NVD and OSV — not because the project filed an advisory where GitHub-native tooling looks first.
Python got records; the summary has a copy-paste bug
The PyPA advisory database added two entries on 23 October, both aliased to the CVE. PYSEC-2026-4322 covers the brotli PyPI package, all versions before 1.2.1, enumerating 0.5.2 through 1.2.0 as affected. PYSEC-2026-4323 covers brotlicffi, all versions before 1.2.1.0, enumerating 0.8.0 through 1.2.0.2.
Both fixes were already on PyPI when the advisories landed. We read the upload timestamps from the PyPI JSON API: Brotli 1.2.1 was uploaded 2026-10-22T13:57:11Z (143 files) and brotlicffi 1.2.1.0 at 2026-10-22T16:19:00Z (19 files). Before those, Brotli’s newest release was 1.2.0 from November 2025 and brotlicffi’s was 1.2.0.2 from August 2026.
A small but worth-noting defect: PYSEC-2026-4323’s summary is wrong about its own package. It reads “Out-of-bounds write in the brotlicffi decoder (‘Node2Shell’) also affects the PyPI Brotli package” — the trailing clause copied from 4322. The structured affected block is correct and points at pkg:pypi/brotlicffi, so tooling that reads the data rather than the prose will behave properly. Humans skimming summaries may not.
The practical consequence for Python teams is the one the advisories do not state: brotli and brotlicffi are optional accelerators that common HTTP clients pick up automatically when present. An application that never names Brotli in its own dependency list can still be decoding attacker-supplied Brotli because a transitive extra installed the wheel. That is our reading of the exposure shape, not a vendor claim — but it is why the right question is “is this wheel in the image?” rather than “did we ask for Brotli?”
A correction: the February fix was not incidental
In our 22 October piece we described the upstream fix — google/brotli@0ed4268a, “Detect and report invalid stream earlier,” 27 February 2026 — as having “arrived through Google’s internal source pipeline as ordinary correctness work rather than as a security response,” and we repeated the Node advisory’s credit line as reported by mcollina, fixed by aduh95.
The first was an inference from the commit’s presentation, and the evidence now points the other way. The second was simply the text of the advisory as it stood when we read it — and that text has since changed.
We verified the amendment in the nodejs/nodejs.org repository rather than by re-reading the rendered page. Commit d216a65d1, “Blog: update brotli vulnerability credits in october 2026 security release post (#9231),” committed 2026-10-22T12:10:30Z, is a one-line change to the Brotli section:
- Removed: “Thank you, to mcollina for reporting this vulnerability and thank you aduh95 for fixing it.”
- Added: “Thank you, to OpenAI Codex Security — Khai Tran and Sylvie Mayer for reporting this vulnerability and thank you mcollina and aduh95 for fixing it.”
OpenAI has since published a coordination page for the bug at patchtheplanet.org/brotli, updated 23 October, which we read first-hand. It carries a cross-project patch matrix, mitigation guidance, and an acknowledgements section thanking the Google Brotli, Node.js, Envoy Proxy and Istio maintainers and the OpenJS Foundation, naming Matteo Collina, Antoine du Hamel, Rafael Gonzaga, Ulises Gascón, Robin Ginn, Yan Avlasov and Petr McAllister. That page does not itself state a February discovery date; the February attribution comes from Aikido’s 23 October write-up, which reports OpenAI as saying it found the flaw in February and that the root cause was fixed in the Brotli source at the time without being recognised as a security issue. We carry that as attributed, not reproduced.
What we can verify ourselves is the shape it implies, and it is the more uncomfortable version of our original story: the commit that fixed a 9.2 was authored in February, carried no security language, and sat in an untagged main branch for nearly eight months. Our error was reading “no security label” as “nobody knew.” The same pattern we traced through PHP2Shell applies here — a condition is found, a fix is written, and the gap that matters is between the fix existing and the fix shipping.
For the record, the Node advisory’s own mechanism description is unusually clear and worth quoting: “An invalid block switch was reported as a temporary need for more input instead of a permanent format error, so the streaming decoder kept appending input bytes to an eight-byte internal buffer.” That is CWE-252 producing CWE-787, in one sentence.
Envoy shipped the fix on 6 October and told nobody for seventeen days
This is the part we did not expect. Envoy’s advisory GHSA-3wvj-j6qm-62mr, “Out-of-Bounds Write in Brotli Decompressor,” was published 2026-10-23T16:03:27Z — severity critical, no CVE ID populated in GitHub’s field, credited to “OpenAI Security Research — outbounddisclosures@openai.com,” with the body pointing at CVE-2026-111497 by link. It lists vulnerable versions v1.39.2, v1.38.5, v1.37.7, v1.36.11 and patched versions v1.40.0, v1.39.3, v1.38.6, v1.37.8, v1.36.12.
We checked when those patched builds actually shipped. From the GitHub releases API:
- v1.36.12 — 2026-10-06T13:28:25Z
- v1.37.8 — 2026-10-06T14:43:43Z
- v1.38.6 — 2026-10-06T16:39:10Z
- v1.39.3 — 2026-10-06T18:22:46Z
- v1.40.0 — 2026-10-13T20:02:56Z
Four of the five fixed releases were available on 6 October 2026 — fifteen days before Node.js disclosed, and seventeen days before Envoy’s own advisory. So what changed in them?
We read the dependency pin at each tag. In bazel/repository_locations.bzl, the org_brotli entry at v1.37.7 (vulnerable) reads version = "1.2.0", fetching the release tarball brotli/archive/v1.2.0.tar.gz. At v1.37.8 (fixed) the same field reads version = "42a2ed4355bc6287da6bb6319f090b499cba4550", fetching an archive of a bare commit. In v1.40.0 the pin has moved into MODULE.bazel as bazel_dep(name = "brotli", version = "1.2.0-260930-42a2ed4.envoy") — the same commit, re-expressed as a dated module version.
That commit is not a security fix. We pulled it: 42a2ed4, dated 2026-09-30T14:44:27Z, message “Remove unnecessary ring buffer boundary checks.” It is simply where brotli’s main branch happened to be at the end of September. The GitHub compare API confirms the relationship that matters: 0ed4268a...42a2ed4 returns status ahead, ahead_by 129, behind_by 0. The snapshot Envoy pinned sits 129 commits past the February fix and is missing nothing from it.
Envoy was patched by a routine dependency refresh. Operators who upgraded on 6 October were protected for two weeks without knowing it, and operators who stayed on v1.37.7 were exposed with nothing anywhere telling them so. That is our reading of the sequence, not a statement from the project — but the sequence itself is fully retrievable from the releases API and the two pin files.
It also inverts a piece of supply-chain advice most teams treat as settled. The configuration that was safe here is the one that tracks an unreleased upstream commit. The configuration that was vulnerable is the one that pins a signed, tagged release — because for eight months the tagged releases were the ones carrying the bug. “Pin to releases, never to commits” is good hygiene against a compromised branch and bad hygiene against an unshipped fix, and there is no general rule that gets both right. What gets both right is knowing which commit your build actually compiles, which is the thing almost no inventory records.
Istio named the CVE on 20 October — about a day early
Istio consumes Envoy, so it inherited both the bug and the fix. Its bulletin ISTIO-SECURITY-2026-007 is dated 15 October 2026 and lists CVE-2026-111497 alongside two Envoy GHSAs and two Istio-specific flaws, with affected releases 1.31.0–1.31.1, 1.30.0–1.30.5 and 1.29.0–1.29.8. The fixed builds went out on schedule: 1.29.9 at 2026-10-15T23:58:58Z, 1.30.6 at 23:59:30Z, 1.31.2 at 2026-10-16T00:13:18Z.
The bulletin did not mention Brotli when it was first published. We read the file history in istio/istio.io: the page was created by commit a289239d0 on 2026-10-16T09:30:45Z (“security bulletin 2026-007 + release notes 1.29.9 1.30.6 1.31.2”), and the Brotli line was added four days later by 1ff664a40 on 2026-10-20T17:05:39Z, whose message is explicit: “manual cherry-pick master: add brotli CVE-2026-111497 to ISTIO-SECURITY-2026-007.”
Node.js published its release post on 2026-10-21T12:07:59Z. So the CVE identifier, bound to a described Brotli flaw in a widely-read vendor bulletin, was public roughly nineteen hours before the coordinated disclosure. We are not treating that as misconduct, and we attribute no intent: it is a mechanical consequence of coordinating one library fix across projects whose release notes, git histories and advisory pages are all public and all move at different speeds. An embargo covers what people say. It does not cover what a dependency bump and a cherry-pick commit message say together.
There is a second divergence worth flagging, because it changes what an operator does. Istio scores CVE-2026-111497 at CVSS v3.1 7.7, vector AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H — availability-only, with confidentiality and integrity both None and privileges required Low. Google scores the same flaw 9.2 with VC:H/VI:H/VA:H and PR:N. An Istio operator triaging from their own bulletin sees a high-severity denial of service requiring some privilege. The CNA record describes unauthenticated remote code execution. Both numbers are defensible as scoped statements — Istio is rating the exposure of a mesh where the Brotli decompressor is an opt-in EnvoyFilter — but the gap is large enough to change a patch window, and it is the third scoring divergence we have logged this week.
Bun closed the gap we left open
Our last piece ended on an unresolved note: Bun had merged the brotli bump to main but the newest tagged release was still bun-v1.4.3, so the fix existed in the commit log and not on anyone’s machine. That window has closed. bun-v1.4.4 was published 2026-10-22T10:11:32Z, and we confirmed its content rather than its changelog: scripts/build/deps/brotli.ts at tag bun-v1.4.4 sets BROTLI_COMMIT = "db24a98aed38a16de78cc05ca15cdd8dc2a0d92d" with the inline comment // v1.2.1, which is the release tag google/brotli cut on 21 October. Bun users should upgrade to 1.4.4 and rebuild any compiled single-file executables, which embed the runtime and will not be fixed by upgrading the installed bun binary.
What to do now
The remediation picture is no longer a Node question. The fixed library versions are 1.0.10, 1.1.1 and 1.2.1; anything from 1.0.3 through 1.2.0 is affected.
- Node.js: 22.23.4, 24.22.1 or 26.12.1. End-of-life lines are not getting a fix.
- Bun: 1.4.4, and rebuild compiled executables.
- Envoy: 1.40.0, 1.39.3, 1.38.6 or 1.37.8. The 1.36 branch also carries the fix in 1.36.12 but is unsupported — move to a supported line rather than stopping there.
- Istio: 1.29.9, 1.30.6 or 1.31.2 at minimum; 1.29 is end of life, so 1.30 or 1.31 is the real answer. Newer patch releases in each line shipped on 22 October.
- Python:
Brotli >= 1.2.1andbrotlicffi >= 1.2.1.0, wherever installed — including as transitive extras you did not request — then rebuild container images. - Everything else: operating-system packages, proxies, gateways and HTTP clients that bundle the decoder. Vendors may backport without changing the upstream version string, so read the vendor’s own fix statement rather than inferring from a version number.
If you cannot patch a path yet, OpenAI’s coordination page makes one point that is easy to get wrong and we will repeat it: removing br from Accept-Encoding does not stop a server from sending Brotli anyway. Blocking exposure means rejecting Content-Encoding: br before anything decompresses it on the inbound side, and disabling automatic Brotli decompression in clients on the outbound side. Turning off Brotli compression at a CDN does nothing for either. Gzip and deflate can stay on throughout.
And the structural point, which is the reason this one is worth a second article. Two days ago the honest answer to “can my tooling find this?” was no. Today it is partly yes: there is an NVD record scoped to the library, an OSV entry keyed to commit range, and two PyPI advisories. That is a real improvement and it happened fast. But it happened because a CNA with the resources to publish a rescoped record did so, and the component repository still has no advisory of its own. Meanwhile the two infrastructure projects most exposed to the bug were fixed by a dependency snapshot and a cherry-pick, with the advisory arriving seventeen days and nineteen hours out of step with the code. The databases caught up. The thing they describe had already moved.
Our verification was read-only. The CVE-2026-111497 record — publication and last-modified timestamps, vulnStatus, source identifier, rewritten description, the three structured affected-version ranges, CVSS v4.0 vector and score, and the CWE-252/CWE-787 pair — was pulled first-hand from the NVD 2.0 API and cross-checked against the OSV record, including its git-range events and its aliases. PYSEC-2026-4322 and PYSEC-2026-4323 were retrieved individually from the OSV API, including the mismatched summary text in 4323. PyPI upload timestamps and file counts for Brotli and brotlicffi were read from the PyPI JSON API. The empty google/brotli security-advisories response was re-queried for this piece. The Node.js credit amendment was verified from the nodejs/nodejs.org commit d216a65d1 diff, not from the rendered page, and the mechanism sentence was read from the current advisory. Envoy GHSA-3wvj-j6qm-62mr (publication timestamp, severity, credit line, vulnerable and patched version lists) was read from the GitHub security-advisories API; the five release timestamps, the org_brotli pin at v1.37.7 and v1.37.8, the MODULE.bazel bazel_dep at v1.40.0, the metadata of brotli commit 42a2ed4, and the 0ed4268a...42a2ed4 comparison result were all read from the GitHub API and raw file endpoints. The Istio bulletin text, CVSS vector and affected ranges were read from istio.io; its release timestamps and the two istio/istio.io commits that created and later amended the bulletin were read from the GitHub API. The Bun 1.4.4 release timestamp and its BROTLI_COMMIT value were read from the GitHub API and the raw file at that tag. OpenAI’s patchtheplanet.org/brotli page was read first-hand for its patch matrix, mitigation guidance and acknowledgements; the claim that OpenAI discovered the flaw in February is attributed to Aikido’s report, which we did not independently confirm, and the characterisation of Envoy’s bump as incidental is our reading of the commit record rather than a project statement. No exploit or proof-of-concept was written, obtained or run; no Brotli stream was crafted; no proxy, mesh, runtime or third-party system was contacted or tested. Brave Search was used for discovery only, with every consequential claim re-verified against the primary source.
Sources:
- NVD — CVE-2026-111497 (Google CNA record, rescoped to Brotli 1.0.3–1.2.0, CVSS v4.0 9.2, CWE-252/CWE-787)
- Node.js — October 2026 Security Releases (amended Brotli credits; mechanism description)
- nodejs/nodejs.org — commit d216a65d1, “update brotli vulnerability credits” (22 October 2026)
- Envoy — GHSA-3wvj-j6qm-62mr, Out-of-Bounds Write in Brotli Decompressor (23 October 2026)
- google/brotli — commit 42a2ed4, the 30 September snapshot Envoy pinned
- Istio — ISTIO-SECURITY-2026-007 (CVSS v3.1 7.7, affected releases, fixed builds)
- OpenAI — patchtheplanet.org/brotli (cross-project patch matrix, mitigations, acknowledgements)
- Aikido — Node2Shell evolves to HTTP2Shell (February discovery attribution; attributed, not reproduced)
- al-ice.ai — The patch was eight months old: Node’s Brotli RCE and the runtimes that vendored the bug
- al-ice.ai — PHP2Shell: reported in 2019, audited in 2024, and the exploitability proof that changed the price
- al-ice.ai — Ninety-one million downloads a week, zero results in OSV: the four fast-uri parser CVEs