Node2Shell: the Node.js flaw and who has to update the runtime

Server with the hexagonal Node.js logo in a synthwave machine room, crossed by a stream of red compressed data, while a cart with a glowing blue crate sits idle in the corridor
The patch has been out since 21 October. The open question is who installs it.
date: · 9 min
author: Matteo

Node2Shell is the name given to CVE-2026-111497, a flaw in the Brotli decoder that Node.js carries inside it. After PHP2Shell it feels like watching the same film in another language: the bug isn’t in your code or your packages, it’s in the runtime.

As soon as I read about it I thought of vibe coding. If Lovable generated your app, do you know which version of Node runs it? Do you even know whether Node is involved? I went to check, and the answer is more interesting than I expected. For an app published on Lovable, the problem seems to be already handled by someone else. For the same app exported to your own server, the problem is yours.

None of the technical analyses I read link Node2Shell to Lovable or similar tools, so the reasoning about these platforms is mine. I’ll point out where the facts end and my opinion begins.

What Node2Shell is (explained without pointers)

Brotli is a compression algorithm from Google, the one that often compresses the web pages you receive. Its decoder, from version 1.0.3 to 1.2.0, mishandles one specific case: an invalid block switch is mistaken for a simple request for more data, and the program ends up writing past the end of an 8-byte buffer.

It’s like a postman who, faced with a wrong address, doesn’t stop but slips the letter into the neighbour’s mailbox. With the right letter and the right neighbour, things can go very badly for someone.

Node.js includes this library, but the same flaw also hits Bun, .NET, the Python Brotli and brotlicffi packages, Envoy and Istio. That’s why you’ll also find it under other names: HTTP2Shell, Broccoli2Shell, Brotli2Shell.

Two details tell the story well:

  • An AI found it. The Node.js bulletin credits OpenAI Codex Security. The AI that writes the code and the AI that finds the holes in it now work in the same building.
  • The fix sat in plain sight for eight months. According to Help Net Security the commit in google/brotli dates from 27 February, with no release or advisory. Its message even called the bug low severity.

Who is exposed to Node2Shell

Node.js doesn’t decompress Brotli on its own initiative: it takes code, yours or a library’s, that decompresses attacker-controlled data. The trouble is that this code is often already there, without you asking for it.

ScenarioExposed?
Express 5 with express.json()yes, by default
Express 4no, it answers 415
Node HTTP server with no decompressionno
Koa, NestJS, Strapi, Eggyes
Fastifywith @fastify/compress
Server-side fetch(), axios, got to user-chosen URLsyes, they decode the response automatically
Self-hosted Next.jsyes, through image optimization

The fetch() row is the one that worries me most: link previews, webhooks, crawlers, AI agents reading a page. The server goes and gets a URL decided by someone else, and whoever controls that URL also decides what comes back. These are features you now add with one line of prompt.

I wanted to see the Express row with my own eyes. I installed Express with a plain npm i express (you get 5.3.0), wrote three lines of express.json() and sent a harmless Brotli-compressed JSON body. Response: 200, JSON parsed correctly. No configuration, no choice on my part.

If you want to run the same test on your own app locally (the data is harmless, it only tells you whether the parser decompresses):

node -e "process.stdout.write(require('zlib').brotliCompressSync('{\"hello\":\"brotli\"}'))" > body.br

curl -i -X POST http://localhost:3000/your-route \
  -H 'Content-Type: application/json' \
  -H 'Content-Encoding: br' \
  --data-binary @body.br

If the route answers as it would to normal JSON, the decoder is reachable. If you get 415, it isn’t through that path. This only covers that route, and says nothing about fetch().

How serious it really is: the experts disagree

Some caution here, because the sources contradict each other:

  • Severity: 9.2 critical according to the CVE record, “high” for Node.js.
  • Sysdig writes that reliable code execution has been reproduced in only one setup (Imperva, official 24.22.0 image on arm64), while crashing the process is easy. Other researchers argue the real impact is denial of service.
  • ELLIO reports a shell on four x86-64 targets with the same 24.22.0, Express 5.3.0 and Koa 3 included. But each build needs its own offsets: the same values against 22.23.3 and 26.12.0 produced nothing in 10,000 attempts, and most of the 400 attempts against Express only killed the worker.
  • Datadog achieved a repeatable memory read and a deterministic crash. No shell, but it sees the rest as a matter of engineering effort.

On the attack side, CISA added it to the KEV catalog on 27 October, with a 10 November deadline for US federal agencies, without giving details. The public cases I found are attempts on honeypots: the one documented by ELLIO scored zero hits in 50 tries against a clean target. A curious detail: the source IP had already been linked to PHP2Shell. I haven’t found any confirmed victims so far.

To sum up: anyone on an old version certainly risks a service outage, and code execution is proven but not trivial. That’s no reason to wait: an update costs minutes, and exploits tend to get better, not worse.

Who updates Node? Lovable, Replit and vibe coding

This is the question I care about, because npm update does nothing here. The bug is compiled into the runtime. So who replaces it?

