Node2Shell: Node.js Brotli Flaw Enables RCE
Node.js has patched a memory-corruption bug in the Brotli decoder bundled with the runtime. CVE-2026-111497 is an out-of-bounds write that Node.js says can permit remote code execution. The fixed releases are 22.23.4, 24.22.1, and 26.12.1, published on Oct. 21. Imperva’s Red Team independently reproduced code execution against Node.js 24.22.0 on Linux arm64 and confirmed that 24.22.1 rejected the same input.
| Item | Detail |
|---|---|
| CVE | CVE-2026-111497 |
| Component | Brotli decoder bundled in Node.js |
| Impact | Out-of-bounds write; Node.js says it can permit remote code execution |
| Affected lines | Node.js 26.x, 24.x, and 22.x |
| Fixed releases | 22.23.4, 24.22.1, and 26.12.1 |
| Severity | Node.js rates it high; the CVE record’s CVSS 4.0 score is 9.2, critical |
| Exploitation | CISA’s SSVC entry lists exploitation as none and automatable as no, as of Oct. 22; Imperva’s PoC is an independent validation, not evidence of attacks in the wild |
What Node2Shell Actually Is
Node2Shell is Imperva’s name for its analysis of the flaw, not a second vulnerability. The underlying bug is in Google Brotli’s decoder, which Node.js vendors and exposes through its built-in compression APIs, request-body middleware, and fetch() implementation. The CVE record classifies it as CWE-787 (out-of-bounds write) and CWE-252 (unchecked return value). Google lists three affected Brotli ranges: 1.0.3 before 1.0.10, 1.1.0 before 1.1.1, and 1.2.0 before 1.2.1.
The official Node.js advisory rates the issue high and credits OpenAI Codex Security’s Khai Tran and Sylvie Mayer for reporting it, with Matteo Collina and Antoine du Hamel credited for the fix. Imperva’s write-up independently validates the impact and the patch. Imperva’s result establishes code execution for the tested Linux arm64 build and application setups; it does not claim that every Node.js build is equally exploitable. The CVE record is stricter than Node’s label: its CVSS 4.0 vector scores 9.2, critical, with network attack, no privileges, and no user interaction.
That combination does not mean every Node.js service is remotely exploitable. It means a network-reachable decoder path, fed attacker-controlled Brotli, is the condition to look for. A service that never decodes untrusted Brotli has a much smaller problem than one that accepts compressed request bodies or fetches arbitrary URLs.
Why a Compression Bug Reaches Code Execution
Brotli is a compressed stream format with block types that change as decoding progresses. Imperva’s test input reached a block transition that the stream’s own structure made impossible. In the vulnerable decoder, that condition could look like a request for more input instead of a format error, so decoding continued with invalid internal state. Imperva’s sanitizer run showed an out-of-bounds write from that state.
The upstream fix is not a rewrite of the format. Google’s commit, titled “Detect and report invalid stream earlier,” changes the block-switch routines to return BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH and propagates that error instead of continuing. It also adds a sanity check on the decoded meta-block length. Node’s security release carries that commit as a cherry-pick.
This happens inside the native decoder, before application code sees the decompressed bytes. Middleware cannot validate the output first, and a schema check on the request body is too late. The write can corrupt adjacent decoder state; the CVE record says the impact can include memory disclosure, denial of service, or potentially arbitrary code execution, depending on the build and the input.
The Two Main Paths Into Node’s Decoder
The flaw is in Node.js, but the reachable paths are application decisions. Imperva tested both directions of an HTTP exchange:
| Path | How untrusted Brotli arrives | What Imperva tested |
|---|---|---|
| Incoming request body | A parser accepts Content-Encoding: br and inflates the body before the route handles it |
Express 5.0.0–5.3.0 with body-parser 2.0.0–2.3.0; code execution with raw() and json() in the tested configurations on Node 24.22.0 |
| Consumed HTTP response | Node’s built-in fetch() receives a Brotli-compressed response from a server the application does not control |
Code carried in the Brotli response ran on Node 24.22.0; the fixed build rejected it |
| Direct zlib call | Application code passes untrusted bytes to zlib.brotliDecompress(), brotliDecompressSync(), or createBrotliDecompress() |
Not separately listed in Imperva’s endpoint tests; it reaches the same bundled decoder |
Express 5’s migration guide says express.json(), express.urlencoded(), express.text(), and express.raw() support Brotli decompression. Body-parser 2.0.0 added Brotli support, and its inflate option defaults to true. Those details matter because the middleware is a route into the vulnerable runtime, not the source of the flaw. Updating Express or body-parser alone does not replace the Node.js fix.
A bare Node.js HTTP server does not decode an incoming Brotli body just because it receives one. The application has to invoke a parser or the zlib API. On the response side, the application has to fetch a Brotli-encoded response. Both are common, but neither is automatic in every Node service.
Edge behavior changes the exposure. If a CDN or reverse proxy terminates Brotli and forwards uncompressed bytes, Node never sees the compressed body on that hop. If the edge forwards Content-Encoding: br, Node’s parser still decodes it. An inbound WAF also does not protect a response that your server fetches outbound; that path needs the runtime patch or an egress control.
Fixed Versions and What to Verify
| Release line | Fixed version | Notes |
|---|---|---|
| Node.js 26.x | 26.12.1 | Current line; the Oct. 21 security release includes the Brotli cherry-pick |
| Node.js 24.x | 24.22.1 | LTS line; 24.22.0 is specifically vulnerable |
| Node.js 22.x | 22.23.4 | Maintenance LTS line |
Node.js 24.22.0 was released on Oct. 14 with Brotli 1.2.0, which the CVE lists as affected. The fix arrived a week later in 24.22.1. If you deployed 24.22.0 during that window, it is the obvious candidate to check first.
Do not verify the patch by reading the bundled Brotli version string. Node 24.22.1 still reports Brotli 1.2.0 because the project cherry-picked the fix rather than bumping the vendored version. The reliable marker is the Node.js release: 22.23.4, 24.22.1, or 26.12.1 and newer.
Check the runtime inside each artifact, not only the build file. Run node -v in the container image, serverless layer, build agent, or host that actually executes the service. A package inventory can miss a Node binary copied into /usr/local/bin, and a base-image tag can point at a rebuilt layer. End-of-life Node lines do not receive a fixed release; move them to a supported line rather than assuming the decoder is safe.
What to Check in Your Stack
- Patch and redeploy the runtime. Move to 22.23.4, 24.22.1, 26.12.1, or a newer fixed release in the same line. Rebuild container images and verify
node -vinside the running artifact. - Find the Brotli decode paths. Search for
zlib.brotliDecompress,brotliDecompressSync,createBrotliDecompress, andContent-Encoding: brhandling. Include Express body parsers and body-parser configurations, not just direct zlib calls. - Trace the input source. Public endpoints that accept compressed request bodies are the clearest inbound case. Services that fetch third-party URLs, scrape pages, proxy responses, or follow user-supplied links are the outbound case. A trusted internal API is lower risk than an endpoint an attacker can point at a malicious server.
- Check the edge. Determine whether your CDN, load balancer, or WAF decodes Brotli before forwarding. If it forwards
br, the Node service still decodes it. If it decodes at the edge, that decoder needs patching too; moving the parsing boundary does not remove the bug class. - Use temporary controls only as a bridge. Imperva says it has added WAF rules for this CVE. If your application does not need compressed request bodies, body-parser’s
inflate: falserejects compressed bodies instead of decoding them. RejectingContent-Encoding: brat the edge can reduce inbound exposure. None of these fixes an outboundfetch()path or replaces the Node.js patch. - Watch for decoder failures. Unexpected process exits or Brotli decompression errors around untrusted input are worth investigating, but they are not a reliable exploitation signal. Correlate them with the runtime version and the source of the compressed data.
Patching body-parser or Express without patching Node.js leaves the vulnerable decoder in place. A WAF rule can block a known input shape, but the durable fix is the runtime release.
The Patch Gap Is the Supply-Chain Lesson
| Date | Event |
|---|---|
| Feb. 27, 2026 | Google commits the upstream Brotli decoder fix, 0ed4268a4401abcb44720963 |
| Oct. 14, 2026 | Node.js 24.22.0 ships with Brotli 1.2.0 |
| Oct. 21, 2026 | Node.js 22.23.4, 24.22.1, and 26.12.1 ship the cherry-pick |
| Oct. 22, 2026 | CVE-2026-111497 is published; Imperva publishes its analysis |
The upstream fix was available for roughly eight months before Node’s security release carried it. That is not a reason to treat Node.js as uniquely careless; it is the normal failure mode of vendored native dependencies. A runtime can be on its latest release and still contain a decoder snapshot that predates an upstream security fix.
It also explains why the version string is a poor check. Node 24.22.1 contains the fix without changing the Brotli version header. The thing to track is the downstream runtime release, not only the upstream library version in an SBOM.
Bottom Line
If you run Node.js 22.x, 24.x, or 26.x, assume the Brotli decoder is present. The question is whether attacker-controlled compressed data reaches it. Patch to 22.23.4, 24.22.1, or 26.12.1 and newer, then map the inbound request-body and outbound fetch() paths that invoke it. Treat Node 24.22.0 as vulnerable, and do not wait for exploitation reports before replacing it.
CISA’s SSVC entry lists exploitation as none as of Oct. 22, and Imperva’s code execution was a controlled test that took multiple attempts. Those are reasons to verify exposure carefully, not reasons to leave a network-reachable memory-corruption bug in a production runtime.
Related Reading
More in-depth coverage from this blog on closely related topics:
Sources and References
- Node.js — Wednesday, October 21, 2026 Security Releases
- Node.js v22.23.4 release notes
- Node.js v24.22.1 release notes
- Node.js v26.12.1 release notes
- Node.js v24.22.0 release notes
- CVE-2026-111497: Out-of-Bounds Write in Google Brotli Decoder
- Google Brotli: Detect and report invalid stream earlier
- Imperva: Node2Shell: From Brotli Data to Remote Code Execution
- Express 5 migration guide: Brotli encoding support
- body-parser release history: Brotli support in 2.0.0
- Node.js zlib documentation: Brotli decompression APIs
- Node.js 24.22.1 bundled Brotli version header
