Skip to main content
October 23, 20269 min read

The Brotli Flaw Behind Node2Shell and How to Patch Node.js

Get technical support

Which of these can actually be exploited?

A Brotli decoder flaw fixed on October 21 can let crafted data run code in Node.js apps that fetch remote content or inflate request bodies. How to patch.

On October 21 the Node.js project released 22.23.4, 24.22.1 and 26.12.1, fixing ten vulnerabilities across every supported line. The one to act on first is CVE-2026-111497, an out-of-bounds write in the Brotli decoder that Node.js bundles. The advisory rates it high and says it permits remote code execution. A day later Imperva's red team published Node2Shell, a working demonstration of exactly that on the official Node.js 24.22.0 image.

For a team running Node.js, the question that sets the urgency is whether any of its services decode Brotli data that someone else controls. More of them do than you would guess, because the built-in fetch() decodes a Brotli response even when the request never asked for one. We checked that ourselves, and it is why this belongs in this week's work.

The Timeline

DateWhat happened
October 21Node.js ships 22.23.4, 24.22.1 and 26.12.1 with fixes for ten CVEs, CVE-2026-111497 among them
October 21Google releases Brotli 1.0.10, 1.1.1 and 1.2.1 with the fix
October 22NVD publishes the CVE with Google's CVSS 4.0 score of 9.2
October 22Imperva publishes Node2Shell, code execution on the official 24.22.0 image
October 22The Python bindings follow, Brotli 1.2.1 and brotlicffi 1.2.1.0 on PyPI
October 23Debian lists its brotli and nodejs packages as vulnerable, Ubuntu as needing evaluation

Neither the Node.js advisory nor Imperva reports exploitation in the wild so far.

Where the Bug Lives

The flaw is in Google's Brotli library itself, in the decoder source c/dec/decode.c. The CVE record covers versions 1.0.3 through 1.2.0 on all platforms and says a crafted Brotli stream can corrupt the decoder's memory, leak memory contents, crash the process or potentially run code. The fixed library versions are 1.0.10, 1.1.1 and 1.2.1.

Imperva traced the mechanism. A Brotli stream announces the block types it will use as decoding goes on, and their test input reached a block change that the stream's own structure made impossible. The vulnerable decoder read that state as a request for more input and carried on with invalid internal state, and continuing from there wrote past the end of a buffer. The upstream fix makes the decoder stop with a format error at that point.

Official Node.js binaries and the Docker images carry their own copy of Brotli, which is why the fix arrived as a Node.js release. One detail will mislead anyone who checks the wrong number. We ran process.versions.brotli on both images, and 24.22.0 and the fixed 24.22.1 both report 1.2.0. Node.js imported the upstream patch without changing the version string, so the only reliable check is the Node.js version itself.

Distribution packages work differently. Debian's tracker notes that its nodejs package uses the system Brotli library, so on Debian the fix will come through the brotli package rather than through nodejs.

Two Ways a Node.js App Reaches the Decoder

A service can meet Brotli on either side of an HTTP exchange. Imperva reproduced code execution through both.

Reading a response with fetch()

The first path is a service that downloads something and reads the body. Node's built-in fetch() decodes the response according to its Content-Encoding header.

We tested what that means in practice on the official 24.22.1 image. By default fetch() sends accept-encoding: gzip, deflate, with no Brotli in it. When the server answered with Content-Encoding: br anyway, fetch() decoded the body and returned the text. Sending an explicit gzip, deflate header changed nothing. So there is no request header that keeps a hostile server away from the decoder.

That puts these services in scope:

  • Anything that fetches a URL a user supplies, such as link previews, URL checkers, import-from-URL features and scrapers.
  • Webhook and callback handlers that fetch a resource back from an address given in the payload.
  • API clients talking to third parties, where a compromised or hostile upstream controls the response.

Inflating a request body

The second path is middleware that decompresses incoming request bodies. body-parser added Brotli support in 2.0.0, and Express 5's express.json(), express.urlencoded(), express.text() and express.raw() use it. Imperva ran code through raw() and json() on Express 5.0.0 and 5.3.0 and on body-parser 2.0.0, 2.0.1 and 2.3.0, the latest releases when they tested.

Express 4 apps on body-parser 1.x do not inflate Brotli bodies, because the support only arrived in 2.0.0. A plain Node.js HTTP server does not decode a Brotli body just by receiving it either. Decoding needs middleware or an explicit call.

Calling zlib yourself

Both paths end in the same decoder that zlib.brotliDecompress() and zlib.createBrotliDecompress() use. Code that calls those on uploaded files or other data from outside is in the same position, whatever framework sits around it.

What the Demonstration Showed

Imperva's proof of concept made a test application create a marker file it had no code to create, using only the Brotli data it received. It ran on the official Node.js 24.22.0 image in fresh Linux arm64 containers, with no debugger and no special build. The same inputs on 24.22.1 were rejected, no marker appeared, and the server stayed up.

