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

> Node2Shell (CVE-2026-111497) is a flaw in the Brotli decoder used by Node.js, already in CISA's catalog of exploited vulnerabilities. Who is exposed, how serious it really is, and why with apps generated by Lovable and the like the right question is: who updates Node?

- URL: https://mautoblog.com/en/posts/node2shell-nodejs-brotli-flaw-who-updates-runtime-2026/
- Language: en
- Published: 2026-11-02
- Author: Matteo
- Categories: Security and Privacy, Software Development
- Tags: JavaScript, Cybersecurity, DevOps, Docker, AI-Assisted Development
- Translation (it): https://mautoblog.com/posts/node2shell-falla-nodejs-brotli-chi-aggiorna-2026/

---

**Node2Shell** is the name given to CVE-2026-111497, a flaw in the Brotli decoder that Node.js carries inside it. After [PHP2Shell](/en/posts/php2shell-critical-php-vulnerabilities-rce-update-2026/) 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](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases) 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](https://www.helpnetsecurity.com/?p=389353) 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.

| Scenario | Exposed? |
|---|---|
| Express 5 with `express.json()` | yes, by default |
| Express 4 | no, it answers 415 |
| Node HTTP server with no decompression | no |
| Koa, NestJS, Strapi, Egg | yes |
| Fastify | with `@fastify/compress` |
| Server-side `fetch()`, `axios`, `got` to user-chosen URLs | yes, they decode the response automatically |
| Self-hosted Next.js | yes, 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):

```bash
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](https://www.sysdig.com/blog/node2shell-cve-2026-111497-detecting-the-node-js-brotli-rce) 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](https://ellio.tech/en/blog/node2shell-cve-2026-111497-rce-on-stock-nodejs-builds/) 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](https://securitylabs.datadoghq.com/articles/node2shell-cve-2026-111497-nodejs-brotli-decoder/) 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](https://www.securityweek.com/cisa-warns-of-node2shell-exploitation-as-honeypots-log-attack-attempts/) 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 runs | Who updates | What I found |
|---|---|---|
| **Lovable, published app** | Lovable and Cloudflare | [Each app is a worker for `workerd`](https://lovable.dev/blog/how-we-migrated-lovable-dev-away-from-nextjs). A project collaborator [wrote on 27 October](https://github.com/cloudflare/workerd/issues/8004) that it is "patched in production". I found no notice from Lovable |
| **Functions on Supabase** | Supabase | They run on Deno and the [Edge Runtime manifest](https://github.com/supabase/edge-runtime/blob/main/Cargo.toml) 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 |
| **Vercel** | Vercel, but you must redeploy | [Since 28 October](https://vercel.com/kb/bulletin/node-js-security-advisory) new deployments are fixed. Existing ones need a redeploy, and on Node 20 you must change version |
| **Replit** | to be verified | Third-party guides describe Node apps [started with `node dist/index.js`](https://livemy.app/blog/deploy-replit-agent-app). I found no notice |
| **Lovable exported to your own server** | **you** | See below |

That updates on your own server are your job is obvious, and Lovable says so too: the [guide to hosting apps elsewhere](https://docs.lovable.dev/tips-tricks/external-deployment-hosting) 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](https://docs.lovable.dev/introduction/faq), 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:

| Where | Version | Status |
|---|---|---|
| Node installed on the Mac | 26.0.0 | old |
| Container with Strapi | 22.23.3 | old |
| Frontend container | 22.22.2 | old |
| Dashboard container | 22.19.0 | old |
| `node:22-bookworm-slim` image | 22.23.3 | old |
| `n8nio/n8n:latest` image | 22.22.0 | old, 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

| Line | Version with the fix |
|---|---|
| Node.js 26 | 26.12.1 |
| Node.js 24 | 24.22.1 |
| Node.js 22 | 22.23.4 |
| Node.js 20, 23, 25 | no fix, end of life: move to another line |
| Bun | 1.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

```bash
# 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:

```bash
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](https://developers.cloudflare.com/changelog/post/2026-10-25-emergency-waf-release/)'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](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases), [Sysdig](https://www.sysdig.com/blog/node2shell-cve-2026-111497-detecting-the-node-js-brotli-rce), [ELLIO](https://ellio.tech/en/blog/node2shell-cve-2026-111497-rce-on-stock-nodejs-builds/), [Datadog Security Labs](https://securitylabs.datadoghq.com/articles/node2shell-cve-2026-111497-nodejs-brotli-decoder/), [SecurityWeek](https://www.securityweek.com/cisa-warns-of-node2shell-exploitation-as-honeypots-log-attack-attempts/), [Help Net Security](https://www.helpnetsecurity.com/?p=389353), [Vercel](https://vercel.com/kb/bulletin/node-js-security-advisory), [workerd issue](https://github.com/cloudflare/workerd/issues/8004), [Lovable documentation](https://docs.lovable.dev/tips-tricks/external-deployment-hosting).

