# Node2Shell: la falla di Node.js e chi deve aggiornare il runtime

> Node2Shell (CVE-2026-111497) è una falla nel decoder Brotli usato da Node.js, già nel catalogo CISA delle vulnerabilità sfruttate. Chi è esposto, quanto è grave davvero e perché con le app generate da Lovable e simili la domanda giusta è: chi aggiorna Node?

- URL: https://mautoblog.com/posts/node2shell-falla-nodejs-brotli-chi-aggiorna-2026/
- Lingua: it
- Pubblicato: 2026-11-02
- Autore: Matteo
- Categorie: Sicurezza e Privacy, Sviluppo Software
- Tags: JavaScript, Cybersecurity, DevOps, Docker, Sviluppo con IA
- Traduzione (en): https://mautoblog.com/en/posts/node2shell-nodejs-brotli-flaw-who-updates-runtime-2026/

---

**Node2Shell** è il nome dato alla CVE-2026-111497, una falla nel decoder Brotli che Node.js si porta dentro. Dopo [PHP2Shell](/posts/php2shell-vulnerabilita-critiche-php-rce-aggiornare-2026/) sembra di rivedere lo stesso film con un altro linguaggio: il bug non sta nel tuo codice e nemmeno nei tuoi pacchetti, sta nel runtime.

Appena l'ho letta ho pensato al vibe coding. Se la tua app l'ha generata Lovable, sai che versione di Node la fa girare? Sai almeno se c'è, Node? Sono andato a controllare e la risposta è più interessante di quella che mi aspettavo. Per un'app pubblicata su Lovable il problema sembra già gestito da altri. Per la stessa app esportata su un tuo server, il problema è tuo.

Nessuna delle analisi tecniche che ho letto collega Node2Shell a Lovable o simili, quindi il ragionamento su queste piattaforme è mio. Ti segnalo dove finiscono i fatti e dove comincia la mia opinione.

## Cos'è Node2Shell (spiegato senza puntatori)

Brotli è un algoritmo di compressione di Google, quello che spesso comprime le pagine web che ricevi. Il suo decoder, dalla versione 1.0.3 alla 1.2.0, gestisce male un caso preciso: un cambio di blocco non valido viene scambiato per una semplice richiesta di altri dati, e il programma finisce per scrivere oltre la fine di un buffer di 8 byte.

È come un postino che, davanti a un indirizzo sbagliato, invece di fermarsi infila la lettera nella cassetta del vicino. Con la lettera giusta e il vicino giusto, a qualcuno può andare molto male.

Node.js include questa libreria, ma la stessa falla tocca anche Bun, .NET, i pacchetti Python `Brotli` e `brotlicffi`, Envoy e Istio. Per questo la trovi anche con altri nomi: HTTP2Shell, Broccoli2Shell, Brotli2Shell.

Due dettagli raccontano bene la storia:

- **L'ha trovata un'IA.** Il [bollettino di Node.js](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases) accredita OpenAI Codex Security. L'IA che scrive il codice e l'IA che ci trova i buchi, ormai, lavorano nello stesso palazzo.
- **La correzione è stata in chiaro per otto mesi.** Secondo [Help Net Security](https://www.helpnetsecurity.com/?p=389353) il commit in `google/brotli` è del 27 febbraio, senza release né avviso. Il messaggio la definiva pure di bassa gravità.

## Chi è esposto a Node2Shell

Node.js non decomprime Brotli di sua iniziativa: serve del codice, tuo o di una libreria, che decomprima dati controllati da chi attacca. Il guaio è che quel codice è spesso già lì, senza che tu l'abbia chiesto.

| Scenario | Esposto? |
|---|---|
| Express 5 con `express.json()` | sì, di default |
| Express 4 | no, risponde 415 |
| Server HTTP Node senza decompressione | no |
| Koa, NestJS, Strapi, Egg | sì |
| Fastify | con `@fastify/compress` |
| `fetch()`, `axios`, `got` lato server verso URL scelti dall'utente | sì, decodificano la risposta in automatico |
| Next.js self-hosted | sì, tramite l'ottimizzazione delle immagini |

La riga del `fetch()` è quella che mi preoccupa di più: anteprime dei link, webhook, crawler, agenti IA che leggono una pagina. Il server va a prendere un URL deciso da altri, e chi controlla quell'URL decide anche cosa torna indietro. Sono funzioni che oggi si aggiungono con una riga di prompt.

Ho voluto vedere la riga di Express con i miei occhi. Ho installato Express con un semplice `npm i express` (arriva la 5.3.0), ho scritto tre righe di `express.json()` e ho spedito un corpo JSON compresso con Brotli, innocuo. Risposta: `200`, JSON letto correttamente. Nessuna configurazione, nessuna scelta da parte mia.

Se vuoi fare la stessa prova su una tua app in locale (è un dato innocuo, serve solo a capire se il parser decomprime):

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

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

Se la rotta risponde come a un JSON normale, il decoder viene raggiunto. Se ricevi `415`, da quel percorso no. Vale solo per quella rotta, e non dice nulla sul `fetch()`.

## Quanto è grave davvero: gli esperti non sono d'accordo

Qui bisogna andarci piano, perché le fonti si contraddicono:

- **Gravità**: 9,2 critica secondo il record CVE, "high" per Node.js.
- **Sysdig** [scrive](https://www.sysdig.com/blog/node2shell-cve-2026-111497-detecting-the-node-js-brotli-rce) che l'esecuzione di codice affidabile è stata riprodotta in un solo ambiente (Imperva, immagine ufficiale 24.22.0 su arm64), mentre far cadere il processo è facile. Altri ricercatori sostengono che l'impatto reale sia il blocco del servizio.
- **ELLIO** [riporta](https://ellio.tech/en/blog/node2shell-cve-2026-111497-rce-on-stock-nodejs-builds/) una shell su quattro bersagli x86-64 con la stessa 24.22.0, Express 5.3.0 e Koa 3 compresi. Però ogni build richiede offset propri: gli stessi valori su 22.23.3 e 26.12.0 non hanno prodotto nulla in 10.000 tentativi, e la maggior parte dei 400 tentativi contro Express ha solo ucciso il worker.
- **Datadog** [ha ottenuto](https://securitylabs.datadoghq.com/articles/node2shell-cve-2026-111497-nodejs-brotli-decoder/) una lettura ripetibile della memoria e un crash deterministico. Niente shell, ma giudica il resto una questione di tempo e lavoro.

Sul fronte attacchi, [CISA l'ha aggiunta al catalogo KEV](https://www.securityweek.com/cisa-warns-of-node2shell-exploitation-as-honeypots-log-attack-attempts/) il 27 ottobre, con scadenza 10 novembre per le agenzie federali americane, senza dare dettagli. I casi pubblici che ho trovato sono tentativi sui honeypot: quello documentato da ELLIO ha fatto zero centri su 50 contro un bersaglio pulito. Curiosità: l'IP di provenienza era già stato collegato a PHP2Shell. Vittime confermate, per ora, non ne ho trovate.

Riassumendo: chi ha una versione vecchia rischia di sicuro il blocco del servizio, e l'esecuzione di codice è dimostrata ma non banale. Non è un buon motivo per rimandare: i costi di un aggiornamento sono minuti, e gli exploit tendono a migliorare, non a peggiorare.

## Chi aggiorna Node? Lovable, Replit e il vibe coding

Questa è la domanda che mi interessa, perché `npm update` qui non serve a niente. Il bug è compilato dentro il runtime. Quindi: chi lo cambia?

| Dove gira l'app | Chi aggiorna | Cosa risulta |
|---|---|---|
| **Lovable, app pubblicata** | Lovable e Cloudflare | [Ogni app è un worker per `workerd`](https://lovable.dev/blog/how-we-migrated-lovable-dev-away-from-nextjs). Un collaboratore del progetto ha [scritto il 27 ottobre](https://github.com/cloudflare/workerd/issues/8004) che è "patched in production". Da Lovable nessun avviso trovato |
| **Funzioni su Supabase** | Supabase | Girano su Deno e il [manifest di Edge Runtime](https://github.com/supabase/edge-runtime/blob/main/Cargo.toml) dichiara il crate Rust `brotli`, non il decoder C di Google. È una mia lettura del codice, non l'ho testata e non c'è una dichiarazione ufficiale |
| **Vercel** | Vercel, ma tu devi ridistribuire | [Dal 28 ottobre](https://vercel.com/kb/bulletin/node-js-security-advisory) le nuove distribuzioni sono corrette. Le esistenti vanno ridistribuite, e su Node 20 devi cambiare versione |
| **Replit** | da verificare | Guide di terzi descrivono app Node [avviate con `node dist/index.js`](https://livemy.app/blog/deploy-replit-agent-app). Nessun avviso trovato |
| **Lovable esportata su un tuo server** | **tu** | Vedi sotto |

Che sul tuo server gli aggiornamenti tocchino a te è ovvio, e Lovable lo scrive anche: la [guida per ospitare le app altrove](https://docs.lovable.dev/tips-tricks/external-deployment-hosting) dice che su un'infrastruttura tua aggiornamenti e sicurezza sono a carico tuo. Meno ovvio è cosa ci sia da aggiornare. Le app Lovable create dal 13 maggio 2026 [usano TanStack Start](https://docs.lovable.dev/introduction/faq), quindi hanno codice che gira sul server, e la stessa guida dice di usare "Node.js 22", senza indicare una versione minima.

Da qui in poi è opinione mia. Il vibe coding non ha creato questa falla, e un'app su una piattaforma gestita sta probabilmente meglio di tante applicazioni scritte a mano e dimenticate su una VPS dal 2023. Il rischio nasce nel passaggio: il giorno in cui esporti il progetto per risparmiare sull'abbonamento, ti ritrovi amministratore di sistema. L'avviso c'è, ma sapere che tocca a te non ti dice cosa controllare. Prima avevi un servizio. Ora hai un `Dockerfile` scritto da un modello, una versione di Node che non hai scelto e nessuna notifica quando esce una patch.

Non ho numeri su quante app siano in questa situazione, e non credo ce li abbia nessuno. So però come ero messo io.

## Ho controllato il mio Mac: tutto da aggiornare

Prima di dare lezioni ho lanciato qualche `node --version`. Sono ambienti di sviluppo, non server pubblici, ma il risultato è istruttivo:

| Dove | Versione | Stato |
|---|---|---|
| Node installato sul Mac | 26.0.0 | vecchia |
| Container con Strapi | 22.23.3 | vecchia |
| Container di un frontend | 22.22.2 | vecchia |
| Container di una dashboard | 22.19.0 | vecchia |
| Immagine `node:22-bookworm-slim` | 22.23.3 | vecchia |
| Immagine `n8nio/n8n:latest` | 22.22.0 | vecchia, e costruita 8 mesi fa |

Zero su sei. Lo Strapi, provato con lo stesso corpo Brotli innocuo, ha risposto come a un JSON normale, senza nessun `415`. E notare quel `latest`, che sul disco significa "l'ultima che ho scaricato", ovvero otto mesi fa. Ecco, io questo mestiere lo faccio da un po' e ci sono cascato lo stesso. Il problema non è di chi non sa programmare: Node è il pezzo che nessuno guarda, perché di solito funziona.

## Come correggere Node2Shell

### 1. Le versioni corrette

| Ramo | Versione con la correzione |
|---|---|
| Node.js 26 | 26.12.1 |
| Node.js 24 | 24.22.1 |
| Node.js 22 | 22.23.4 |
| Node.js 20, 23, 25 | nessuna correzione, fine vita: va cambiato ramo |
| Bun | 1.4.4 |

Se usi il Node di una distribuzione collegato al Brotli di sistema, il numero di versione da solo non basta: controlla l'avviso del distributore.

### 2. Controllare la versione che gira davvero

```bash
# Sul server o in locale
node --version

# Dentro un container in esecuzione
docker exec NOME_CONTAINER node --version

# Dentro un'immagine (scarica prima quella aggiornata)
docker pull node:24-slim
docker run --rm node:24-slim node --version
```

Nel dubbio, fai scrivere `process.version` ai log all'avvio: è l'unica risposta che riguarda il processo vero.

### 3. Aggiornare e ricostruire

Riavviare un container non cambia il suo Node. L'immagine va ricostruita:

```bash
docker compose build --pull
docker compose up -d
```

Se il `Dockerfile` dice `FROM node:24.22.0`, resterà vulnerabile per quante volte lo ricostruisci: la versione va alzata a mano. Vale anche per il `Dockerfile` che ti ha scritto il modello, e vale per i pacchetti globali e le immagini di terzi che si portano dentro il proprio Node.

Su una piattaforma gestita, cerca il suo avviso e, nel dubbio, ridistribuisci. Poi verifica: fidarsi va bene, controllare è meglio.

### 4. Se proprio non puoi aggiornare subito

Sono misure tampone, che non sostituiscono la patch:

- `express.json({ inflate: false })`: l'ho provato e risponde `415`, ma blocca anche il gzip;
- rifiuta `Content-Encoding: br` sul proxy davanti all'app, se nessun client legittimo lo usa;
- cerca nel codice `fetch`, `createBrotliDecompress` e simili dove l'URL arriva dall'utente;
- esegui il servizio con un utente senza privilegi e tieni i segreti fuori dalle variabili d'ambiente, per limitare i danni.

Occhio ai WAF: Vercel precisa che le sue regole non coprono le risposte scaricate dal tuo codice, e quelle di emergenza di [Cloudflare](https://developers.cloudflare.com/changelog/post/2026-10-25-emergency-waf-release/) partono in sola registrazione. Nei log cerca worker terminati con segnale 139 subito dopo una richiesta Brotli, e soprattutto shell o connessioni in uscita partite dal processo web.

## In sintesi

- Node2Shell (CVE-2026-111497) è una scrittura fuori dai limiti nel decoder Brotli, presente in Node.js e in molto altro software.
- Colpisce chi decomprime dati controllati da altri: Express 5 lo fa di default, e anche il `fetch()` lato server verso URL scelti dall'utente è esposto.
- È nel catalogo CISA. Il crash è facile, l'esecuzione di codice è dimostrata ma non banale, e non ho trovato vittime confermate.
- La soluzione è **Node 22.23.4, 24.22.1 o 26.12.1**, ricostruendo le immagini. `npm update` non basta.
- Non è un problema del vibe coding in sé: su una piattaforma gestita la patch la mette qualcun altro. Quando esporti l'app, quel qualcuno sei tu.

Una cosa la puoi fare adesso: lancia `node --version` dove gira la tua app. Se non sai dove lanciarlo, hai già trovato il primo problema.

Fonti: [bollettino di Node.js](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), [issue su workerd](https://github.com/cloudflare/workerd/issues/8004), [documentazione di Lovable](https://docs.lovable.dev/tips-tricks/external-deployment-hosting).

