Node.js vulnerability: a server module, a cracked shield and a security patch being applied

Node.js fixes Brotli RCE (CVE-2026-111497), TLS bypasses: what to do

Published: October 22, 2026|Affiliate Disclosure

Node.js 26.12.1, 24.22.1 and 22.23.4 fix ten vulnerabilities, four of them rated high. The worst is an out-of-bounds write in the bundled Brotli decoder, reachable through fetch(), Express 5 or zlib. Other bugs bypass TLS certificate checks and truncate DNS queries. What is verified, how CloudLinux, Plesk and Vercel are responding, and what VPS admins, hosting customers and developers should do.

On Wednesday 21 October 2026 the Node.js project shipped security releases 26.12.1, 24.22.1 and 22.23.4, fixing ten vulnerabilities, four of them rated high. The most serious is CVE-2026-111497, an out-of-bounds write in the Brotli decoder bundled with Node.js, which the project says permits remote code execution. The release also fixes three TLS certificate validation bugs, a DNS query truncation on NUL characters, and blocks certificates from the compromised eMudhra certificate authority. One day after PHP, this is the second major release this week that touches most web servers, and as with PHP the only complete fix is updating. This article sums up what is officially confirmed, who is really exposed and what each kind of operator should do.

What happened: ten bugs, four high, no CVSS scores

Node.js pre-announced the release on 14 October with a single piece of information: the highest severity would be "high". On Wednesday 21 October at 10:00 UTC the tags for all three versions were created, and shortly after 12:00 UTC the project updated the same post with the list of issues. The official text is terse: for each vulnerability it gives a title, the affected lines and a thank-you, nothing more. No CVSS score, no vector, no description of exploitation conditions. The CVE.org records were still "reserved" at the time of writing and NVD had no entries. Any numbers circulating online therefore do not come from Node.js.

Breakdown by severity: four high (Brotli, two TLS bypasses, DNS), three medium (keep-alive socket, DNS SANs for IP addresses, random values in snapshots) and three low (ZIP, Permission Model twice). Eight of the ten were reported by external researchers through HackerOne; the reports are not public yet. The fixes were written by core team members James Snell, Matteo Collina, Antoine du Hamel, Filip Skokan and Rafael Gonzaga. The commits landed on the public branches on the afternoon of 20 October, a day before the release, so anyone watching the repository saw the content of the fixes early.

Beyond the CVEs, the release bumps dependencies: c-ares 1.34.9 on all lines, OpenSSL 3.5.9 on 24.x and 22.x, SQLite 3.53.4 on 22.x, undici on all lines and the root certificate store from NSS 3.131.1. None of it is flagged as breaking; these are patch releases.

CVE-2026-111497: the Brotli decoder writes out of bounds, and more code calls it than you think

The bug is not in Node.js code itself but in Google's Brotli library, which Node.js bundles and exposes through the zlib module. The Node.js fix is a cherry-pick of an upstream commit, "Detect and report invalid stream earlier", by Evgenii Kliuchnikov of Google, dated 27 February 2026. The fix therefore sat on Brotli's main branch for eight months before anyone labelled it a security fix. The mechanism, from the diff: if a compressed stream declares a single block type and then nevertheless requests a block switch, the fast decoding path ignored that failed step and carried on, leading to a write outside the allocated buffer. The patched decoder returns a new error, BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH, and stops. Google released Brotli 1.2.1, 1.1.1 and 1.0.10 the same day with a one-line note about CVE-2026-111497; the repository has no advisory and no issue.

The bug was reported by Matteo Collina, maintainer of undici and Fastify, and fixed by Antoine du Hamel. Node.js says nothing about how far it can be pushed. Vercel writes in its bulletin that researchers demonstrated process-memory disclosure and command execution on unpatched 22.x, 24.x and 26.x, with the permissions of the Node.js process. Nobody reports in-the-wild exploitation and the CISA KEV catalogue does not list the bug.

