Node2Shell is CVE-2026-111497, an out-of-bounds write in the Brotli decompression code compiled into Node.js. A crafted Brotli stream can crash the process, leak memory or, in the worst case, run code. Node.js fixed it on 21 October 2026 in versions 22.23.4, 24.22.1 and 26.12.1. Release lines that have reached end of life, including Node.js 20 and older, do not receive the fix.
The bug is not new. It sat in the Brotli library for eight years before anyone noticed, and it reaches Node.js applications through ordinary features: accepting compressed request bodies and decompressing responses from other servers. That is why it is worth understanding rather than just patching.
Node2Shell at a glance
| Item | Detail |
|---|---|
| CVE | CVE-2026-111497 (Node2Shell) |
| Component | Brotli decoder bundled in Node.js |
| Bug class | Out-of-bounds write |
| Severity | Rated high by Node.js; rated critical by Google |
| Fixed in | Node.js 22.23.4, 24.22.1, 26.12.1 (and Bun 1.4.4) |
| Not fixed | End-of-life lines: Node.js 20, 21, 23, 25 and older |
| Introduced | Brotli 1.0.3, 26 February 2018 |
| Found by | OpenAI Codex Security (Khai Tran, Sylvie Mayer) |
Timeline: an eight-year-old bug
| Date | Event |
|---|---|
| 26 February 2018 | Bug introduced in Brotli 1.0.3 |
| 27 February 2026 | Fix released in the upstream Brotli project |
| 21 October 2026 | Node.js security release ships the fix in 22.x, 24.x and 26.x |
| 22 October 2026 | Public proofs of concept available |
| 23 October 2026 | Not yet on CISA's KEV list; no confirmed in-the-wild exploitation reported |
The gap between the upstream fix in February and the Node.js release in October is the part worth noticing. A fix in a library is not a fix in the software that bundles it. Node.js compiles Brotli into its own binary, so applications stayed exposed until Node.js itself shipped a release, however long the library fix had been public. The same pattern applies to OpenSSL, zlib and every other library a runtime vendors.
The October Node.js release was not only about Brotli. It addressed ten CVEs across the core and bundled dependencies including c-ares, OpenSSL, SQLite, Undici and a certificate-authority change. Upgrading for Node2Shell closes the others too.
The root cause, explained simply
Brotli compresses data as a series of blocks, and a stream declares how many block types it will use. According to Miggo's analysis, the decoder used a single return value to mean two different things: wait for more input, and this stream is invalid. A malicious stream declares one block type and then switches types anyway. Because the decoder cannot tell the error apart from a normal pause, it keeps going with corrupted bookkeeping and eventually writes past the end of its buffer.
Writing past a buffer is how memory-safety bugs become code execution. The attacker controls the data being written, and with enough care they can steer it over something the program trusts. Imperva's researcher Ron Masas, who named the issue Node2Shell, showed that this is achievable against a real Node.js build.
Who is actually reachable?
Having Node.js installed is not enough to be exploitable. The attacker has to get malformed Brotli data into the decoder. Miggo identifies two realistic paths.
Inbound: compressed request bodies
Express 5 with body-parser 2 decompresses request bodies sent with Content-Encoding: br. An API that accepts JSON from the internet can therefore be handed a malicious Brotli body by anyone who can reach it, without logging in.
Outbound: compressed responses
Node's built-in fetch, axios, got and SuperAgent decompress Brotli responses automatically. An application that calls a third-party API, a webhook target or a URL a user supplies will decompress whatever comes back. A compromised or malicious upstream server can return the poisoned stream.
Both paths are common in business software: customer portals, integrations, scrapers, payment callbacks and internal services that fetch from each other. If your Node.js service talks to anything you do not fully control, treat it as reachable.
How to check your Node.js version everywhere
# Host
node -v
# Every running container that has node
for c in $(docker ps --format '{{.Names}}'); do
v=$(docker exec "$c" node -v 2>/dev/null) && echo "$c: $v"
done
# Base images your Dockerfiles build from
grep -rn "^FROM node" --include=Dockerfile* .Fixed versions are 22.23.4, 24.22.1 and 26.12.1 or later in the same line. Anything on Node.js 20 or older is not fixed and is end of life; it needs a move to a supported line, not a patch. Watch for floating Docker tags such as node:22-alpine. They pick up fixes only when the image is rebuilt, so a container built last month still runs last month's Node.js.
How to fix Node2Shell
- 1
Upgrade the runtime
Move to 22.23.4, 24.22.1 or 26.12.1. Pin the exact version in Dockerfiles, for example node:22.23.4-alpine3.24, so the fix is visible in code review.
- 2
Rebuild, then redeploy
Rebuild every image and restart every process. A package upgrade on the host does nothing for a container with its own Node.js.
- 3
Leave end-of-life lines
Node.js 18 and 20 will not be patched. Test on 22 or 24; most applications need only dependency updates, and native addons are the main thing to check.
- 4
Run your tests on the new runtime
Run the full test suite on the target version before the switch, then watch error rates and latency for an hour after.
If you cannot upgrade immediately
- Reject request bodies with Content-Encoding: br at the reverse proxy if your API does not need them. Very few business APIs do.
- Limit which hosts your services fetch from, and avoid fetching arbitrary user-supplied URLs.
- Set an Accept-Encoding header without br on outbound calls where your HTTP client allows it, so upstream servers do not send Brotli.
- Run Node.js processes as a non-root user in containers with limited filesystem access, so a successful exploit gains less.
Planning the move off Node.js 18 and 20
For many teams Node2Shell is not a patch but a migration, because their services still run on a line that has reached end of life. The move from 18 or 20 to 22 or 24 is usually smaller than people fear, but it rewards a short plan over a blind version bump.
- 1
Read the engines field
Check package.json engines and the Node version in Dockerfiles and CI. Mismatches between local, CI and production are where surprises come from.
- 2
Rebuild native addons
Packages with compiled code must be rebuilt for the new runtime. A clean npm ci on the target version shows which ones fail.
- 3
Run the test suite on the target
Run unit and integration tests on the new version before touching production. Most breakage shows up as deprecation warnings or changed defaults rather than crashes.
- 4
Switch one service at a time
Move the least critical service first, watch error rates, latency and memory for a day, then continue. Keep the old image tagged for a quick rollback.
Keep runtimes current by default
The cheapest defence against the next runtime CVE is a routine: rebuild images on a fixed schedule, track which Node line each service uses, and plan the next major move before the current line reaches end of life. Node.js publishes its release schedule well in advance, so an end-of-life date should never arrive as a surprise.
The other Node.js risk in 2026: npm supply-chain attacks
Node2Shell is a bug in the runtime. The year's bigger Node.js story has been malicious code arriving through packages that teams install on purpose. Three incidents show the pattern.
| Date | Package | What happened | Source |
|---|---|---|---|
| 31 March | axios 1.14.1 and 0.30.4 | Malicious dependency plain-crypto-js@4.2.1 injected; downloads multi-stage payloads including a remote access trojan | CISA alert, 20 April |
| 14 May | node-ipc 9.1.6, 9.2.3, 12.0.1 | Three malicious versions carrying an identical credential-stealing payload | StepSecurity |
| 28 May | 14 typosquatted packages | Install-time stager and credential harvester aimed at cloud and CI/CD secrets | Microsoft |
Axios matters because of reach: it is one of the most widely installed HTTP clients in the JavaScript ecosystem. CISA's advice was to downgrade to axios 1.14.0 or 0.30.3, delete the malicious package, rotate any credentials the build or runtime could reach, and block the attacker's domains. It also recommended two npm settings worth adopting generally: ignore-scripts=true, and a minimum release age before new versions are installed.
Supply-chain controls that work in practice
- Install with npm ci from a committed lockfile so the versions in production are the versions that were reviewed.
- Disable install scripts by default and allow them only for packages that genuinely need them.
- Wait a few days before adopting brand-new versions; most malicious releases are found and pulled quickly.
- Keep CI/CD secrets out of the install step, and use short-lived credentials so a stolen token expires.
- Do not run npm install when a production container starts. Build once, test, and ship the built artifact.
What this means for teams running Node.js
Node2Shell and the npm incidents share a lesson: the code you did not write is most of what runs. A runtime bundles libraries and an application bundles hundreds of packages, and either can carry the vulnerability. The teams that handled October calmly were those already running a supported Node.js line with pinned images and a routine rebuild, so the fix was a version bump rather than a migration.
If your services still run on Node.js 18 or 20, Node2Shell is the clearest argument yet for moving. End-of-life runtimes do not just miss features; they miss fixes like this one, permanently.
What is Node2Shell?
Node2Shell is CVE-2026-111497, an out-of-bounds write in the Brotli decoder bundled with Node.js that can lead to remote code execution from a crafted Brotli stream. It was fixed on 21 October 2026.
Which Node.js versions fix CVE-2026-111497?
Node.js 22.23.4, 24.22.1 and 26.12.1 fix CVE-2026-111497. End-of-life release lines, including Node.js 20 and older, do not receive the fix.
Is Node2Shell exploited in the wild?
As of 23 October 2026, no in-the-wild exploitation had been confirmed and the CVE was not on CISA's KEV list, but public proofs of concept existed and code execution had been demonstrated.
Does Node2Shell affect applications that do not use compression?
An application is reachable if it decompresses Brotli data from an untrusted source, for example request bodies sent with Content-Encoding: br or Brotli responses from external servers fetched with fetch, axios or got.
Which axios versions were compromised in 2026?
CISA reported that axios 1.14.1 and 0.30.4 were published on 31 March 2026 with a malicious dependency, plain-crypto-js 4.2.1. The safe versions named were 1.14.0 and 0.30.3.
Sources
- Miggo: Inside Node2Shell— Root cause, timeline and reachable code paths.
- Imperva: Node2Shell, from Brotli data to RCE— Exploitation research.
- CISA: axios npm supply-chain compromise— Affected versions and recommended actions.
- StepSecurity: node-ipc supply-chain attack
- Microsoft: typosquatted npm packages
Related reading
- When a Node.js API request becomes a background job
- Docker and Nginx release and rollback
- Web platform engineering— Runtime upgrades, image pinning and release pipelines.
- Plan a Node.js upgrade— Send the services, Node versions and how each one is deployed.
Services This Relates To
Written by KYCONNECTS Engineering.