The demonstration has two limits. The exploit did not succeed on every attempt and took several tries in their setup. Imperva also tested one architecture and one release line, so the 22.x and 26.x lines and x86 builds were not demonstrated. The vulnerable code is the same across all of them, which is why the fix covers every line.

The Node.js advisory credits OpenAI Codex Security, Khai Tran and Sylvie Mayer, with the report, and Matteo Collina and Antoine du Hamel with the fix. Imperva's work is an independent check of the impact and of the patch.

The severity labels disagree, and the disagreement is mostly about where each tracker sits. Node.js rates the flaw high. Google, as the CNA for Brotli, gives it 9.2 under CVSS 4.0, which NVD shows as critical while its own analysis is pending. Ubuntu lists a medium priority, and Debian marks its nodejs package unimportant, noting that it uses the system Brotli library. For a service that decodes untrusted Brotli, the demonstrated code execution decides the priority.

The Other Fixes in the Same Release

The Brotli flaw is one of four high-rated issues in the October 21 release, and the other three are a reason to update even for services that never touch Brotli. Two are TLS certificate hostname bypasses, CVE-2026-105483 for an IP address written with fullwidth digits and CVE-2026-106069 for a host that loosely resolves to an IP address. Both matter to any service that makes outbound TLS connections to hosts it does not fully control. The fourth, CVE-2026-105482, is dns.resolve truncating names that contain a NUL character. The 26.x line also gets fixes for a ZIP parsing issue and the Permission Model, so a single update covers all of them.

Not Only Node.js

Because the bug sits in the library, anything that decodes untrusted Brotli with an affected copy of it is exposed in the same way.

Python is the clearest case outside Node.js. The brotli and brotlicffi packages bundle the C library, and urllib3, and with it requests, decodes Brotli responses whenever one of them is installed. Fixed releases are on PyPI as Brotli 1.2.1 and brotlicffi 1.2.1.0.

System libraries are slower. We checked the package metadata, and libcurl on Debian 12 and Ubuntu 24.04 depends on the system libbrotli1, at 1.0.9 and 1.1.0 respectively, both inside the affected range. curl decodes Brotli when asked to with --compressed, and libcurl when an application enables automatic decoding. On October 23 neither distribution had a fixed package yet, so those hosts depend on the distribution trackers.

How to Check Your Services

Start with the Node.js version, read inside whatever actually runs the code.

node -v
docker exec <container> node -v
kubectl exec deploy/<name> -- node -v

Anything below 22.23.4, 24.22.1 or 26.12.1 on its line needs the update. Then look for the two inbound paths and direct calls.

npm ls express body-parser
grep -rnE "brotliDecompress|createBrotliDecompress" --include=*.js --include=*.ts src/

For Python services and for hosts that use the system library, check these as well.

pip show brotli brotlicffi | grep -E "^(Name|Version)"
dpkg -l 'libbrotli*' | grep ^ii

What to Do

Update Node.js to 22.23.4, 24.22.1 or 26.12.1, or later on the same line. A container image needs more than a rebuild when its tag is pinned to an exact version. The tag has to change, and an image on a floating tag such as node:24-alpine still has to be rebuilt and redeployed before the new runtime is what serves traffic. The Node.js project states that end-of-life versions are always affected when a security release ships, so anything on 20.x or older needs to move to a supported line.

If an inbound path cannot be patched today, refusing Brotli request bodies at the proxy closes it in the meantime. Very few clients send Brotli-compressed bodies, but test against your real traffic before relying on it. In nginx, inside the server block:

if ($http_content_encoding ~* "br") {
    return 415;
}

The fetch path has no equivalent stopgap, since the request header does not decide whether a response gets decoded. Services that fetch addresses outsiders control should be first in line for the update.

For Python, upgrade Brotli to 1.2.1 and brotlicffi to 1.2.1.0. For hosts relying on the distribution's libbrotli, apply the brotli update as soon as Debian or Ubuntu publishes it, starting with machines where software fetches URLs from outside with compression enabled.

What We Did on Our Own Site

Our free website checker downloads whatever address a visitor types in and reads the response, and it even asked for Brotli. That is the fetch path exactly, reachable by anyone on the internet. We moved the site's image from Node.js 24.21.0 to 24.22.1 on October 23.

We covered what to prepare before the October 21 Node.js release the week it was announced. The same month brought a similar story in another runtime, two PHP flaws that allow unauthenticated code execution, and the lesson is the same. Runtimes hide in container images and on hosts that no package inventory lists. If you want every runtime you run found, updated and kept that way, that is part of our security and compliance work.

Sources: Node.js October 21, 2026 security releases, Imperva, Node2Shell, NVD, CVE-2026-111497, Brotli v1.2.1 release, body-parser history, Debian security tracker, Ubuntu CVE page, Brotli on PyPI

Or read how we handle it in Security & Compliance.