ELS Builds for the October Node.js Fixes Are on the Way
Fixed builds for affected end-of-life Node.js lines are expected in the ELS for Runtimes repositories over the next 24 to 48 hours, covering the October 21 CVEs that apply to each line.
ELS for Runtimes status: Our team is finishing patches for the affected end-of-life versions now, and timing varies a little by version and platform. The updates come through the same yum, apt or apk workflow you already use, and the builds live under /opt/alt/ next to any other Node.js version on the host. Plan a restart for every Node.js process once the package is in.
The Short Version
What happened: On October 21, 2026, Node.js shipped security releases for its three supported lines, 22.x, 24.x and 26.x, one week after a pre-alert. Ten CVEs, four of them rated High. Jump to this section →
What the worst one does: CVE-2026-111497 is an out-of-bounds write in the Brotli decoder that ships inside Node.js, and the advisory says it permits remote code execution. Any process on an unpatched build that decompresses Brotli data an attacker controls is exposed, and your code doesn’t have to call Brotli directly for that to happen. Jump to this section →
How bad it really is: The first public proof of concept only reached code execution on a deliberately weakened build, which made it look less urgent than the advisory’s wording. Then Imperva’s red team published Node2Shell, showing code execution against the official Node.js 24.22.0 image through fetch() and Express 5 request parsing. No exploitation in the wild has been reported as of October 22. Jump to this section →
Who is affected: Any 22.x, 24.x or 26.x release older than the fixed version for its line, plus the end-of-life lines Node.js no longer patches. The CVE table further down shows which CVEs hit which line. The advisory repeats its standing warning that end-of-life versions are always affected, and Node.js 20, 18, 16, 14 and 12 get nothing from upstream. Jump to this section →
Where things stand: Upstream fixes are out for 22, 24 and 26. Vercel’s WAF blocks the inbound attack path for projects on its platform but doesn’t cover responses your code fetches from elsewhere. ELS for Runtimes builds for affected end-of-life lines are expected over the next 24 to 48 hours.
What to do today: Patch the supported lines, move end-of-life ones onto a fixed ELS build as soon as the one for your line lands, and restart everything, because a running process keeps the old decoder in memory. To see what’s running right now:
ps -eo pid,lstart,args | grep -E '[n]ode( |$)'
The Long Version
Most Node.js security work happens one layer up. Dependabot opens a pull request, npm audit goes quiet, and nobody spends a second thinking about the binary everything runs on. The runtime has CVEs of its own, though, and on October 21 the worst of them turned out to be memory corruption in a decoder your app may be feeding straight from the internet.
On 22, 24 or 26, the fix is boring. Bump the patch version, roll the fleet, move on.
It gets a lot less boring when part of the estate is still on Node.js 18 or 20 because a native addon won’t build on anything newer, or because a framework migration keeps slipping a quarter at a time. Those lines ship a Brotli decoder that predates the fix, and they receive none of the patches. Most of this post is about that gap.
Ten CVEs and the One Everyone Is Talking About
The project gave its usual week of warning. On October 14, Antoine du Hamel posted a pre-alert to the oss-security list saying new 22, 24 and 26 releases would land on or shortly after October 21, with the highest severity rated High. No component names and no CVE numbers, which is normal for Node.js. The releases arrived on schedule.
CVE-2026-111497 is the headline. The advisory credits Khai Tran and Sylvie Mayer of OpenAI Codex Security with the report and Matteo Collina and Antoine du Hamel with the fix. A few early write-ups said Collina found it, but the advisory has him on the fixing side. The patch itself is a cherry-pick of upstream Brotli commit 0ed4268a, which carries the very understated message “Detect and report invalid stream earlier.”
The timing on that commit is odd. According to The Hacker News, the change first went into Brotli’s source in February 2026, but it didn’t ship in a security release until October 21, when Brotli 1.2.1, 1.1.1 and 1.0.10 came out. So a fix for a memory-safety bug sat in a public repository for months, and an AI security tool is credited with the finding. Expect to see that combination a lot more, and expect the time between an unannounced upstream commit and a working exploit to keep shrinking.
The other three High-severity issues all sit in name handling. CVE-2026-106069 lets a TLS certificate pass hostname verification when the host only loosely resolves to an IP address. CVE-2026-105483 is a separate bypass that works when the IP is written in fullwidth digits. That one only affects 24.x and 26.x, and The Hacker News points out that the commit fixing it describes the bug as low-severity and hard to exploit, even though the advisory rates it High. CVE-2026-105482 is in dns.resolve(), where a name containing a NUL byte could get cut short, so the name your code validated and the name actually queried weren’t the same. Patched versions reject NUL bytes in resolver input outright.
The Medium and Low items are narrower but not irrelevant. One worth flagging for anyone shipping CLIs or tools as single executable applications is CVE-2026-106074. The entropy caches behind crypto.randomUUID() and crypto.randomInt() were getting saved into startup snapshots and SEAs, so every process launched from the same binary produced the same “random” values. Anything using those UUIDs as tokens or nonces deserves a look.
| CVE | Severity | Issue | Affected lines |
| CVE-2026-111497 | High | Out-of-bounds write in the bundled Brotli decoder permits remote code execution | 26.x, 24.x, 22.x |
| CVE-2026-106069 | High | TLS hostname bypass when the host loosely resolves to an IP address | 26.x, 24.x, 22.x |
| CVE-2026-105483 | High | TLS hostname bypass when the IP is written in fullwidth digits | 26.x, 24.x |
| CVE-2026-105482 | High | dns.resolve() truncates names containing a NUL character |
26.x, 24.x, 22.x |
| CVE-2026-105481 | Medium | Keep-alive socket processes a response written before the request was sent | 26.x, 24.x, 22.x |
| CVE-2026-106072 | Medium | Non-canonical IPv4 hostnames let a certificate’s DNS SANs authenticate IP endpoints | 26.x, 24.x, 22.x |
| CVE-2026-106074 | Medium | randomUUID() and randomInt() caches saved into startup snapshots and SEAs |
26.x, 24.x, 22.x |
| CVE-2026-106070 | Low | ZipFile returns attacker-controlled prefix bytes after failed header validation | 26.x |
| CVE-2026-106071 | Low | Permission Model filesystem over-grant via wildcard lookup | 26.x |
| CVE-2026-106073 | Low | Permission Model bypass through fs.realpathSync |
26.x, 24.x, 22.x |
The releases also refresh c-ares, undici and the bundled CA certificates on all three lines, OpenSSL on 22.x and 24.x, and SQLite on 22.x. The certificate change is the sleeper. Node.js uses Mozilla’s root list but does no revocation checking by default, so chains built on the eight eMudhra intermediates reported compromised this month kept verifying right up until this release. The patched builds add a compiled-in blocklist of those keys, the same hashes Go shipped in its own fix. Two caveats apply. TLS sessions resumed from before the upgrade aren’t re-verified until they time out, and end-of-life lines don’t get the block at all.
How Brotli Gets Into Your Process Without Asking
Node.js doesn’t decode Brotli on its own. Something in your process has to ask for it, and plenty of things ask without you noticing.
Outbound traffic is the big one. The built-in fetch() decompresses responses carrying Content-Encoding: br automatically, and there’s no caller option to switch that off. Current versions of axios, node-fetch and got do the same by default. So any service that requests URLs it doesn’t control is on the path – a webhook sender, a link-preview worker, a scraper, an AI agent pulling web pages into a prompt. Vercel’s bulletin adds another path that’s easy to miss. Self-hosted Next.js doesn’t block Brotli responses in image optimization, so the fix there is upgrading Node.js, not touching Next.js config.
Inbound is the second path. Express 5’s body parsers, via body-parser 2.x, inflate Brotli request bodies by default, so an API using express.json() will decompress a Brotli body it never asked for. Express 4’s body-parser 1.x doesn’t handle Brotli bodies and rejects them with a 415. A bare http server doesn’t decode request bodies just by receiving them. Then there’s the direct route, where code calls zlib.brotliDecompress() or createBrotliDecompress() on uploads, archives or cached artifacts.
A quick way to sort the urgency. Anything internet-facing on Express 5, or anything that fetches arbitrary URLs, should be patched today. An Express 4 service that only talks to backends you own, with no custom decompression middleware and no direct zlib Brotli calls on outside data, can sit a little lower on the list. It still needs the update this week, because the other nine CVEs don’t care which Express version you run. The advisory offers no workaround. Setting inflate: false on body parsers, turning off decompression in axios or got, or rejecting Content-Encoding: br at the proxy can narrow exposure for a few hours, but fetch() has no off switch and none of those measures is a fix.
From Lab Curiosity to Stock Image in a Day
The severity story moved fast. A public proof of concept appeared on October 22, and per The Hacker News, it reached code execution only against a deliberately weakened test build. On a standard build, its author reported a crash and a limited memory read. It would have been easy to read that and push the ticket to next sprint.
The same day, Imperva’s red team published Node2Shell. In fresh containers running the official Node.js 24.22.0 Linux arm64 image, with no debugger and no instrumented binary, Brotli data delivered as a fetched response or as a request body made the application run code supplied with that data. They reproduced it through fetch() and through Express 5.0.0 to 5.3.0 with the raw() and json() parsers. The same inputs bounced off 24.22.1. It took multiple attempts in their setup, and they only tested arm64, so it isn’t push-button – but it’s a long way from “only works in a lab.”
If triage parked this based on the first proof of concept, reopen it. Imperva also notes that unexpected process exits during decompression can help prioritize investigation, though neither crashes nor decoder errors are a reliable sign of exploitation on their own. As of October 22, no in-the-wild exploitation has been reported, and CVE-2026-111497 isn’t in CISA’s Known Exploited Vulnerabilities catalog.
The Lines Upstream Won’t Touch
Node.js security advisories carry a standing warning, and this one repeats it. End-of-life versions are always affected when a security release occurs. That’s a policy statement, not a per-CVE test result. What’s certain is that the Brotli fix only went into Node.js on October 21, so no upstream end-of-life release includes it. Node.js 20 reached end of life on April 30, 2026, 18 on April 30, 2025, 16 on September 11, 2023, 14 on April 30, 2023, and 12 on April 30, 2022. None of them gets a release for this. Vercel was blunt about it, too. Projects on its platform still pinned to Node.js 20 have to switch to 22 or 24 and redeploy, because 20 won’t receive the fix.
How far back does the Brotli bug go? Brotli has shipped inside Node.js since the 10.x and 11.x days, and the Brotli project pushed this fix all the way back to its 1.0 branch. That’s a strong hint the vulnerable decoder logic is old, not something introduced last spring. Node.js 16 and older don’t have a global fetch(), but axios and got still hand Brotli responses to the same zlib decoder, and direct zlib calls work the same on every line.
The installs that cause trouble are rarely the ones anyone thinks of as Node.js servers. A build container pinned to Node 16 because an image-processing addon stopped compiling on 18. An internal admin tool someone shipped as a single binary in 2022. A vendor appliance with Node 14 under its web console. Picture a payments team with 40 services on Node 18, held there by one dependency whose maintainer moved on, reading an RCE advisory with no patch attached. Nobody is migrating 40 services by Friday.
That’s the gap ELS for Runtimes covers. TuxCare provides security fixes for Node.js versions that have reached end of life, so once the build for its line lands, a service pinned to 18 gets this month’s fixes as a package update instead of an emergency migration. The migration still needs to happen, and the Node.js end-of-life dates aren’t moving. With ELS, it happens on a schedule your team picks, not one an advisory picks for you.
Which Node.js Lines Get a Fix and From Where
| Node.js line | Upstream status | Where the October fix comes from |
| 26.x | Current, moving to Active LTS on October 28, 2026, end of life April 30, 2029 | Node.js 26.12.1 |
| 24.x | Maintenance LTS since October 20, 2026, end of life April 30, 2028 | Node.js 24.22.1 |
| 22.x | Maintenance LTS until April 30, 2027 | Node.js 22.23.4 |
| 20.x | End of life since April 30, 2026 | No upstream fix. ELS for Runtimes |
| 18.x | End of life since April 30, 2025 | No upstream fix. ELS for Runtimes |
| 16.x and 14.x | End of life since 2023 | No upstream fix. ELS for Runtimes |
| 12.x | End of life since April 30, 2022 | No upstream fix. ELS for Runtimes |
| Odd-numbered lines (25, 23 and earlier) | End of life | No upstream fix. ELS covers 23 on Debian 12, Debian 13 and Alpine. Other versions on request |
The ELS for Runtimes documentation lists Node.js 12 through 26 by operating system, with other versions and architectures available on request. For a deeper look at running past end of life, see this guide to Node.js extended support after EOL.
Here’s what the documentation lists under active support right now:
| Operating system | Package type | OS version | Node.js versions |
| EL 6 (CentOS, CloudLinux, Oracle Linux, etc.) | RPM | 6.x | 12, 14 |
| EL 7 (CentOS, CloudLinux, Oracle Linux, etc.) | RPM | 7.x | 12, 14, 16, 18, 20 |
| EL 8 (AlmaLinux, CentOS, CentOS Stream, CloudLinux, Oracle Linux, etc.) | RPM | 8.x | 12, 14, 16, 18, 20, 22, 24, 26 |
| EL 9 (AlmaLinux, CentOS, CloudLinux, Oracle Linux, etc.) | RPM | 9.x | 12, 14, 16, 18, 20, 22, 24, 26 |
| EL 10 (AlmaLinux, CloudLinux, Oracle Linux, etc.) | RPM | 10.x | 12, 14, 16, 18, 20, 22, 24, 26 |
| Ubuntu | DEB | 18.04 | 12, 14, 16, 18, 20 |
| Ubuntu | DEB | 20.04 | 12, 14, 16, 18, 20, 22 |
| Ubuntu | DEB | 22.04 | 12, 14, 16, 18, 20, 22, 24 |
| Ubuntu | DEB | 24.04 | 12, 14, 16, 18, 20, 22, 24, 26 |
| Debian | DEB | 10 | 12, 14, 16, 18, 20 |
| Debian | DEB | 11 | 12, 14, 16, 18, 20, 22 |
| Debian | DEB | 12 | 12, 14, 16, 18, 20, 22, 23, 24 |
| Debian | DEB | 13 | 12, 14, 16, 18, 20, 22, 23, 24, 26 |
| Alpine Linux | APK | 3.23, 3.24 | 14, 16, 18, 23 |
Builds are available for x86_64 on every supported OS, and for aarch64 (arm64) on Debian 12, Debian 13 and Alpine 3.23 and later. The arm64 builds matter this week, since Imperva’s code execution demo ran on arm64.
Distribution packages work a bit differently. Some builds, including Debian and Ubuntu packages and Homebrew, link a shared Brotli library instead of the copy bundled with Node.js. In that case, track the security updates for the Brotli library your package manager supplies. Checking takes one line:
ldd "$(command -v node)" | grep -i brotli
A libbrotli path in the output means the binary links a shared Brotli library. No match suggests a bundled or statically linked copy, which is how official nodejs.org builds ship, but the command alone can’t prove that, so check your package information or build configuration if it matters. ldd is Linux only. On macOS, where Homebrew lives, otool -L "$(command -v node)" | grep -i brotli does the same job.
Patch, Restart, Then Prove It
Start with inventory, because the list is always longer than expected. Version managers like nvm and Volta put binaries in home directories, distribution packages put them in /usr/bin, and ELS builds live under /opt/alt/ with an enable script per version. These two Bash loops print every node on your current PATH, then every ELS build under /opt/alt/, with the version each one reports:
type -ap node | while read -r n; do echo "$n $("$n" -v)"; done
for e in /opt/alt/alt-nodejs*/enable; do [ -f "$e" ] && ( . "$e" && echo "$e $(node -v)" ); done
That gives you a starting inventory, which isn’t the same as what each service runs. systemd units, PM2 configs, CI runners and container base images can each point at a different binary, so check those too. On RPM-based systems, ELS builds update with yum update 'alt-nodejs*', and the DEB and APK instructions sit on the same docs page.
Don’t trust version strings for the Brotli fix on upstream builds. Node.js cherry-picked the patch without bumping Brotli’s version, so process.versions.brotli looks identical before and after. The release did expose a new zlib constant that patched upstream binaries have and unpatched ones don’t:
node -p "'BROTLI_DECODER_ERROR_FORMAT_BLOCK_SWITCH' in require('zlib').constants"
true means the fixed decoder is present. That check is reliable for official upstream builds only, so for distribution or ELS builds go by the vendor’s advisory. The project’s own npx is-my-node-vulnerable is handy for upstream installs too, but it works from version numbers, so a backported build can look vulnerable when it isn’t.
Then restart, which is the step that gets skipped. A running Node.js process keeps the old decoder until it exits, and a container keeps it until it’s redeployed from an image rebuilt on a patched base. SEAs and startup snapshots have to be rebuilt with the patched runtime, and for CVE-2026-106074 that rebuild is the fix. Rerun the ps command from the top of this post and confirm every Node.js process started after the update went in. Single executable applications and renamed binaries won’t show up in that pattern, so check those by service name.
Last, don’t close the ticket on a scanner’s version comparison. A backported build can still report its original upstream version. Where your tooling supports it, feed it TuxCare’s machine-readable security data for Node.js, which includes CSAF advisories, and keep a record of the package installed, the advisory it addresses and the process actually serving traffic. That’s what an auditor will ask for.
Why a Runtime Flaw Hits Differently
A runtime is a dependency too, and once nobody is shipping fixes for it, every new core vulnerability stays open for as long as that runtime stays in production. Teams that would never ship a two-year-old lodash happily run their code on a Node.js line that stopped receiving security fixes in 2025.
This week also showed how little time there is to plan. Seven days from pre-alert to release, a public proof of concept the next day, a working exploit against the stock image that same day, and fixes that reach exactly three release lines. “Upgrade next quarter” is a perfectly good plan for next quarter. It’s no plan at all for this week.
So patch the supported lines now and restart what needs restarting. For the Node.js lines upstream has left behind, move them onto a fixed ELS build as soon as the one for your line lands, cut their exposure to untrusted Brotli data in the meantime, and then get the migration on a calendar.
How these Events Unfolded
| Date | Event |
| February 2026 | The Brotli fix commit lands in google/brotli, with no security release attached |
| October 14, 2026 | Antoine du Hamel posts the Node.js pre-alert to oss-security, highest severity High |
| October 21, 2026 | Node.js ships 22.23.4, 24.22.1 and 26.12.1, fixing ten CVEs |
| October 21, 2026 | Brotli publishes 1.2.1, 1.1.1 and 1.0.10, and Vercel deploys WAF rules for the inbound path |
| October 22, 2026 | A public proof of concept reaches code execution only on a deliberately weakened build |
| October 22, 2026 | Imperva publishes Node2Shell, with code execution on the official Node.js 24.22.0 image |
| October 22 to 24, 2026 | Expected window for ELS for Runtimes builds covering affected end-of-life Node.js lines |
Sources
Sources for this post include the Node.js October 21, 2026 security release advisory, the release notes for 22.23.4 and 24.22.1, the pre-alert on oss-security, the Node.js release schedule, the google/brotli fix commit, the eMudhra blocklist pull request, Vercel’s security bulletin, Imperva’s Node2Shell research, The Hacker News coverage, the CISA KEV catalog, is-my-node-vulnerable and TuxCare’s ELS for Runtimes documentation for Node.js and machine-readable security data.