Node2Shell: Crafted Brotli Data Can Turn a Stock Node.js Build Into Remote Code Execution
A memory-safety bug in the Brotli decoder that ships inside Node.js, tracked as CVE-2026-111497 and nicknamed "Node2Shell", has been shown to give an attacker code execution on an unmodified, official Node.js build. Node.js released patches on October 21. If your services decompress Brotli data from sources you don't trust, update now.
What happened
Imperva's researchers built a proof of concept against the official Node.js 24.22.0 image for Linux arm64. They ran it as shipped: no debugger, no custom build. Sending carefully malformed Brotli input to the bundled decoder was enough to run attacker-supplied code.
Some caveats matter here:
- The result is probabilistic. Not every attempt worked, and getting code execution took several tries.
- The scope tested was narrow. The demonstration covers one build (24.22.0, arm64) and one application setup. The 22.x and 26.x lines and other architectures were not tested for code execution, although the vulnerable decoder is present in all of them.
- This goes further than earlier public work. The first public PoC only achieved code execution on a deliberately weakened build. On standard builds it produced a crash and a limited memory read.
- No in-the-wild exploitation had been reported as of October 22.
Why it matters
The flaw is in the Brotli decoder bundled with Node.js, so it affects the 22.x, 24.x and 26.x release lines. An application can reach the decoder from either side of an HTTP exchange:
- Outbound: your code reads a Brotli-compressed response through Node's built-in
fetch(), for example when calling a third-party API or a server an attacker controls. - Inbound: middleware decodes a Brotli-compressed request body. Express 5's body parsers (
express.json(),express.urlencoded(),express.text(),express.raw()) now decode Brotli alongside gzip and deflate. A plain Node.js HTTP server does not decode a request body by default, so the inbound path needs that middleware or an explicit decompression call.
Code execution was reproduced over both paths, against pinned versions of Express 5.0.0–5.3.0 and body-parser 2.0.0–2.3.0. Not every release in between was tested.
The root cause is a state-handling error. A Brotli stream declares the block types it will use, and the crafted input jumps to a block that the stream's own structure rules out. The vulnerable decoder treats that impossible state as "needs more input" and keeps going. A memory-sanitizer run showed that this leads to an out-of-bounds write. The fix pulls in an upstream Brotli patch, so the decoder now stops with a format error at that point.
What to do
- Upgrade Node.js to the patched release on your line (24.22.1 for 24.x, and the matching fixed releases for 22.x and 26.x). This is the only real fix. Node.js offers no in-runtime workaround.
- Don't forget bundled binaries. Container base images, serverless layers, desktop apps and CI images often carry their own copy of Node, and a package-manifest inventory can miss them. Rebuild and redeploy those artifacts.
- Move off end-of-life lines. Node.js 20.x and earlier will not get this fix.
- Reduce exposure while you patch. Where possible, stop accepting
Content-Encoding: bron request bodies that don't need it, and be careful about fetching Brotli-compressed content from untrusted servers. WAF rules from vendors such as Imperva and Vercel can block known attack patterns in inbound requests. They do not protect thefetch()response path and are no substitute for updating.
Knowing every place a vulnerable runtime is running is often harder than the patch itself. 4tify's vulnerability scanning service helps here. It finds outdated runtimes and components across your hosts and container images, including the ones nobody remembered were there, so a fix like this one reaches every copy.
Research credit: Imperva. Originally reported by The Hacker News.
