If you run Node.js, update to 26.12.1, 24.22.1 or 22.23.4 now. If you run Python, upgrade the Brotli package to 1.2.1 and brotlicffi to 1.2.1.0. If you run Bun, move to 1.4.4. All three carry a fix for CVE-2026-111497, an out-of-bounds write in Google's Brotli decoder that can let a remote, unauthenticated attacker crash a process, read its memory or potentially run code by sending it crafted compressed data. Google's CVE record scores it 9.2 under CVSS v4.0.
The flaw was first known as "Node2Shell" because Node.js shipped the first fix. It is now called "HTTP2Shell" because the bug lives in the upstream Brotli library (versions 1.0.3 through 1.2.0), and Brotli is the compression format behind a large share of web traffic. Anything that decodes Brotli with a vulnerable copy of that library is in scope. Despite the name, this has nothing to do with the HTTP/2 protocol.
The patches are the easy part. The hard part is finding every copy of Brotli you own, because it rarely shows up as an application in its own right. This post covers what the flaw is, how far exploitation has gone, and how to get to full coverage fast.
Preview of an upcoming Tanium capability, shown for illustration and subject to changes. Not yet generally available.

What is Brotli?
Brotli is a lossless compression format developed by Google and standardized as RFC 7932 (opens in a new tab). It is one of the main ways the web shrinks data in transit: a browser or HTTP client says it can accept Brotli (Accept-Encoding: br), and the server sends a compressed response marked Content-Encoding: br. Every major browser supports it, and most web servers and CDNs use it.
The code that unpacks Brotli data is called the decoder. Google's open-source C decoder is compiled into language runtimes, HTTP libraries, proxies and applications, so a single bug in it shows up wherever any of those decompress data they did not create.
What happened
The fix sat in Brotli's public source for eight months before anyone labeled it a security issue. Envoy and Istio shipped fixes earlier in October without drawing much attention. Once Node.js shipped its fix and advisory on October 21, researchers turned it into a working exploit within a day, and the scope kept widening as other ecosystems checked their own copies.
| When (UTC) | Event |
|---|---|
| Oct 24 | Bun's post on X calls the Brotli decoder bug "HTTP2Shell" |
| Oct 23, late | Aikido reports the flaw reaches Python and other languages and renames it "HTTP2Shell" |
| Oct 23, 19:30 | CISA KEV catalog update does not list CVE-2026-111497 |
| Oct 23, 13:26 | Czech national CERT (GovCERT.CZ (opens in a new tab)) warns organizations to patch Node.js (post (opens in a new tab)) |
| Oct 23 | Bun publishes advisory GHSA-663w-cv8g-jj2p; GitHub advisory GHSA-9v8h-qqgm-4m35 covers the PyPI Brotli and brotlicffi packages |
| Oct 22, 16:19 | brotlicffi 1.2.1.0 published to PyPI |
| Oct 22, 13:57 | Python Brotli 1.2.1 published to PyPI |
| Oct 22, 13:35 | Google publishes the CVE record with a CVSS v4.0 score of 9.2 |
| Oct 22 | Imperva demonstrates code execution on the official Node.js 24.22.0 image for Linux arm64; the first public proofs-of-concept appear on GitHub |
| Oct 22, 10:11 | Bun 1.4.4 ships with the fixed Brotli library |
| Oct 21, 23:24 | Vercel deploys WAF rules for its hosted projects |
| Oct 21, ~19:27 | Brotli project releases fixed versions 1.0.10, 1.1.1 and 1.2.1 |
| Oct 21, 10:38 | Node.js ships 22.23.4, 24.22.1 and 26.12.1, crediting OpenAI Codex Security for the report |
| Oct 15 | Istio's security bulletin names CVE-2026-111497, with fixed releases 1.31.2, 1.30.6 and 1.29.9 (bulletin (opens in a new tab)) |
| Oct 6 | Envoy ships fixed releases; its advisory GHSA-3wvj-j6qm-62mr follows on Oct 23 |
| Feb 27 | Fix committed to the google/brotli repository with no advisory or release |
The vulnerability
One bug in one C file reaches every runtime and package that compiles the Brotli decoder in. When the decoder hits a block switch it cannot complete, vulnerable versions fail to tell "this stream is malformed" apart from "send more input" and keep decoding. A crafted stream can then push the decoder to write past a small working buffer and corrupt nearby memory. The upstream fix (google/brotli commit 0ed4268 (opens in a new tab)) stops decoding on the first format error and adds a new error code, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH.
| Component | Affected | Fixed in |
|---|---|---|
Google Brotli library (opens in a new tab) (upstream C) | 1.0.3 through 1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
Node.js (opens in a new tab) (bundled Brotli) | 22.x, 24.x, 26.x before the fix; all end-of-life lines (20.x and older, 23.x, 25.x) | 22.23.4, 24.22.1, 26.12.1 |
| Brotli 1.0.3 through 1.2.0; brotlicffi 1.0.3 up to (but not including) 1.2.1.0 | 1.2.1; 1.2.1.0 | |
| 1.0.21 through 1.4.3 | 1.4.4 | |
| Deployments with the Brotli decompressor configured | 1.40.0, 1.39.3, 1.38.6, 1.37.8 (and 1.36.12, unsupported) | |
| Meshes with Brotli decompression enabled | 1.31.2, 1.30.6 (and 1.29.9, end of life) | |
| Anything else that embeds Brotli 1.0.3 through 1.2.0 | Check with the vendor | Vendor-dependent |
No login is needed, but the app has to decode the attacker's data. That can happen in either direction:
- Inbound: a server decompresses a request body sent with
Content-Encoding: br. Express 5 does this by default throughexpress.json()(body-parser 2.x). Express 4 rejects Brotli bodies. - Outbound: an app fetches a Brotli-compressed response from a server the attacker controls, such as a webhook target, URL preview, crawler or "fetch this URL" feature. Node.js
fetch()decodes Brotli responses automatically, and so do Python's urllib3 (and therefore requests), httpx and aiohttp whenever Brotli or brotlicffi is installed.
The outbound path is why this matters beyond web servers: a malicious server can attack the client, so build agents, scrapers, integration services and developer tools are in scope too. Your code doesn't need to import Brotli to be exposed; an installed package is enough.
The same Node.js release fixes nine other CVEs, including three more rated High (Node.js advisory (opens in a new tab)), and updates the bundled certificate list for the eMudhra CA key compromise. One upgrade covers all of it.
Is it being exploited?
Not that anyone has reported yet, but the gap between lab and stock builds closed in a day. As of October 24, 01:30 UTC, CVE-2026-111497 is not in CISA's KEV catalog, CISA's assessment on the CVE record lists exploitation as "none", and no public indicators of compromise have been released.
The research moved quickly:
- The first public proof-of-concept reached code execution only against a deliberately weakened test build. On stock Node.js it caused a crash and limited memory disclosure.
- Imperva's red team then ran attacker-supplied code on the official, unmodified Node.js 24.22.0 image for Linux arm64, through both
fetch()and Express 5 request parsing. The exploit took several attempts, and 24.22.1 rejects the same input. - One public lab reports crashing unpatched Node.js 20, 22, 24 and 26 over the network. One researcher argues the bad write can't be reached and the real impact is denial of service; that is a single analysis, not the position of Node.js or Google.
- No one has yet published code execution against the Python packages or Bun, but they compile the same decoder code.
Don't read the open debate as permission to wait. A crash alone can take down any service that decodes Brotli from users, exploits mature fast once a fix is public, and fake proof-of-concept repositories for headline CVEs often carry malware. Treat any "Node2Shell" or "HTTP2Shell" exploit, checker or scanner from an unknown source with caution.
Most at risk: internet-facing Node.js services on Express 5, services that fetch content from URLs users supply, and anything still on an end-of-life Node.js line. It's also worth retro-hunting for Node.js and Python processes that exited unexpectedly during decompression, and for inbound requests carrying Content-Encoding: br.
Why finding Brotli is harder than it looks
Brotli almost never appears in an installed-software list, because it is compiled into other things. To answer "am I exposed?" you have to find the things that carry it:
- Node.js runtimes installed through OS packages, official installers, Homebrew, and version managers such as nvm, fnm and Volta, often several per machine and many outside any package database.
- Applications that ship their own Node.js, including desktop apps, developer tools, vendor products and single executable apps. The fix depends on the app's vendor, not on your Node.js upgrade.
- Python environments with Brotli or brotlicffi installed, often as a transitive dependency of an HTTP client or Scrapy. Every virtualenv, conda environment, CI runner and container has its own copy.
- Bun binaries, including standalone executables built with
bun build --compile, which keep the vulnerable decoder until they are rebuilt. - System Brotli libraries (for example
libbrotlion Linux), which some distribution builds of Node.js and other software link against instead of a bundled copy. - Container images, where the runtime version is baked in at build time and stays vulnerable until the image is rebuilt on a patched base.
Two things make it worse. Vulnerability scanners were slow to map the CVE to the Python packages, so some tools did not flag them for the first days. And name matching misleads: the npm package called brotli is a separate JavaScript library and not the vulnerable component.
The reliable answer is to check the runtime or package version where the code actually runs, and compare it with the fixed releases above. Watch for two traps: Node.js still reports process.versions.brotli as 1.1.0 or 1.2.0 on fixed releases, so check the Node.js version itself, and Linux distributions keep the old upstream number when they backport the fix, so compare the full package release with the vendor's advisory.
How to use Tanium to find and fix it
If your organization runs software that decompresses Brotli data, we recommend taking immediate steps to identify affected components and prioritize remediation.
Tanium can show where Node.js runtimes, Brotli libraries and Brotli packages live, which copies are running and which are listening on the network, as far as your sensor, Index and SBOM coverage reaches. Know the gaps: in internal testing, a Node.js runtime inside a Docker container and an unpackaged Node.js under /opt were both missing from Installed Applications and outside Index and SBOM coverage, and showed up only in running processes; Listen Ports also reported "lsof unavailable" on both hosts. Endpoint data can't show whether an application actually decodes Brotli from an attacker, so treat every vulnerable copy as a candidate until its owner confirms the request and response paths.
Step 1: Ask Tanium Atlas
In Atlas, click + New Page and paste this prompt into the chat panel. It carries the fixed versions, names the sensor or exact question for each step so Atlas can finish in one pass, and asks Atlas to show its evidence and its gaps. It looks for installed, unpackaged and containerized runtimes, bundled copies of the decoder, and what is listening on the network. It is read-only and waits for your approval before proposing any action. Atlas needs the Atlas role and the Tanium AI Features setting (Read more in the Tanium Resource Center (opens in a new tab)).
Find which of my endpoints may be exposed to HTTP2Shell / Node2Shell (CVE-2026-111497), the out-of-bounds write in Google's Brotli decoder disclosed in October 2026, including Brotli decoders bundled inside other software and containers, then help me fix them. Use the reference data below, not internet search. This is read-only until I approve: do not create sensors, run actions or deploy anything.
How to work: run the steps in order with the sensor or exact question named; do not look up sensor details or search for sensors. Tally every row with one script; rerun a result capped at 10,000 rows filtered per line, and if printed output is cut off, rerun the tally printing only the totals and every golang entry. Do not rerun a query to add columns; use results as returned. If you run low on steps, write step 6 with what you have and list the steps not finished.
Rules: for each step, name the sensor and give endpoints answered out of the total targeted. An empty result, "No Results Found", "N/A", "lsof unavailable" or an error is a gap, not proof that an endpoint is safe. Never infer a Brotli or runtime version from a file name or path, except a Node.js install directory named node-vX.Y.Z or .nvm/versions/node/vX.Y.Z, reported as "version from install directory"; never report any other version read from a path or file name (for example php 8.4.26, PHP 8.3 directories, perl5/5.40 or GuestConfiguration 1.26.116); name the parent application only, with no version number of any kind in its name or location (write "PHP for Windows", "Perl", "Azure Guest Configuration" or "a Python virtual environment", not "PHP 8.4", "Perl 5.40", "1.26.116" or "Python 3.12 venv"). Report only queries you actually ran.
Format: when every query has run, write the report in one message as your final answer, beginning with "## Step 1", and end your turn with it; the final answer must contain the full report text, never a reply that it was written above. Do not write the report in an earlier message; write it only once, as the final answer. Use a table per step where it helps. Report affected, end-of-life and unresolved rows; give fixed rows as counts. In category 1 follow each host with its version and "end of life" only for Node.js 20 or earlier, 23 or 25, e.g. HYPERV-01 (Node.js 24.15.0). Every count you state must equal the number of items you list with it.
1. Installed software. Ask Installed Applications (columns Name, Version), filtered per line to names matching the case-insensitive regex ^(node\.?js|bun|envoy|istio|google chrome|microsoft edge|mozilla firefox|libbrotli|brotli|python3.*brotli), so names with an architecture suffix such as libbrotli1:amd64 also match. Group by name, full version and the Operating System sensor (not Operating System Generation) with endpoint counts; give Ubuntu, RHEL-family and SUSE one row each, and list every Node.js row, including distribution nodejs packages. Linux: the Debian and Alpine fixes are published, so any lower release, including Debian rebuilds such as 1.1.0-2+b7, is vulnerable and can be patched now; with no listed fix, report "awaiting vendor fix" for RHEL-family, Ubuntu, SUSE, Amazon Linux and Fedora 43 to 45, and "review" for others, including Debian 10 (1.0.7-2+deb10u1) and Debian 11 (1.0.9-2+b2), which have no listed fix. Browsers: report Google Chrome, Microsoft Edge, Microsoft Edge WebView2 Runtime and Mozilla Firefox as separate rows (the regex matches WebView2 through "microsoft edge"); compare major versions. Chrome, Edge and WebView2 below 148, and Firefox below 150 (other than ESR 140.10+ and 115.35+), predate the upstream Brotli fix (category 4); 148+ and 150+ are fixed.
2. Exposure. Ask exactly these two questions:
a) Get Computer Name and Listen Ports and Installed Applications having Installed Applications:Name matches "(?i)^(node\.?js|nodejs|bun|envoy|istio).*" from all machines with Listen Ports:Name matches "(?i)^(node|node\.exe|bun|bun\.exe|envoy|istio-proxy|pilot-agent)$"
b) Get "Computer Name" and "Running Processes With Parent" from all machines with "Running Processes With Parent" matches ".*(node|envoy|docker-proxy|[^a-z]bun[^a-z]).*"
From a), give each listening node, bun, envoy, istio-proxy or pilot-agent process with only the Local Ports at the same list positions as that process, and its installed version; a host matched because a Listen Ports Name is node, bun or envoy even when its Process shows another title (for example clawdbot-gateway); label a listener with no installed version "unpackaged runtime (unknown version)". From b), keep only node, bun, envoy and docker-proxy processes: a host runtime started from a directory such as /opt/node-v24.22.0 is an unpackaged install, so compare that version with the fixes; if Listen Ports reported "lsof unavailable" for its endpoint, its listening state is unknown; a runtime whose parent chain leads to containerd-shim or runc runs in a container (unknown version); docker-proxy -host-ip/-host-port shows a port published from a container (a host IP other than 127.0.0.1 is reachable from the network); a runtime restarted in a loop ("while true; do node ...") should be checked for exploitation crashes.
3. Comply and Patch. Ask each sensor once from cached (TDS) data, not live, without retrying; quote any error. Count endpoints with a Comply - Vulnerability Findings Details row whose Check ID is CVE-2026-111497, by Detected Products, and endpoints with a Patch - Applicable Patch Status by CVE row whose CVE is CVE-2026-111497, by Title and Install Status. The definitions cover Node.js only and need an assessment run; empty results are a gap.
4. Bundled Brotli decoders. Ask exactly: Get "SBOM Package Information Filtered By"["*brotli*","","","","10000"] from all machines
Columns: Name, Version, Type, Parent. Drop encoder-only rows (brotlienc, libbrotlienc, mod_brotli) before counting. Classify every group, not only the largest:
- Go github.com/andybalholm/brotli (one entry per Type golang row, with version and the application named from the Parent file name, never "etc."; list as many entries as there are golang rows): fixed in v1.2.7; worst case likely a crash.
- Container layers: rows whose Parent is under /var/lib/docker, /var/lib/containerd or .local/share/containers, counted separately for Docker, containerd and Podman.
- .NET System.IO.Compression.Brotli or System.IO.Compression.Native, and Python _brotli: one row each, with count and the parent application of every copy (count every row, including rows with the same Parent, so the count equals the copies itemised; match names case-insensitively).
- Other Google Brotli C code (brotlidec, libbrotlidec, brotlicommon), with the parent application (labelled inferred from path): compare a version with the Brotli fixes (1.2.0 is affected, including Pillow's copies, which are rows whose Parent contains pillow.libs (libbrotlidec and libbrotlicommon) and get their own row with their count); with no version, mark it Unresolved and name the vendor that must confirm, in at most four rows grouped by vendor.
- Other ports (Java, Perl, Rust): list without Google's versions.
- Linux /usr/lib*/libbrotli*: decided by step 1; give only the number of non-encoder rows whose Parent starts with /usr/lib or /lib.
5. Unpackaged runtimes. Ask exactly: Get "Index - File Details"["100","*","^(node|node\.exe|bun|bun\.exe|envoy)$","*","*","*","*","*","*","1","0","Directory Path","Ascending"] from all machines
Count only Object Type file rows, with one script that prints only totals grouped by file name and directory, never a truncated listing; table columns: File name, Typical directory, Rows, Version (unknown).
6. Summary. Using only host names already returned by steps 1 to 5 (run no extra queries), put each finding into one category: (1) known vulnerable version, listening or published on the network (a container runtime of unknown version goes in 4), named by Computer Name exactly as returned, with its version; (2) vulnerable Node.js, Bun, Envoy or Linux package, not shown listening, excluding any host already named in (1) (browsers below the fixed major always go in 4); (3) fixed (count only); (4) unresolved: no version (list container runtimes and published container ports first), bundled copy, predates fix, image layer, missing coverage or error. Name the vendor to contact for each unresolved group. Then propose fixes for my review: Patch for OS packages, Deploy for vendor software, rebuilds for bundled copies and images. Wait for my approval before any action.
Fixed versions:
- Google Brotli: 1.0.10, 1.1.1 or 1.2.1 on its branch (1.0.3 to 1.2.0 are affected)
- Node.js: 22.23.4, 24.22.1, 26.12.1 (22, 24 and 26 are supported lines, never end of life; 20 and earlier, 23 and 25: end of life, no fix)
- Bun: 1.4.4
- Envoy: 1.40.0, 1.39.3, 1.38.6 or 1.37.8, or later on the branch
- Istio: 1.31.2, 1.30.6
- Python brotli: 1.2.1; brotlicffi: 1.2.1.0
- Debian 13: 1.1.0-2+deb13u1; Debian 12: 1.0.9-2+deb12u1
- Alpine: 1.2.1-r0 (3.23, 3.24), 1.1.1-r0 (3.21, 3.22)Step 2: Or run the checks yourself
Find installed runtimes and packages. In Interact, start with package inventory on each platform:
Get Computer Name and Installed Applications from all entities with (Installed Applications contains "Node.js" or Installed Applications contains "nodejs" or Installed Applications contains "libnode" or Installed Applications contains "brotli")
Get Computer Name and Homebrew Applications from all entities with (Homebrew Applications contains "node" or Homebrew Applications contains "brotli" or Homebrew Applications contains "bun")
Get Computer Name and Installed RPMs from all entities with (Installed RPMs contains "brotli" or Installed RPMs contains "nodejs")
Get Computer Name and Installed Applications having Installed Applications:Name matches "(?i)^(node\.?js|bun|envoy|istio|google chrome|microsoft edge|mozilla firefox|libbrotli|brotli|python3.*brotli).*" from all machines with Installed Applications:Name matches "(?i)^(node\.?js|bun|envoy|istio|google chrome|microsoft edge|mozilla firefox|libbrotli|brotli|python3.*brotli).*"Find running and bundled copies. Package inventory misses runtimes installed by version managers and those shipped inside other products, such as VS Code Server, Kibana, CI agents and Electron apps. Look at what is running and, where Index is enabled, at the files themselves:
Get Computer Name and Command Line of Process["^(node|nodejs|bun)(\.exe)?$"] from all entities
Get "Computer Name" and "Running Processes With Parent" from all machines with "Running Processes With Parent" matches ".*(node|envoy|docker-proxy|[^a-z]bun[^a-z]).*"
Get Computer Name and Processes Using Module["libbrotlidec"] from all entities with (Is Linux equals True or Is Mac equals True)
Get Computer Name and Processes Using Module["_brotli"] from all entities
Get Computer Name and Index - File Details[100,".*","^(node|node\.exe|nodejs|bun|bun\.exe|envoy|envoy\.exe|libbrotlidec.*|brotlidec.*\.dll|_brotli.*\.(so|pyd))$",".*",".*",".*",".*",".*",".*",1,0,"",""] from all entitiesOfficial Node.js builds, Bun, Envoy and Electron link Brotli statically, so Processes Using Module won't list them; it finds processes that load a shared system library or the Python extension. Running Processes With Parent does show them, including runtimes started inside containers (look for a parent chain through containerd-shim or runc) and ports published by docker-proxy.
Look inside applications with SBOM. Where Tanium SBOM is licensed, find every Brotli copy SBOM can see, including Python _brotli extensions and bundled C decoders that an ecosystem filter misses, then vulnerable Python Brotli packages and the Express 5 apps that decode Brotli request bodies by default:
Get "SBOM Package Information Filtered By"["*brotli*","","","","10000"] from all machines
Get Computer Name and SBOM Package Information Filtered By["*rotli*","","^(1\.0\.[3-9]|1\.1\.0|1\.2\.0)(\..*)?$","python",10000] from all entities with SBOM Has Results contains true
Get Computer Name and SBOM Package Information Filtered By["express","","^5\.","nodejs",10000] from all entities with SBOM Has Results contains true
Get Computer Name and SBOM Package Information Filtered By["body-parser","","^2\.","nodejs",10000] from all entities with SBOM Has Results contains trueCheck Comply coverage before trusting a clean result. Comply maps supported findings to CVEs and can assess configured container registries. The Tanium Vulnerability Library has CVE-2026-111497 with definitions for Node.js only, on Windows, macOS and Linux, and they produce findings only after an assessment runs. The Windows and Linux definitions are marked performance-impacting, so they run only in assessments that include High Resource CVEs. NVD has not published CPEs for this CVE yet, so SBOM and container image assessments, which match by CPE, won't flag it until it does. Treat a missing finding for anything other than Node.js as a gap, not a clean result.
Watch for two version traps. Node.js reports process.versions.brotli as 1.1.0 or 1.2.0 even on fixed releases, because the fix was cherry-picked without changing the header, so check the Node.js version instead. Linux distributions keep the old upstream number when they backport a fix, so compare the full package release with the vendor's advisory.
Step 3: Mitigate at the edge
The main mitigation is to stop Brotli request bodies before they reach a vulnerable decoder, and no endpoint module can do that: Tanium Enforce matches ports, addresses and programs, not HTTP headers. Until you can patch:
- Reject requests with
Content-Encoding: brat your WAF, reverse proxy or ingress. - In Express 5, set
express.json({ inflate: false })and the same onurlencoded(),text()andraw(). This also rejects gzip bodies. - In Envoy or Istio, remove the Brotli decompressor filter.
- In Python, uninstall Brotli and brotlicffi as the environment owner. HTTP clients fall back to gzip; Scrapy and fontTools WOFF2 support break.
- For outbound fetches such as webhooks, link previews and import-from-URL, use an egress allow-list.
As a last resort, an Enforce rule can block a service's inbound port or a node binary by path. That takes the service down and does nothing for outbound traffic.
Step 4: Patch
Most vulnerable copies are bundled inside something else, so patching splits by who owns the copy:
- OS packages: use Tanium Patch for distribution
nodejsandbrotli/libbrotlipackages as vendors publish them. Debian and Alpine have fixes; Ubuntu, RHEL, SUSE, Amazon Linux and Fedora stable did not as of Oct 23. After updatinglibbrotli, restart every process that still has the old library loaded. - Windows Node.js and vendor apps: use Tanium Deploy. Prefer the gallery's version-specific Node.js 22, 24 and 26 packages, which are at 22.23.4, 24.22.1 and 26.12.1. In internal testing the generic Node.js LTS package was still at 24.22.0, which is vulnerable, so check the imported package version before targeting. Apps on Electron 42 or later already carry the fix, because Electron builds Node.js against Chromium's Brotli and Chromium 148 includes the fix; apps on Electron 41 or older need a vendor update. Electron has not issued a statement.
- Owner-managed copies: application teams update nvm, fnm, Volta and Homebrew installs, Python environments and lockfiles, rebuild
bun build --compileexecutables and container images, and redeploy through their normal release process. - Bundled products without a fix: some products that ship their own Node.js or Brotli had no fixed release as of Oct 23. Mitigate them at the edge and track the vendor.
An updated package on disk does not replace a decoder already loaded by a running process.
Step 5: Hunt
The trigger is a compressed body or response inside TLS, so no endpoint sees it arrive. What endpoints can see is the outcome. A real attempt should show repeated crashes of one Node.js, Bun or Envoy service before any code runs, because each failed guess at the memory layout kills the process. A supervisor or a restart loop, such as a container running while true; do node server.js; done, can hide those crashes from crash reporting, so also look for runtimes that keep restarting. Start with recent crashes. These two questions cover Windows only; on Linux, look for core dumps from node, bun or envoy, for example under /var/lib/systemd/coredump or /var/crash:
Get Computer Name and "Application Crashes in Last X Days"[7] from all entities with "Application Crashes in Last X Days"[7] contains "node"
Get Computer Name and Generic Event Log Search["Application","1000","node.exe","168"] from all entities with Is Windows equals TrueThen look for what a payload would do next: node or bun starting a shell, whoami, curl or another download tool, running a binary from a temp directory, or writing to persistence locations such as authorized_keys, cron or systemd units. Tanium Threat Response can watch for these behaviors and, where Threat Response trace tooling is deployed and current, retro-hunt the Recorder from October 21.
A single crash is not proof of an attack. Correlate repeated crashes on one service with WAF or proxy logs for Content-Encoding: br requests in the same few minutes, and with egress logs for Brotli responses from unusual destinations.
Step 6: Confirm
After the update, verify the running runtime version, package release or image digest on every affected instance. Re-run the Tanium inventory and any SBOM or Comply assessments. Confirm that services using bundled decoders were rebuilt and restarted. Investigate any endpoint that did not answer, any assessment that failed, and any application copy the scans could not identify.
A clean scan is good evidence for the software it covered. It is not proof that every Brotli decoder in the environment was found.
What to do now
Work through these in order; the first three matter most today.
- Find every copy, including Node.js runtimes outside package managers, Python environments with Brotli or brotlicffi installed, Bun binaries, and applications that bundle their own runtime. Record the exact version of each.
- Rank by exposure. Internet-facing Express 5 services, anything that fetches URLs supplied by users or third parties, and end-of-life Node.js lines come first.
- Patch to Node.js 26.12.1, 24.22.1 or 22.23.4; Python Brotli 1.2.1 or brotlicffi 1.2.1.0; Bun 1.4.4; or upstream Brotli 1.0.10, 1.1.1 or 1.2.1. Restart every process afterward, because a running process keeps the old code, and rebuild container images, wheels and
bun buildexecutables on the fixed version. - Move off end-of-life Node.js. Node.js 20 and older get no fix. Plan the move to 22.x or 24.x.
- If you can't patch yet, cut off the routes you can:
- Reject requests with
Content-Encoding: brat your reverse proxy, load balancer or WAF. - In Express 5, set
express.json({ inflate: false })(this also rejects gzip bodies, so test your clients). - In Python, uninstall Brotli and brotlicffi; HTTP clients fall back to gzip and deflate. Remove extras such as
httpx[brotli]too, or the next install brings them back. - Run Node.js as an unprivileged user and keep secrets out of its environment.
- Reject requests with
- Don't lean on WAF rules alone. They cover inbound requests only, not Brotli responses your own code fetches, and stripping
brfromAccept-Encodingdoesn't stop a malicious server from sending one anyway. - Hunt. Review recent logs for Node.js, Python and Bun processes that crashed during decompression, and for unusual inbound requests with
Content-Encoding: br. - Ask your vendors. For any product that bundles Node.js, Python or Brotli, ask whether it includes Brotli 1.0.3 through 1.2.0 and when a fix ships.
Where fixes stand (as of Oct 24, 17:30 UTC)
Upstream and language-level fixes are out; Linux distribution packages are still rolling out, so check your vendor before assuming a fix is available.
| Platform | Status |
|---|---|
Node.js official builds (nodejs.org (opens in a new tab)) | Fixed in 22.23.4, 24.22.1, 26.12.1 (advisory (opens in a new tab)) |
| Node.js 25.x, 23.x, 20.x and older | No fix; end of life |
| Docker official node images | Fixed tags 22.23.4, 24.22.1, 26.12.1; rebuild on a fresh pull, since cached base layers may still be old |
| Python Brotli and brotlicffi (PyPI) | Fixed in 1.2.1 and 1.2.1.0 |
| Bun | Fixed in 1.4.4; rebuild bun build --compile executables (release (opens in a new tab)) |
| Envoy · Istio | Fixed on supported branches (see the table above) |
| VS Code Server (remote SSH, WSL, tunnels) | Fixed in 1.143.1, which bundles Node.js 24.22.1 |
| Google Brotli (upstream) | Fixed in 1.0.10, 1.1.1, 1.2.1 (release (opens in a new tab)) |
| Debian 12 and 13 brotli | Fixed in 1.0.9-2+deb12u1 and 1.1.0-2+deb13u1 (tracker (opens in a new tab)); sid unfixed. Debian's nodejs links the system library, so this package carries the fix |
| Alpine brotli | Fixed in 1.2.1-r0 (edge, 3.24, 3.23) and 1.1.1-r0 (3.21, 3.22; 3.22 not yet in Alpine's security database) |
| Ubuntu, RHEL 8/9/10, SUSE, Amazon Linux 2023, Fedora 43 to 45 brotli | No fix yet. Fedora is fixed only in Rawhide (F46) and ELN (Bodhi (opens in a new tab)); Red Hat has a fix only in Red Hat Hardened Images (RHSA-2026:87420) |
| RHEL nodejs:24 and nodejs:26 modules · Ubuntu nodejs | No fix yet |
| Java brotli4j (used by Netty-based stacks) | Fixed in 1.24.0 (Oct 24), which bundles Brotli 1.2.1 (release (opens in a new tab)) |
| Google Chrome · Microsoft Edge and WebView2 | Chrome is fixed in 148 and later (all users from 148.0.7778.96 on May 5); Edge and WebView2 148 and later are inferred fixed from Chromium |
| Firefox · Thunderbird | Firefox 150, ESR 140.10 and ESR 115.35 ship a fixed Brotli (MFSA 2026-30); same root cause inferred |
| Electron apps | Electron 42 and later carry the fix through Chromium 148; apps on Electron 41 and older need a vendor update. No statement from Electron |
| Go github.com/andybalholm/brotli (separate implementation, used by Traefik and others) | v1.2.7 fixes the same logic flaw, which likely causes a crash rather than code execution (issue (opens in a new tab)) |
| curl for Windows (static Brotli) | Fixed in 8.23.0_4 (curl for Windows (opens in a new tab)) |
| Kibana | No fix yet; published builds bundle Node.js 24.21.0, and community issue #299985 is open |
| .NET System.IO.Compression | No Microsoft statement yet (dotnet/runtime #136422 (opens in a new tab)) |
Sources
- Node.js: October 21, 2026 security releases (opens in a new tab)
- Node.js v22.23.4 release notes (opens in a new tab) and v24.22.1 release notes (opens in a new tab)
- Google Brotli v1.2.1 release (opens in a new tab)
- CVE-2026-111497 record (opens in a new tab)
- GitHub advisory for Python Brotli packages (GHSA-9v8h-qqgm-4m35) (opens in a new tab)
- Envoy advisory (GHSA-3wvj-j6qm-62mr) (opens in a new tab) and Istio bulletin (opens in a new tab)
- Imperva: Node2Shell, from Brotli data to remote code execution (opens in a new tab)
- Aikido: Node2Shell evolves to HTTP2Shell (Python packages) (opens in a new tab)
- Aikido: Node2Shell analysis (opens in a new tab)
- Brandefense: Node2Shell / HTTP2Shell timeline and exposure (opens in a new tab)
- Bun v1.4.4 release (opens in a new tab)
- Debian security tracker: CVE-2026-111497 (opens in a new tab)
- Vercel: Node.js security advisory (opens in a new tab)
- Tanium Atlas documentation (opens in a new tab) and Comply vulnerability assessments (opens in a new tab) (on-premises (opens in a new tab))