Node2Shell is the nickname that Imperva and then the wider community attached to CVE-2026-111497, an out-of-bounds write in Google’s Brotli decoder that ships inside Node.js and a long list of other runtimes [2][3][4]. Google rates it 9.2 under CVSS v4 and calls it critical; Node.js rates its own fix as high severity and shipped it on 21 October 2026 in 22.23.4, 24.22.1 and 26.12.1 [1][3].
The honest state of play is different from PHP2Shell in an important way. There is a demonstrated path to code execution on an official Node.js build, but no exploitation in the wild has been reported, the public proof of concept only achieved code execution on a deliberately weakened build, and the researcher who demonstrated the real thing says it did not work on every attempt [3][4]. That makes this a race you can still win with inventory and patching, rather than an incident you are responding to.

Figure 1. The bug lives in the decoder. Exposure depends entirely on what hands that decoder attacker-controlled bytes.
The bug, mechanically
Brotli is a streaming format. A stream announces the block types it will use as decoding proceeds, and a well-formed stream cannot switch to a block type its own structure rules out. In the vulnerable decoder, an invalid block switch was reported internally as a temporary need for more input rather than a permanent format error, so the decoder kept appending incoming bytes to an eight-byte internal buffer [1][5]. That is the out-of-bounds write: bytes past the end of the buffer, corrupting adjacent decoder state [1][2].
The consequences scale with what sits next to that buffer: a crash, disclosure of process memory, or potentially arbitrary code execution [1][2]. Node’s fix imports the upstream Brotli change that returns a format error at that point, and the audit tooling notes the new error code, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH (-17), which is also what makes a safe version probe possible [1][5].
One detail from the disclosure is worth keeping: OpenAI, whose Codex Security team found the issue during red-teaming, says the root cause had already been fixed in the Brotli source in February, but nobody knew it was a security vulnerability and there had been no newer Brotli release since [3]. A fixed upstream commit is not a fixed product.
A naming note, because search results are already confusing: the flaw has also been called HTTP2Shell and Broccoli2Shell, and it is unrelated to React2Shell, CVE-2025-55182, which is a different bug in a different component [5].
Where it is actually reachable
This is the section to read against your own code, because the bug is in a library and most applications never call it directly.
| Reach path | Detail | Off switch |
|---|---|---|
| Request bodies | Express 5 and body-parser 2.x decode Content-Encoding: br by default when inflate is enabled. koa-bodyparser, @koa/bodyparser, koa-body, co-body and @fastify/compress also decode Brotli. Express 4 and body-parser 1.x do not [5] | express.json({ inflate: false }) and the equivalents for urlencoded, text and raw [5] |
| Outbound responses | fetch() through undici, plus axios, node-fetch and got, decode Brotli responses automatically. A service that fetches a user-influenced URL is exposed: webhooks, link previews, Open Graph fetchers, image proxies [5] | None at application level. Runtime patch only [5] |
| Direct node:zlib use | brotliDecompress, brotliDecompressSync, createBrotliDecompress and BrotliDecompress all reach the decoder [5] | Do not pass untrusted bytes to them [5] |
| Compressed files and stored objects | Anywhere an application decompresses a Brotli file supplied by a user, including archives and import pipelines [3] | Validate provenance or decompress in a sandbox |
| Python brotli and brotlicffi | brotli.decompress(), brotli.Decompressor and Brotli.Decompressor are exposure. Compression-only imports never reach the decoder [5] | Do not decompress untrusted bytes |
| Other runtimes and proxies | .NET, PowerShell, Visual Studio, Bun, Envoy and Istio all shipped fixes, because the same decoder is embedded in each [3][5] | Patch the runtime or the linked library |
Two of those rows deserve emphasis. The request-body path is the one most web teams control, and it is a configuration change away from being closed on endpoints that do not need Brotli. The outbound path is the one that catches people out, because a link-preview or webhook feature that fetches an attacker-supplied URL will decompress whatever comes back, and there is no flag in the HTTP client to turn that off [5]. That path only closes with the runtime patch.
Affected and fixed versions
| Component | Fixed in | Notes |
|---|---|---|
| Node.js 22.x | 22.23.4 | Supported line [1] |
| Node.js 24.x | 24.22.1 | Supported line [1] |
| Node.js 26.x | 26.12.1 | Supported line [1] |
| Node.js 20.x and older | No fix | End of life, migrate to a supported line [4][5] |
| Google Brotli (C library) | 1.0.10, 1.1.1, 1.2.1 | Affected range 1.0.3 to 1.2.0 [2] |
| Bun | 1.4.4 | Embeds the decoder [5] |
| Envoy | 1.40.0, 1.39.3, 1.38.6, 1.37.8, 1.36.12 | The 1.36 branch is unsupported [5] |
| Istio | 1.31.2, 1.30.6, 1.29.9 | Embeds Envoy [5] |
| Python Brotli | 1.2.1 | PyPI package [3] |
| Python brotlicffi | 1.2.1.0 | PyPI package [3] |
| .NET runtime | 8.0.33, 9.0.22, 10.0.14, 11 RC3 | Microsoft fixed these on 25 October [3] |
| Visual Studio | 2019, 2022 and 2026 updates | Microsoft rates the flaw critical here [3] |
| PowerShell | 7.4, 7.5, 7.6 | Microsoft rates the flaw important here [3] |
Whether you patch the runtime or the library depends on how the binary is built. If Node links a shared libbrotli, fixing the library can be enough; if Brotli is compiled into the binary, the runtime is what has to move [5]. Container base images, serverless layers and vendored binaries are the usual places where the version you think you deployed is not the version that is running.
Evidence of exploitation in the wild
| Question | Answer | Source |
|---|---|---|
| Has exploitation been observed in the wild? | No. Reporting through 22 October said none had been reported, and the 26 October round-up still described no in-the-wild exploitation [3][4] | THN, SecurityWeek |
| Has code execution been demonstrated? | Yes, on one configuration. Imperva achieved it on the official Node.js 24.22.0 image for Linux arm64, with no debugger or custom build, against both a response the application fetched and a request body processed by Express 5 or body-parser 2 [3][4] | THN, SecurityWeek |
| How reliable is that demonstration? | It did not succeed on every attempt, took several tries in that setup, and only 24.22.0 was tested; the same input was rejected by 24.22.1 and no code ran [4] | THN |
| What about the public proof of concept? | It achieved code execution only against an intentionally weakened build. On a standard build its author reported a crash and a limited memory read, and said a working attack on a normal build was not achieved. Some researchers argue the bug is only usable for denial of service [3][4] | THN, SecurityWeek |
| What does Microsoft think? | Microsoft rates CVE-2026-111497 as elevation of privilege with a CVSS v3.1 score of 8.1, critical for .NET and Visual Studio, important for PowerShell, with an exploitability assessment of Exploitation More Likely [3] | SecurityWeek |
| Is it in CISA KEV? | No, as of the 23 October catalog [6] | CISA KEV |
The gap between those rows is the useful part. A demonstrated RCE on a standard build means this is not theoretical, and Microsoft’s Exploitation More Likely rating means it expects attempts. But no exploitation has been reported, the public tooling does not work on normal builds, and the one credible demonstration needed repetition and a specific architecture. Treat this as a patch-now vulnerability with an evidence-based expectation of exploitation later, not as a compromise indicator today.
Detection
Detection for Node2Shell has two halves: finding the exposed code paths before an attack, and recognising the crash or corruption signatures if one happens.
Finding exposure, which is most of the work
- Inventory the runtime, not the manifest. Node reports the Brotli version it carries:
node -p "process.versions.brotli". Check that on the running image, not in the repository [5]. - Check whether the binary bundles or links Brotli.
ldd $(command -v node)on Linux, orotool -Lon macOS, tells you whether a shared libbrotli can be patched separately or the runtime has to move [5]. - Grep the code for reachable decoders: brotliDecompress, brotliDecompressSync, createBrotliDecompress, and outbound clients that fetch user-controlled URLs. On the Python side, brotli.decompress and the Decompressor classes [5].
- Check the framework versions that decode by default: Express 5 and body-parser 2.x, koa-bodyparser, co-body and @fastify/compress. On Express 5, confirm whether inflate is enabled on routes that accept bodies [5].
- Sweep the deployed artifacts: container digests, serverless layers, sidecars, proxies, .NET and PowerShell hosts, Bun services, Envoy and Istio versions. The audit kit published for this CVE does exactly this and can fail a build when anything is exposed [5].
Recognising an attempt or a success
| Signal | What it looks like | Caveat |
|---|---|---|
| Worker crash and restart | SIGSEGV, exit code 139, core dumps, container restart loops, or a Node process that disappears under load [1] | A successful exploit need not crash. Absence of crashes is not absence of compromise |
| Brotli format errors and decompression failures | zlib errors, unexpected end of input, format errors surfaced by the framework’s body parser | Ordinary malformed client traffic also produces these |
| Memory disclosure signs | Unusual data in responses, logs or error messages that looks like heap contents rather than application output [1][2] | Rare and easy to miss; a memory read may be the only effect |
| Brotli request bodies on endpoints that do not need them | Content-Encoding: br against JSON or form APIs, especially where clients normally send gzip or identity | Some legitimate clients and mobile SDKs do send Brotli |
| Compressed bodies with implausible structure | Malformed streams that fail deep in the decoder rather than at the first byte, or very high ratios followed by a crash | You cannot inspect compressed payloads without decompressing them, and the decompression is the attack |
| Outbound fetch of user-supplied URLs | Link preview, webhook, OG fetcher or image proxy traffic reaching a host the user chose, returning Content-Encoding: br [5] | This is the path with no application-level off switch |
| Post-exploitation behaviour | Unexpected child processes of the Node process, outbound connections, file writes, or new scheduled tasks | The same generic signals you would chase for any RCE |
A note on WAF coverage, because it is often oversold here. A web application firewall can reject Brotli where it is not needed, and Imperva added rules for the attacks it reproduced, but inspecting a compressed body means decompressing it, which is exactly the operation being attacked [4]. Vercel added blocking rules on 21 October for inbound requests, and explicitly noted that they do not cover Brotli responses an application fetches from other servers and do not replace updating [4].
Mitigation
| Measure | Effect | Limits |
|---|---|---|
| Patch Node to 22.23.4, 24.22.1 or 26.12.1 | Removes the flaw in the bundled decoder [1] | Must reach every artifact that bundles a Node binary, including container images and layers [4] |
| Patch Brotli to 1.0.10, 1.1.1 or 1.2.1 where it is a shared library | Fixes the decoder without moving the runtime [2] | Only applies if the runtime links a shared library rather than bundling it [5] |
| Patch .NET, PowerShell, Visual Studio, Bun, Envoy and Istio | Closes the same decoder in each embedded runtime [3][5] | Each has its own release cadence and verification |
| Set inflate: false on body parsers that do not need Brotli | Stops request bodies from reaching the decoder on routes that only accept JSON or forms [5] | Breaks clients that legitimately send br; decide per route |
| Reject Content-Encoding: br at the edge where it is not required | Removes the request-body path before it reaches Node [5] | Does nothing for the outbound response path |
| Do not fetch user-supplied URLs, or isolate the fetcher | Closes the outbound path, which has no application-level off switch [5] | Requires product changes, not just configuration |
| Cap request body size and decompressed output | Reduces resource exhaustion and limits payload size | Does not prevent the out-of-bounds write itself |
| Run Node with least privilege, seccomp and egress restrictions | Turns a successful exploit into a contained one rather than a platform compromise | Containment, not prevention |
| Move off Node 20 and older | There is no fix for end-of-life lines [4][5] | Requires an application upgrade, so plan it rather than defer it |
For teams that cannot patch immediately, the practical order is: turn off Brotli request decoding on endpoints that do not need it, reject br at the edge for those routes, restrict what your services are allowed to fetch, and put the runtime patch on the shortest possible clock. That combination closes the request-body path completely and makes the outbound path the only remaining exposure, which is a much smaller surface to reason about.
After patching, prove it
- Confirm the running artifact, not the build file: exec into the deployed container or serverless runtime and read
process.versions.brotli, or the Node version itself [5]. - Check the image digest and the base image tag, because a rebuilt application on an unchanged base image can still carry the vulnerable runtime.
- Re-run the inventory across sidecars, proxies and build agents. Envoy, Istio, Bun and .NET hosts are easy to forget because they are not in the application repository [3][5].
- Keep a record of which services take Brotli request bodies by design, so the edge rules and the parser settings can be reviewed together rather than drifting apart.
Sources
- Node.js, “Wednesday, October 21, 2026 Security Releases”, including the CVE-2026-111497 entry describing the invalid block switch, the eight-byte buffer and the fixed releases. · https://nodejs.org/en/blog/vulnerability/october-2026-security-releases
- CVE Program record for CVE-2026-111497, including the Brotli affected range, the fixed versions and the CVSS v4 metrics. · https://cveawg.mitre.org/api/cve/CVE-2026-111497
- SecurityWeek, “Node2Shell: Brotli Flaw Triggers Patching Across Microsoft, Node.js, Python Packages” (26 October 2026), covering the wider product scope, Microsoft’s rating, the Python fixes and the state of in-the-wild reporting. · https://www.securityweek.com/node2shell-brotli-flaw-triggers-patching-across-microsoft-node-js-python-packages/
- The Hacker News, “Researchers Demonstrate Node2Shell RCE Exploit on Node.js Build Using Crafted Brotli Data” (23 October 2026), for the Imperva demonstration, its limitations, the public PoC caveats and Vercel’s WAF position. · https://thehackernews.com/2026/10/researchers-demonstrate-node2shell-rce.html
- pt-act, node2shell-cve-2026-111497 response kit, including the reach-path table, the fixed version matrix, the bundling versus linking distinction and the read-only exposure scanner. · https://github.com/pt-act/node2shell-cve-2026-111497
- CISA, Known Exploited Vulnerabilities catalog, as retrieved from the official JSON feed on 23 October 2026, in which CVE-2026-111497 did not appear. · https://www.cisa.gov/known-exploited-vulnerabilities-catalog
The takeaway
Node2Shell is a reminder that the compression libraries underneath everything are part of your attack surface, and that “it is only reachable if you decompress untrusted data” is a smaller qualifier than it sounds. Modern frameworks now decode Brotli by default, and modern applications fetch URLs that users choose. The patch is available and the exploitation is not yet in the wild, which is the best combination a defender gets: a real deadline with no incident attached.
Published for defensive education. Version ranges, reach paths and exploitation evidence are taken from the sources listed above; verify current vendor guidance before acting.