The Node2Shell Brotli RCE, tracked as CVE-2026-111497, started as a routine Node.js security release and turned into a much wider problem within two days. Node.js shipped fixes on October 21; by October 23 OpenAI had pointed out that the same bug lives in Google's Brotli library and reaches Bun, Envoy, Python bindings and curl-based clients. If you ship Node or Python APIs, especially on Azure, upgrading one runtime is the start, not the finish. Our view at Loop is simple: patch the runtime, then go looking for the copies you forgot.
What happened in the Node2Shell Brotli RCE
On October 21, 2026, Node.js released security fixes across its three supported release lines (22.x, 24.x and 26.x); the highest severity in each line is High. The one that matters most sits in Brotli decompression.
According to the Node.js advisory, the streaming decoder mishandled an invalid block switch. Instead of treating it as a permanent format error, it reported a temporary need for more input, and then kept appending input bytes to an eight-byte internal buffer. Anyone who can feed Brotli data into an app that decompresses it with node:zlib can corrupt process memory. The outcome ranges from a crash, to leaking process memory, to remote code execution. Node.js credits Khai Tran and Sylvie Mayer of OpenAI Codex Security with the report.
The fixed releases are 22.23.4, 24.22.1 and 26.12.1. Node.js 20 has reached end of life and, like every end-of-life line, gets no fix.
The story kept moving after the release:
- October 22: Imperva ran code on the official Node.js 24.22.0 image for Linux arm64, with no debugger and no custom build. It did not succeed on every attempt, but it worked. On 24.22.1 the same data was rejected. Imperva did not test x86-64, the 22.x or 26.x lines, or every Express and body-parser release.
- October 23: OpenAI said the flaw affects software such as Node.js, Bun and Envoy, and that it cuts both ways: an unpatched client that accepts compressed input, such as
curl --compressed, could be taken over by a malicious server. OpenAI also noted that the root cause had been fixed in Brotli's source code in February, without anyone realising at the time that it was a security issue.
The CVE record places the bug in Brotli 1.0.3 through 1.2.0 and recommends moving to 1.0.10, 1.1.1 or 1.2.1. As of CISA's October 23 data, the vulnerability was not in the Known Exploited Vulnerabilities catalog and exploitation was listed as "none". Treat that as a window to act in, not a reason to wait.
How attackers reach the decoder
You don't need to call zlib yourself to be exposed. Two very ordinary code paths get there.
Inbound request bodies. Express 5 and body-parser 2.x decompress request bodies sent with Content-Encoding: br, and any client can set that header. A bare Node.js HTTP server does not decode incoming Brotli bodies on its own.
Outbound fetches. fetch() decompresses Brotli responses automatically. A server that an attacker controls, or has compromised, can reach your process through its response. This includes services that never accept outside traffic: an internal worker that calls a partner API or an AI provider still decodes whatever comes back.
The second path is the one teams underestimate. Webhook delivery, link previews, image proxies and AI agents with a browsing or "fetch URL" tool all let a user decide which server your backend talks to.
Patch the runtime, then find the forgotten copies
Bumping Node on your laptop and in package.json covers less than it seems. Brotli is embedded in runtimes, OS packages, language bindings and developer tools. Build an inventory before you call it done:
| Where | What to check | Fixed at |
|---|---|---|
| Node.js on servers and CI runners | node -v |
22.23.4 / 24.22.1 / 26.12.1 |
| Container images | Base image tag | Same versions, pinned |
| Python virtual environments | brotli, brotlicffi |
1.2.1 / 1.2.1.0 |
| Bun | bun --version |
1.4.4 |
| OS packages | brotli package |
Depends on distro |
| Single-executable apps | Embedded Node version | Rebuild on patched Node |
| Developer machines | Git for Windows, curl | Watch vendor releases |
A few details that catch teams out:
- Containers: a Dockerfile pinned to
node:24.22.0stays vulnerable no matter how often you rebuild it. Move the pin to 24.22.1 or later. - Python: advisory database entries were only added on October 23, so dependency scanners may lag. Check installed versions directly.
- Distros move at their own pace: as of October 23, Debian's security tracker listed a fixed brotli package for Debian 12 (bookworm, 1.0.9-2+deb12u1) but still showed Debian 13 (trixie) and unstable as vulnerable.
- Developer tools: as of October 24, the latest Git for Windows release still bundled Brotli 1.2.0.
A quick first sweep across a repo and a host:
node -p "process.version"
bun --version 2>/dev/null
python -m pip show brotli brotlicffi 2>/dev/null | grep -E "Name|Version"
grep -rn "FROM node:" --include="Dockerfile*" .
Patch in order of exposure
If you can't upgrade everything today, sequence the work by who can send you Brotli data:
- Public routes with Brotli-decoding body parsers. Express 5, body-parser 2.x. Any anonymous client can reach these.
- Features that fetch user-supplied URLs. Webhooks, link previews, image proxies, AI agents with browsing tools.
- Internal workers that call third parties. Less likely to be hit, but one compromised partner or provider is enough.
- CI runners and developer machines. Especially scripts that run
curl --compressedagainst external hosts.
The Node.js advisory offers no temporary workaround for this flaw, so there is no switch to flip inside Node itself. Upgrading is the fix.
Azure: verify the running process, don't assume
Patch status on Azure differs by operating system and by service, so check from inside the running app.
- App Service on Linux: Microsoft reports that the updated built-in Node.js and Python images finished rolling out on both the Latest and Standard Platform Release Channels as of October 22.
- App Service on Windows: Microsoft's guidance says the rollout has started through an accelerated safe deployment process; it does not say it is complete. Platform Release Channels do not apply to Windows apps, and Microsoft recommends verifying through the app's
process.version.
Log the runtime version at startup so it shows up in log stream and Application Insights:
console.log(`[startup] node=${process.version}`);
Use the WAF as a bridge. Microsoft says Azure WAF on Application Gateway and Azure Front Door can help mitigate the inbound vector, meaning Brotli-compressed request bodies sent to your app. Turn it on if patching will take days. But it does nothing for responses your app fetches from other servers, and Vercel's equivalent rules explicitly don't cover those either. It buys time; it does not replace upgrading.
Make runtime patching routine
The most telling detail here is OpenAI's: the code fix sat in Brotli for months before anyone recognised the security impact, and AI-assisted bug hunting is what connected the dots. Expect more findings like this, and make the response boring:
- Declare a minimum runtime. Set
enginesinpackage.jsonand fail installs below it:
{
"engines": { "node": ">=24.22.1" }
}
- Pin base images to a patch version and bump them through pull requests, so upgrades are visible and reviewable.
- Rebuild on advisory. Have CI rebuild and redeploy images when a runtime advisory lands, instead of waiting for the next feature release.
- Stay on supported lines. Node.js 20 received nothing this time.
- Keep a runtime inventory of which services run which Node, Python and Bun versions, so the next advisory is a lookup rather than an investigation.
Key takeaways
- Node2Shell (CVE-2026-111497) is a Brotli decoder bug that can corrupt memory and lead to remote code execution; fixed Node.js versions are 22.23.4, 24.22.1 and 26.12.1, and Node.js 20 gets no fix.
- The root cause is in Google's Brotli library, so Bun, Python's brotli and brotlicffi, Envoy, curl and bundled tools need checking too.
- Exposure comes from Express 5 style body parsing and from
fetch()responses, including on internal services. - Patch public Brotli-decoding routes first, then features that fetch user-supplied URLs.
- On Azure, confirm
process.versioninside the running app, especially on Windows; treat WAF rules as a temporary bridge for inbound traffic only. - Pin patch versions, enforce minimum engines and rebuild on advisories so the next one is routine.