More important than the mechanism is the question of who actually decodes Brotli. Node.js does not decompress anything on its own, but your code or your libraries do, often without you knowing:

  1. The built-in fetch() (undici) automatically decompresses responses with Content-Encoding: br. It is enough for your app to download data from a server the attacker controls. Sending Accept-Encoding: identity does not help; a malicious server can reply with Brotli anyway.
  2. axios, got and node-fetch decompress responses by default.
  3. Express 5 uses body-parser 2.x, which supports Brotli since 2.0.0 and defaults to inflate: true. An attacker can therefore send a compressed request body. Express 4 with body-parser 1.x does not support Brotli.
  4. Direct calls to zlib.brotliDecompress(), brotliDecompressSync() or createBrotliDecompress() on untrusted data, typically in proxies, CDN logic or upload handling.
  5. Self-hosted Next.js: according to Vercel, image optimisation does not block Brotli responses.

Code that only compresses with Brotli has no entry point. And watch your build: Debian and Homebrew build Node.js with --shared-brotli, against the system library. There the fix does not come with a new Node.js version but with the libbrotli package. On Homebrew, brotli 1.2.1 was available before the patched node. The same bug affects other projects carrying their own copy of Brotli: Bun still had its backport in an open pull request at the time of writing, the nginx ngx_brotli module updated its submodule on 22 October, and Chrome has shipped the fixed code since March.

TLS and DNS: a hostname-check bypass, not a MITM on every connection

The claim that the bugs "enable MITM" needs qualifying. The three TLS bugs share a pattern: an IP address written in an unusual form is not recognised as an IP address, certificate checking falls through to the branch for domain names, and a certificate issued for a name then authenticates a connection to an IP address. The attacker still needs a certificate from an authority the client trusts, with a DNS: entry shaped like an IP address. That is not trivial, although after October's eMudhra compromise it is less theoretical than it used to be.

CVE-2026-106069 (high, all lines): the host only becomes an IP address after normalisation. Example from the fix's tests: url.domainToASCII() reads 0127.0.0.1 as octal and returns 87.0.0.1, while the system resolver via dns.lookup() returns 127.0.0.1. A certificate with DNS:87.0.0.1 therefore authenticates a connection to localhost. Matteo Collina's fix rejects hosts that become IP addresses only after domainToASCII(). The same commit also resolves CVE-2026-106072 (medium), where a non-canonical IPv4 address lets a DNS SAN authenticate an IP endpoint.

CVE-2026-105483 (high, 24.x and 26.x only): an IP address written in fullwidth digits, 127.0.0.1, passed against a certificate for 127.0.0.1. It is a regression, and James Snell's commit describes it as "a low-severity bug that's not easy to exploit. Basically it just means the hostname check can be bypassed." The project nevertheless rated it high, one of this release's inconsistencies.

CVE-2026-105482 (high, all lines): dns.resolve*() passed names containing a NUL character to the c-ares library, which as C code reads the string only up to the first NUL. The query example.com\0.trusted.corp thus became a query for example.com. If an application first validates that a name ends in a trusted domain and then hands it to the resolver, it validates something other than what it actually asks for. The patched version throws ERR_INVALID_ARG_VALUE.

One more TLS change has no CVE number. On 17 October Mozilla received a report that the private keys of eight eMudhra (emSign) intermediate certificates had been compromised, and browsers blocked them between 17 and 19 October. Node.js responds in all three versions: chains containing the blocked public keys are rejected with CERT_REVOKED, and the root store from NSS 3.131.1 no longer contains the emSign ECC Root CA - G3 root. If your application calls an API whose certificate chains to eMudhra, the connection will fail after the update. The right reaction is to push the API operator, not to disable rejectUnauthorized.

The rest of the release: keep-alive, random values in snapshots, Permission Model

CVE Severity Lines What it fixes
CVE-2026-111497 high 26, 24, 22 Brotli decoder: out-of-bounds write, possible code execution
CVE-2026-105482 high 26, 24, 22 dns.resolve truncates names containing a NUL character
CVE-2026-106069 high 26, 24, 22 TLS: host becomes an IP address only after normalisation
CVE-2026-105483 high 26, 24 TLS: IP address written in fullwidth digits
CVE-2026-105481 medium 26, 24, 22 Keep-alive socket processes a response written before the request was sent
CVE-2026-106072 medium 26, 24, 22 Non-canonical IPv4 lets DNS SANs authenticate IP endpoints
CVE-2026-106074 medium 26, 24, 22 crypto.randomUUID() and randomInt() repeat values in snapshots and SEAs
CVE-2026-106070 low 26 ZipFile returns attacker-controlled bytes after a failed header check
CVE-2026-106071 low 26 Permission Model: over-grant via wildcard lookup
CVE-2026-106073 low 26, 24, 22 Permission Model: bypass via fs.realpathSync

Of the three medium issues, CVE-2026-106074 deserves attention. Anyone shipping an application as a single executable (SEA) or using startup snapshots has entropy caches baked into the binary, so every launched process generates the same UUIDs and the same "random" numbers. Updating the runtime is not enough; those artefacts must be rebuilt. The keep-alive bug concerns the HTTP agent, which could match a request with a response that was already sitting on the socket before the request was sent. The Permission Model bugs do not affect you unless you use --permission.

Who is affected: supported lines got the fix, 20.x and 25.x did not

Three supported lines received the fix. 22.x (Jod) is in maintenance LTS until 30 April 2027, 24.x (Krypton) moved to maintenance on 20 October per the schedule and ends on 30 April 2028, and 26.x is still Current and becomes LTS on 28 October. The same day the alpha phase of Node.js 27 begins under the new annual cycle.

Node.js 20.x reached end of life on 30 April 2026, 25.x on 1 June 2026, and Node.js reminds that end-of-life versions are always affected by a security release and get no fix. In practice that is the largest exposed group: Node 20 is still the default on many hosts, in Debian trixie and in countless Dockerfiles. Moving from 20 to 22 is usually painless; moving from 20 to 24 requires testing native modules. Our guide on critical PHP vulnerabilities from the day before shows the same pattern: unsupported branches are where the risk concentrates.

Where to get the fix, as of the morning of 22 October:

  1. nodejs.org, nvm, fnm: the fixed versions have been in index.json since release; nvm install 24 pulls them directly.
  2. Docker Hub: the 22.23.4, 24.22.1 and 26.12.1 tags and their slim variants were pushed on the evening of 21 October (slim) and after 02:30 UTC on 22 October (full and alpine). The floating tags node:22, node:24 and node:26 moved; an image pinned to node:24.22.0 stays vulnerable. Build with docker build --pull.
  3. NodeSource: 22.23.4 and 24.22.1 in the repositories. Homebrew: node, node@24 and node@22 updated.
  4. Alpine: only nodejs-current 26.12.1 in edge is fixed; the nodejs packages in stable branches are not yet. Debian: the tracker lists the bug as unfixed in all suites (bookworm ships Node 18, trixie 20); the fix will arrive via libbrotli. Fedora: neither 22.23.4 nor 24.22.1 yet. Chocolatey: nodejs-lts awaiting moderation on 24.22.0.

How providers are responding: CloudLinux building, Plesk patched Windows only, Vercel deployed WAF rules

Unlike with PHP, no Czech or Slovak host has published anything this time. Webglobe, Websupport, Forpsi, nextHosting and Český hosting had only PHP on their status pages and blogs on 21 and 22 October. That is not surprising: Node.js on shared hosting usually runs through the CloudLinux Node.js Selector, and the fix arrives with its packages.

Provider / platform Status (22 Oct 2026, 12:30 UTC)
CloudLinux Incident opened at 10:28 UTC: building alt-nodejs 22.23.4, 24.22.1 and 26.12.1 for CloudLinux OS 7 to 9 and TuxCare 7 to 10, plus ea-nodejs22 for cPanel. Promised in the stable repositories by 12:40 UTC. Builds for older, end-of-life Node.js versions that CloudLinux still ships are also in progress; their numbers will be posted separately.
cPanel EasyApache 4 Version 25.91 of 21 October covers PHP, libcurl and nginx; no ea-nodejs line in the changelog. The fixed ea-nodejs22 comes via CloudLinux.
Plesk Obsidian 18.0.81 Update 4 of 21 October updates Node.js 24.22.1 and 22.23.4 in the Windows section only. The Linux changelog has no entry.
Vercel Bulletin: WAF rules blocking the inbound vector deployed on 21 October for every project at no cost; patched runtimes are "rolling out". Projects on 22.x and 24.x need no change; projects on Node 20 must switch versions and redeploy. The WAF does not cover responses your functions fetch themselves.
Heroku Buildpack v375 (classic) and heroku/nodejs 5.8.4 (CNB) of 21 October include all three versions. A new deploy is enough.
Render New services default to 24.22.1; services created before 21 October keep an older default. Exact pinned versions do not move.
Paketo node-engine 8.11.0 of 21 October.
Netlify, Cloudflare, Railway, Fly.io, AWS Lambda, Google Cloud, Azure, DigitalOcean No statement. Lambda in Auto mode gets the fix through a two-phase rollout with no published date; container images must be rebuilt by you. On Fly.io the version is written into the Dockerfile; bump it manually. Cloudflare Workers do not run Node.js but workerd with its own copy of Brotli; whether it is affected, Cloudflare has not said.