Where the app runsWho updatesWhat I found
Lovable, published appLovable and CloudflareEach app is a worker for workerd. A project collaborator wrote on 27 October that it is “patched in production”. I found no notice from Lovable
Functions on SupabaseSupabaseThey run on Deno and the Edge Runtime manifest declares the Rust brotli crate, not Google’s C decoder. That’s my reading of the code, I haven’t tested it and there is no official statement
VercelVercel, but you must redeploySince 28 October new deployments are fixed. Existing ones need a redeploy, and on Node 20 you must change version
Replitto be verifiedThird-party guides describe Node apps started with node dist/index.js. I found no notice
Lovable exported to your own serveryouSee below

That updates on your own server are your job is obvious, and Lovable says so too: the guide to hosting apps elsewhere makes clear that on your own infrastructure updates and security are on you. Less obvious is what there is to update. Lovable apps created since 13 May 2026 use TanStack Start, so they have code running on the server, and the same guide says to use “Node.js 22”, without a minimum patch version.

From here on it’s my opinion. Vibe coding didn’t create this flaw, and an app on a managed platform is probably better off than many hand-written applications forgotten on a VPS since 2023. The risk appears at the handover: the day you export the project to save on the subscription, you become a system administrator. The warning is there, but knowing it’s your job doesn’t tell you what to check. Before, you had a service. Now you have a Dockerfile written by a model, a Node version you didn’t choose, and no notification when a patch comes out.

I have no numbers on how many apps are in this situation, and I doubt anyone does. But I do know how things stood on my side.

I checked my Mac: everything needs updating

Before lecturing anyone I ran a few node --version. These are development environments, not public servers, but the result is instructive:

WhereVersionStatus
Node installed on the Mac26.0.0old
Container with Strapi22.23.3old
Frontend container22.22.2old
Dashboard container22.19.0old
node:22-bookworm-slim image22.23.3old
n8nio/n8n:latest image22.22.0old, and built 8 months ago

Zero out of six. Strapi, tested with the same harmless Brotli body, answered as it would to normal JSON, with no 415. And note that latest, which on disk means “the last one I downloaded”, in this case eight months ago. I’ve been doing this job for a while and I fell for it anyway. This isn’t a problem of people who can’t code: Node is the piece nobody looks at, because it usually just works.

How to fix Node2Shell

1. The fixed versions

LineVersion with the fix
Node.js 2626.12.1
Node.js 2424.22.1
Node.js 2222.23.4
Node.js 20, 23, 25no fix, end of life: move to another line
Bun1.4.4

If you use a distribution’s Node linked against the system Brotli, the version number alone isn’t enough: check the distributor’s advisory.

2. Check the version that is really running

# On the server or locally
node --version

# Inside a running container
docker exec CONTAINER_NAME node --version

# Inside an image (pull the updated one first)
docker pull node:24-slim
docker run --rm node:24-slim node --version

When in doubt, log process.version at startup: it’s the only answer that concerns the real process.

3. Update and rebuild

Restarting a container doesn’t change its Node. The image has to be rebuilt:

docker compose build --pull
docker compose up -d

If the Dockerfile says FROM node:24.22.0, it stays vulnerable however many times you rebuild it: the version has to be raised by hand. The same goes for the Dockerfile a model wrote for you, and for third-party images that carry their own Node.

On a managed platform, look for its notice and, when in doubt, redeploy. Then verify: trusting is fine, checking is better.

4. If you really can’t update right away

These are stopgaps, not a replacement for the patch:

  • express.json({ inflate: false }): I tried it and it answers 415, but it also blocks gzip;
  • reject Content-Encoding: br at the proxy in front of the app, if no legitimate client uses it;
  • search your code for fetch, createBrotliDecompress and the like where the URL comes from the user;
  • run the service as an unprivileged user and keep secrets out of environment variables, to limit the damage.

Watch out for WAFs: Vercel points out that its rules don’t cover responses fetched by your code, and Cloudflare’s emergency rules start in log-only mode. In your logs, look for workers exiting with signal 139 right after a Brotli request, and above all for shells or outbound connections started by the web process.

In short

  • Node2Shell (CVE-2026-111497) is an out-of-bounds write in the Brotli decoder, present in Node.js and plenty of other software.
  • It affects anyone decompressing data controlled by others: Express 5 does so by default, and server-side fetch() to user-chosen URLs is exposed too.
  • It’s in the CISA catalog. Crashes are easy, code execution is proven but not trivial, and I found no confirmed victims.
  • The fix is Node 22.23.4, 24.22.1 or 26.12.1, rebuilding your images. npm update isn’t enough.
  • It isn’t a vibe coding problem as such: on a managed platform someone else applies the patch. When you export the app, that someone is you.

There’s one thing you can do right now: run node --version where your app runs. If you don’t know where to run it, you’ve already found the first problem.

Sources: Node.js bulletin, Sysdig, ELLIO, Datadog Security Labs, SecurityWeek, Help Net Security, Vercel, workerd issue, Lovable documentation.

Copied