The PoC Ran on the Official Build: Imperva’s Node2Shell Turns Brotli Into RCE Through fetch and Express

On 22 October 2026, Imperva Red Team published “Node2Shell: From Brotli Data to Remote Code Execution,” an independent validation of CVE-2026-111497 — the high-severity out-of-bounds write in the Brotli decoder bundled with Node.js — with a standalone proof of concept that ran code carried in Brotli data inside applications on the official Node.js 24.22.0 image for Linux arm64, with no debugger and no instrumented binary. The same inputs against 24.22.1 were rejected and created no marker. We read the write-up first-hand, in full. It is the primary source this story was missing: our 22 October post deliberately omitted the circulating Node2Shell RCE claim for lack of a verifiable primary, and our 24 October follow-up tracked the rescoped record and the early Envoy fixes. This is part three: what the bug actually does when it fires.

What Imperva demonstrated

The setup is deliberately unexotic. Fresh containers running the official vulnerable Node.js image, a test application with no code to create any marker file — and after it received the Brotli data, the file appeared. Code carried with that data had run inside the target process. The team used a memory sanitizer separately, to investigate the underlying bug; the sanitizer explained the flaw but was not needed for the code execution itself. That separation matters for how seriously to take the result: the exploit path needs no special build, only the shipped runtime and untrusted compressed bytes.

Two routes into the native decoder were exercised, and the distinction between them is the most deployment-relevant part of the post. A Node.js service can meet Brotli on either side of an HTTP exchange: it may consume a compressed response from another server, or it may decode an incoming request body through middleware. Both operations feed untrusted data into the same bundled native decoder, and Imperva proved both directions. A service using Node’s built-in fetch() to consume an external Brotli response ran the supplied code; on the patched runtime the same input was rejected. Incoming Brotli bodies handled by configured request parsers did the same. A bare Node.js HTTP server, the post notes, does not decode incoming Brotli bodies simply by receiving them — the parser has to be configured to inflate them. Exposure is a property of the application’s data flow, not of merely running Node.

The middleware matrix, read carefully

The tested request-body matrix is Express 5.0.0–5.3.0 and body-parser 2.0.0–2.3.0, pinned in the endpoint tests to 5.0.0 with body-parser 2.0.1 and 5.3.0 with body-parser 2.3.0, using raw() and json(). Both configurations ran the supplied code on 24.22.0 and rejected it on 24.22.1, with the server staying up. Express 5.3.0 and body-parser 2.3.0 were the latest releases at testing time on 22 October. Two caveats travel with the table, and Imperva states both: the team did not test every intermediate release in those ranges, and the vulnerable code is Node’s — the middleware versions identify paths that can invoke the flaw, not the source of it. That sentence is the remediation guidance in miniature: upgrading Express or body-parser changes the route, not the destination. Only the runtime fix closes it. Express 5 documents Brotli decoding in its request-body parsers and body-parser’s release history records support beginning with 2.0.0, so anything in those ranges configured to inflate Brotli is a candidate path — but the patch goes on the Node binary, in every container image and bundled artifact that ships one.

The honest negative is kept, and defenders should price it in. The proof did not succeed on every attempt; execution took multiple attempts in Imperva’s setup. The demonstrated claim is therefore bounded: code execution for the tested Linux arm64 build and application setups, with the fixed build rejecting the same inputs — not a universal exploit, and not tested across every Node build or application. For detection this cuts both ways. Failed attempts are still attempts, and they may surface as decoder errors or unexpected process exits — but Imperva is explicit that neither is a reliable stand-alone sign of exploitation, and both must be correlated with the deployed runtime, the application logs, and the source of the compressed data.

The bug underneath

The technical deep-dive describes a decoder that, on reaching a block transition the stream’s own structure made impossible, treated the condition as a request for more input and kept going with an invalid internal state. Continuing from that state could write beyond an intended buffer — the out-of-bounds write Node disclosed. The upstream Brotli fix makes the decoder return a format error for the invalid transition and pass it to the caller, which is exactly what the patched runtime did in the application tests. Source comparison showed the vulnerable decoder continuing past the invalid stream condition; the sanitizer run confirmed the write. It is a compact,checkable account: invalid input that looks like incomplete input, processed anyway.

On attribution, one note for readers cross-referencing our earlier coverage. Imperva’s write-up credits Matteo Collina with reporting and Antoine du Hamel with the fix — the shorthand that stood at disclosure. Node’s advisory has since been amended, as we traced on 24 October: the current credit names OpenAI Codex Security — Khai Tran and Sylvie Mayer — for reporting, with Collina and du Hamel for fixing. Read the Imperva credit line as the pre-amendment form, not a competing claim.

What we take from it

Three readings, labelled as ours. First, the response-side path is the one most inventories will miss. Request-body parsing is where teams look for decompression risk; an outbound fetch() that transparently consumes a Brotli response from a third party is not obviously an attack surface until a crafted response turns it into one. It is the same point OpenAI’s researchers made from the other direction, carried in our 24 October post: removing br from Accept-Encoding does not stop a server from sending Brotli anyway. Any service that fetches remote content into a vulnerable runtime is exposed regardless of what it advertises.

Second, the middleware table will be misread as a middleware advisory, and that misreading delays the actual patch. Express and body-parser versions in the post are exposure markers: they tell you which deployed applications can reach the vulnerable decoder. The fix versions — 22.23.4, 24.22.1 and 26.12.1, unchanged from our 22 October post — go on the runtime, including runtimes a package inventory cannot see inside container images. Check the Node version inside each deployed image or host first; audit the middleware second, to map where untrusted Brotli reaches it.

Third, flaky exploitation is still a patch-now signal, and a detect-later problem. A proof that needs multiple attempts lowers the odds of any single probe succeeding and raises the odds that probing blends into background decoder-error noise — which is precisely why Imperva warns against treating those errors as a detection strategy. The telemetry worth collecting is not the error but the pairing: vulnerable runtime version plus an application path that inflates untrusted Brotli. That pairing is knowable from the build and the config, without waiting for an attempt to succeed.

Our verification was read-only and external. Imperva’s “Node2Shell: From Brotli Data to Remote Code Execution” (published 22 October 2026, by Ron Masas; modified the same day) was read first-hand in full — the executive summary, the 24.22.0 arm64 marker-file proof, the 24.22.1 rejection, the fetch() response path and the Express/body-parser request-body matrix with its tested pins and stated limits, the bare-server non-finding, the multi-attempt caveat, the block-transition root cause and the format-error fix, the fixed release lines, the disclosure-date and credit statements, and the defender and WAF-response sections. The fixed-version lines were cross-checked against our 22 October post’s stated versions. Brave Search was used for discovery only. We built no container, fetched no crafted Brotli, ran no proof of concept, and contacted no affected system.

Sources: