DEV Community

The Glitch Fixer
The Glitch Fixer

Posted on Originally published at thehomelabfixer.blogspot.com

Node.js Update: Patch the Node2Shell RCE Tonight — Self-Hosted Fix Guide (CVE-2026-111497)

The only sane advice this week: patch tonight. On Wednesday, October 21, 2026, Node.js shipped security releases for the 26.x, 24.x and 22.x lines. By Thursday, a working proof of concept was public — researchers had demonstrated remote code execution on the official Node.js 24.22.0 Docker image using crafted Brotli data. Today, The Hacker News carried the story wider. The vulnerability has a nickname now: Node2Shell.

If you self-host, this one matters more than most Node advisories. Node.js isn't just a runtime on your server — it is inside your containers. Your dashboards, automation and media tooling ship a Node runtime that a host package audit won't find. This guide is a runbook: find every Node in your stack, then fix each one — including the three traps that catch self-hosters.

Honest-testing note: We did not reproduce any exploit — no exploit or PoC instructions here, only defensive checks. Every fact below is quoted or derived from the official Node.js advisory, Imperva's research, Singapore's CSA advisory, or The Hacker News' coverage.

What Node2Shell actually is

Node2Shell is CVE-2026-111497, an out-of-bounds write in Node.js's bundled Brotli decoder. The official advisory: "A flaw in Node.js can cause Node.js Brotli decoder out-of-bounds write permits remote code execution." It affects the 26.x, 24.x and 22.x lines; the fixed builds are 26.12.1, 24.22.1 and 22.23.4. Reported by OpenAI Codex Security — Khai Tran and Sylvie Mayer; fixed by mcollina and aduh95.

Two things to get right on severity. Node.js's own words: "The highest severity issue fixed in this release is HIGH." Separately, Singapore's CSA scored the same CVE at CVSS v3.1 9.8 — the CSA's score, not the Node.js project's rating. Per CSA: "Successful exploitation of this out-of-bounds write vulnerability could allow an attacker to execute arbitrary code remotely or disclose sensitive information from the memory of affected systems. Exploitation requires an affected application to decompress attacker-controlled Brotli-compressed data."

Imperva's research identified three paths: (1) incoming request bodies with Brotli encoding — Express 5 (versions 5.0.0–5.3.0) and body-parser 2.x (2.0.0–2.3.0) decompress these; (2) outbound fetch() calls decoding Brotli responses from servers you don't control; (3) direct use of node:zlib. Imperva demonstrated RCE against the official Node.js 24.22.0 Linux arm64 image and confirmed the fixed 24.22.1 rejected the same input. No in-the-wild exploitation was reported as of October 22 — a window-of-opportunity story, which is exactly when patching pays off.

This release also fixed ten vulnerabilities total (four high, three medium, three low) — TLS hostname-check bypasses, a dns.resolve NUL-truncation bug, and bundled updates (c-ares, ca-cert with the eMudhra Intermediate CA compromise, OpenSSL, SQLite, Undici). More reasons to update Node.js everywhere it hides.

Step 0 — Are you exposed? Audit every Node in your stack

Start with the Node you know about, on the host:

node -v
# If it prints v22.23.4, v24.22.1 or v26.12.1 (or newer), the host runtime is fixed.
Enter fullscreen mode Exit fullscreen mode

Then the Node you forgot about — inside your containers. A package inventory misses a runtime bundled in an image, so query the image itself:

docker run --rm my-app-image node --version
# Repeat for every Node-based app image in your compose stacks
Enter fullscreen mode Exit fullscreen mode

Critical detail: check the running container, not just the pulled image — an old running container survives an image update:

docker exec my-app-container node --version
# or, for compose: docker compose exec app node --version
Enter fullscreen mode Exit fullscreen mode

Make a list: every container older than 22.23.4 / 24.22.1 / 26.12.1 on its line, plus anything on an end-of-life line. That's your patch queue for tonight.

Step 1 — Re-pull and verify (the image-publisher lag trap)

The lag trap: Your app's Docker image is not fixed until its maintainer rebuilds it on node ≥22.23.4 / ≥24.22.1 / ≥26.12.1. Updating your image tag or restarting the container changes nothing if the publisher hasn't rebuilt yet. Never assume a re-pull fixed it — verify.

Before touching anything, snapshot first — the same snapshot-first discipline as our SafePay ransomware NAS survival guide. Then, for each Node-based app in your patch queue:

cd /path/to/your/compose/stack
docker compose pull          # get the publisher's latest build
docker compose up -d         # recreate containers on the new image
docker compose exec app node --version   # verify the RUNNING container is fixed
Enter fullscreen mode Exit fullscreen mode

Two failure modes. First, a cached digest: if the version still shows old after the pull, confirm the digest actually changed (docker images --digests) — pinned digests and cached layers redeploy the old image silently. Second, the publisher may not have rebuilt yet — check the project's release notes. Same patch-tonight discipline as our GitLab CVSS-10 RCE guide: the vendor's fix and your deployment of it are two different events.

Step 2 — Fix the pin trap

If you build your own images, check your base-image pins. Aikido's warning, in spirit: a Dockerfile pinned to node:24.22.0 stays vulnerable no matter how often you rebuild it — rebuilding on the old pin changes nothing. So:

# In your Dockerfile, change:
#   FROM node:24.22.0-alpine
# to the fixed patch on your line:
#   FROM node:24.22.1-alpine
# Then rebuild:
docker build --no-cache -t my-app:latest .
docker run --rm my-app:latest node --version
Enter fullscreen mode Exit fullscreen mode

Move to 24.22.1, 22.23.4 or 26.12.1 (or later) on whichever line you run. Same careful sequencing as our n8n 3.0 breaking-changes walkthrough — line jumps need release-note reading; patch bumps within a line are tonight's safe, fast move.

Step 3 — The EOL trap

The official advisory is blunt about end-of-life lines: "It's important to note that End-of-Life versions are always affected when a security release occurs." Per third-party guides (Aikido and others), lines like 20.x and earlier, plus odd lines 23.x/25.x, receive no fix — that detail comes from third-party analysis, not the official page.

The homelab killer: check your image's base. If any image you run is still built on an EOL Node line, no re-pull will ever fix it. Two moves: migrate the app to an image built on a supported line (22.x or 24.x), or — if the app can't move tonight — pull it off the public internet and sandbox it behind your VPN. Verify each project's Dockerfile or release notes yourself rather than trusting memory.

Can't patch tonight? A bridge-only stopgap

The official advisory offers no workaround in Node.js itself — patching is the fix. For the container you genuinely can't update tonight, reduce inbound exposure at the reverse proxy by rejecting Brotli request bodies. A bridge only: it does nothing about Brotli responses your app fetches, so don't treat it as protection.

For Nginx Proxy Manager, add to the proxy host's Custom Nginx Configuration:

# Node2Shell stopgap — TEMPORARY, remove after patching
if ($http_content_encoding ~* "br") {
  return 415;
}
Enter fullscreen mode Exit fullscreen mode

For Caddy (in the site's handle block):

# Node2Shell stopgap — TEMPORARY, remove after patching
@brotli header Content-Encoding br
respond @brotli 415
Enter fullscreen mode Exit fullscreen mode

Legitimate browsers sending Brotli request bodies will get a 415 — acceptable for one night on most homelab services, since Brotli request bodies are rare. Schedule the real node js security update for the morning and remove the rule once you're patched.

A note on Bun

If any self-hosted app runs on Bun rather than Node.js: Bun before 1.4.4 is affected, and Bun 1.4.4 includes the fix (per Aikido's FAQ). bun --version on the host or in the container tells you where you stand — update it the same way you'd update Node.js.

Verify the fix

Close the loop the same way you opened it — confirm the running state, not your intent:

# Host runtime
node -v
# Every patched container, checked live:
docker compose exec app node --version
docker exec my-other-app node --version
Enter fullscreen mode Exit fullscreen mode

Expected: 22.23.4, 24.22.1 or 26.12.1 (or newer) on each app's line. Anything still old goes back on the queue — or gets the stopgap and an off-internet sandbox until it can be patched.

One last operator caveat: if you run Watchtower or similar auto-updaters, exclude or pin images prone to major version jumps — the same discipline every nodejs docker update demands — so tonight's fix doesn't arrive as tomorrow's breaking change. Patch the runtime, verify the running container, then sleep.

Companion read: our Plex2Shell compromise check and patch guide covers the same patch-tonight workflow for Plex servers.

Node2Shell FAQ — quick answers

Which Node.js versions fix Node2Shell (CVE-2026-111497)?

The October 21, 2026 releases: 22.23.4, 24.22.1 and 26.12.1 for the 22.x, 24.x and 26.x lines. End-of-life lines (per third-party guides, 20.x and earlier) get no fix — the official advisory states end-of-life versions are always affected.

Is Node2Shell being exploited in the wild?

No in-the-wild exploitation was reported as of October 22, per The Hacker News — but a working proof of concept was publicly demonstrated on October 22. Patch now, while the window is open.

How do I check if my Docker containers run a vulnerable Node version?

Query the image directly — docker run --rm <image> node --version — since a host package audit misses runtimes bundled inside images. Then verify the running container with docker exec <container> node --version, because an old running container survives an image update.

My app image still reports the old Node version after updating — what now?

Three checks: (1) confirm the image digest actually changed after the pull — a cached or pinned digest redeploys the old image; (2) check the project's release notes — the publisher may not have rebuilt on the fixed base yet; (3) check your Dockerfile pins — a Dockerfile pinned to node:24.22.0 stays vulnerable no matter how often you rebuild it.

Does blocking Brotli at my reverse proxy fully protect me?

No. Rejecting Content-Encoding: br request bodies at the proxy only covers one attack path. It does nothing about Brotli responses your app fetches from servers you don't control, or direct node:zlib use. A temporary bridge for the night — the real fix is the Node.js update.

Is Bun affected by Node2Shell?

Bun before 1.4.4 is affected; Bun 1.4.4 includes the fix (per Aikido's FAQ). Run bun --version on the host or in the container and update the same night.


Originally published on The Homelab Fixer.

Top comments (0)