BlogVulnerability Research

Node2Shell: From Brotli Data to Remote Code Execution in Node.js

On 21 October 2026, the Node.js project shipped out-of-cycle releases for CVE-2026-111497 — an out-of-bounds write in the bundled Brotli decoder that reaches straight into Express 5 request bodies, fetch() responses and anything that calls node:zlib's Brotli helpers. Within 24 hours a working proof-of-concept was public and unauthenticated remote code execution had been demonstrated on vanilla Node.js 24.22.0. Here is what the bug is, where it lives in your stack, and the exact versions to pin in the next 48 hours.

9 min readBy TigerGate Research, Security Research TeamPublished Updated

TL;DR

  • · One CVE: CVE-2026-111497 — out-of-bounds write in Node.js's bundled Brotli decoder, rated High by the Node.js project.
  • · Root cause: an invalid Brotli block-type transition is treated as "need more input," so decoding continues against an invalid internal state and writes past the output buffer.
  • · Reachable through three paths most Node apps have at least one of: Express 5 / body-parser 2 Brotli request bodies, built-in fetch() consuming Content-Encoding: br responses, and direct node:zlib Brotli calls.
  • · Patch to Node.js 22.23.4, 24.22.1, 26.12.1 or later. Bun users: Bun 1.4.4.
  • · PoC code execution demonstrated on unmodified Node.js 24.22.0 (Linux arm64). Patched builds reject the same inputs.

What is Node2Shell?

Node2Shell is the nickname attached to CVE-2026-111497, a memory-safety flaw in the Brotli decompression library that ships inside Node.js. The bug lives in native C code shared by both the Google Brotli library and Node's statically linked copy. Because Brotli is wired into so many entry points of modern Node — Express body parsing, undici-backed fetch(), zlib — a single malformed stream of Brotli data can turn into out-of-bounds writes, crashes, and in the lab, remote code execution. The application running on top of Node is incidental; clean, modern JavaScript offers no defence.

The root cause: an invalid block transition that looks like "need more data"

A Brotli stream describes the block types available as decoding progresses. Each time the decoder consumes a header it has to re-check that the stream's declared block structure is internally consistent. In the vulnerable code path, one specific invalid transition is misclassified: instead of raising a format error, the decoder treats it as a request for more input, and processing continues against an invalid internal state.

With the state machine out of sync with the actual stream, subsequent symbol decoding writes past the end of the output buffer. The upstream Brotli fix (commit 0ed4268a, February 2026, released as Brotli 1.2.1, 1.1.1 and 1.0.10) makes the decoder return a format error for the invalid transition, closing the primitive at the source.

The three paths that reach the vulnerable decoder

If your Node service exposes any of the following, assume the attacker can feed bytes to the vulnerable Brotli code:

1. Inbound HTTP request bodies (Express 5 / body-parser 2)

Express 5.0.0–5.3.0 and body-parser 2.0.0–2.3.0 decompress Brotli-encoded request bodies out of the box when the client sends Content-Encoding: br. No application code change is required to opt in — if you accept JSON or urlencoded bodies on Express 5, you accept Brotli. This is the primary unauthenticated attack surface on externally exposed APIs.

2. Consumed HTTP responses via built-in fetch()

Node's built-in fetch() (backed by undici) automatically negotiates and decompresses Brotli-encoded responses. Any server-side code that calls fetch() against a URL an attacker can influence — webhooks, SSRF-adjacent proxies, image/metadata fetchers, URL previews, outbound integrations — can be served malicious Brotli and trigger the same primitive on your worker.

3. Direct node:zlib Brotli calls

brotliDecompress, brotliDecompressSync, and createBrotliDecompress hand attacker-controlled bytes straight to the vulnerable decoder. Common in log pipelines, static-asset tooling, dataset loaders, and anywhere a service deflates archives or user uploads server-side.

What's actually affected

The vulnerable Brotli code shipped with every supported Node.js release line, plus Bun. Patched versions:

Runtime lineFixed in
Node.js 22.x (LTS)22.23.4
Node.js 24.x (LTS)24.22.1
Node.js 26.x (Current)26.12.1
Bun1.4.4

Everything else that bundles a Node binary inherits the bug: Electron apps, serverless Node runtimes on AWS Lambda / Google Cloud Functions / Azure Functions / Cloudflare Workers (Node compat), CI runners with pre-installed Node, Docker base images (node:22, node:24, node:26 and their -alpine, -slim, -bookworm variants built before 21 Oct 2026). Managed Node hosts (Vercel, Netlify, Railway, Fly.io, Render) typically rebuild within hours — confirm their advisory.

Proof-of-concept results

  • Code execution has been demonstrated against unmodified Node.js 24.22.0 on Linux arm64 with multiple middleware configurations.
  • On patched Node.js 24.22.1 the same inputs are rejected and no execution occurs.
  • As of 22 October 2026 there are no public reports of exploitation in the wild, but lab-based RCE is confirmed on an unmodified build.

Timeline

DateEvent
Feb 2026Upstream Brotli commit 0ed4268a lands the format-error fix
21 Oct 2026Node.js ships 22.23.4, 24.22.1, 26.12.1; Brotli 1.2.1, 1.1.1, 1.0.10 released; CVE-2026-111497 published
21 Oct 2026Reporter Matteo Collina; fix by Antoine du Hamel acknowledged
22 Oct 2026Public PoC published; code execution demonstrated on vanilla Node.js 24.22.0

What to do in the next 48 hours

  1. Pin to patched Node. Upgrade production, staging and CI runners to Node.js 22.23.4, 24.22.1, 26.12.1 or newer. Bun to 1.4.4. Rebuild Docker images from the refreshed node:22, node:24 and node:26 tags — pinned digests in your Dockerfiles will not pull the fix.
  2. Inventory Brotli entry points. Grep repositories for express ^5, body-parser ^2, brotliDecompress, createBrotliDecompress, and any custom middleware that negotiates Content-Encoding: br. Audit outbound fetch() targets your service calls against attacker-influenced URLs.
  3. Virtual-patch at the edge. If you cannot reboot workers immediately, drop or inspect requests with Content-Encoding: br at the WAF / API gateway for externally-exposed services, and block outbound Content-Encoding: br responses on egress proxies. Check your WAF vendor's advisory feed for a signature covering CVE-2026-111497.
  4. Hunt for exploitation. Look back 72 hours for: unexplained Node worker crashes or restarts correlated with inbound Content-Encoding: br traffic, segfaults in node processes, fetch()-adjacent errors against new or unfamiliar hosts, and new outbound connections from Node processes to IPs outside your allowlist.
  5. Rotate secrets on any host where exploitation looks plausible — database credentials, API keys loaded through process.env, OAuth client secrets, and anything mounted at /run/secrets.

How TigerGate helps

  • SCA and SBOM flags projects pinned to vulnerable Node.js, Express 5 (<5.3.1), body-parser 2 (<2.3.1), and undici versions across every repo and lockfile, with reachability signals so upgrades land on the services that actually accept Brotli bodies first.
  • Container Security scans Node base images and derived application images for the vulnerable Node binary before they reach your registry and admission controller — including Alpine, slim and distroless variants.
  • Server Vulnerability Monitoring fingerprints Node binaries on Linux hosts and cloud VMs, prioritised by eBPF runtime-reachability so you page on hosts where Node is actually serving HTTP in production, not every box that has a dev copy installed.
  • eBPF runtime detection watches for the exploitation side-effects: anomalous child processes spawned by node, unexpected outbound connections from Node workers, writes to /tmp or /dev/shm followed by execution, and segfaulting Node processes correlated with inbound Brotli traffic.
  • Compliance evidence maps the Node.js patch to SOC 2 CC7.1 vulnerability lifecycle, PCI DSS 6.3.3 (critical patch within 30 days) and ISO 27001 A.8.8 — the audit trail your next auditor will ask for.

Sources

Find vulnerable Node.js across your fleet

14-day trial. Scan every repo, container image and Linux host for Node.js, Express 5, body-parser 2, undici and direct Brotli usage. The runtime agent adds reachability so you page on the services actually accepting Brotli — not every machine with a stale copy of node.