Anyone who does not want to wait for CloudLinux can switch the app to a higher major version in cPanel, but that brings no fix until the new package is in the repository. The sensible move is to watch the incident and restart the app once the packages are installed.

What to do today: depending on where the app runs

Hosting customer with a Node.js Selector (cPanel, Plesk). The provider patches the binary; you check this:

  1. Find out which version the app runs on. In cPanel under Setup Node.js App, in Plesk on the domain card. If it is 20 or older, switch to 22 or 24 and test the app; the old line gets no fix, and even if CloudLinux ships a package for it, that is a temporary patch on unsupported code.
  2. Restart the app once the provider announces the fix. The Restart button in the selector or touch tmp/restart.txt with Passenger. A running process keeps the old binary.
  3. If the provider has announced nothing, ask specifically about alt-nodejs 22.23.4 / 24.22.1.

VPS or own-server admin. Here the responsibility is yours:

  1. Update to 26.12.1, 24.22.1 or 22.23.4 via nvm, NodeSource or the distribution package as soon as one exists. Verify with node -v.
  2. Check whether Node.js uses the system Brotli: ldd $(which node) | grep brotli on Linux, otool -L on macOS. If so, update libbrotli too (1.2.1, 1.1.1 or 1.0.10).
  3. Restart every process: pm2 restart all (and pm2 update if PM2 itself runs on the old binary), systemctl restart, rebuild and redeploy for containers.
  4. Rebuild SEA binaries and startup snapshots, otherwise the repeated-UUID bug stays.
  5. Interim measures if you cannot update right away: set inflate: false in body-parser (rejects compressed request bodies), decompress: false in axios. There is no workaround for fetch(); a server can send Brotli regardless of Accept-Encoding. For DNS, check name.includes('\0') before calling dns.resolve*; for TLS, reject hosts that are not an IP address per net.isIP() but become one after url.domainToASCII(). None of this replaces the update.
  6. Do not fix certificate failures after the update by disabling rejectUnauthorized or setting NODE_TLS_REJECT_UNAUTHORIZED=0. If a connection fails with CERT_REVOKED, the other side uses an eMudhra certificate and the problem is theirs.

Developer and agency. Go through projects by runtime version and deployment method. Three risk groups: anything on Node 20 or older, images and Dockerfiles with a pinned patch version (node:24.22.0, an .nvmrc with an exact number, volta.node in package.json), and applications that fetch content from third-party servers (webhooks, scrapers, proxies, image optimisers), because that is where Brotli decoding happens without your knowledge. A range such as engines.node: ">=24" moves the version automatically on most PaaS platforms; an exact pin does not.

Summary

Node.js fixed ten bugs, and the only one that can lead to code execution was inherited from Google, where the fix sat for eight months without a security label. Exposed is everyone who lets Node.js decode untrusted Brotli data, which thanks to automatic decompression in fetch(), axios and Express 5 means most applications that talk to anything. The TLS bugs do not allow eavesdropping on every connection, but combined with a certificate from a trusted authority they bypass the hostname check, and exactly such certificates could have come out of the compromised eMudhra. The fix is available everywhere except stable distribution packages; CloudLinux promised alt-nodejs by Wednesday midday, Plesk for Linux is still silent. Anyone running on Node 20 has nothing to update and must migrate. For the owner of an ordinary PHP website nothing changes; for anyone running a Node.js backend, a proxy or self-hosted Next.js, this is today's job.

About the author

Tomáš Mahrík

Tomáš Mahrík

Full stack developer with 15+ years of experience, who doesn’t just see hosting as a user, but as someone responsible for operating their own projects on a daily basis.

Stay Updated

Get the latest hosting deals, coupons, and expert tips delivered to your inbox.

Node.js CVE-2026-111497: Brotli RCE, TLS and DNS flaws