You patched PHP. Now patch Node: the October 2026 Node.js security release for Laravel + Vite apps

Node.js 22.23.4, 24.22.1 and 26.12.1 fix four High CVEs, including a Brotli RCE. npm audit will not catch them. How to update Vite, SSR, Sail and CI.

For your AI agent

Copies a prompt. Paste it into the AI agent you already have open.

Read https://laravel-vuejs.space/laravel-node-security-release-october-2026.md
Node.js 22.23.4, 24.22.1 and 26.12.1 fix four High CVEs, including a Brotli RCE. npm audit will not catch them. How to update Vite, SSR, Sail and CI.
Show the diff. Do not commit.
On this page · 12 sections
  1. PHP, in sixty seconds
  2. What the October 21 release fixes
  3. The four High CVEs
  4. Medium and low
  5. Where Node lives in a Laravel app
  6. Find every Node you run
  7. Update the binary
  8. nvm and fnm
  9. Docker
  10. GitHub Actions
  11. Laravel Sail
  12. Laravel Forge
  13. Coolify
  14. Restart runtime Node
  15. Who should treat this as urgent
  16. One checklist for PHP and Node
  17. Recap
  18. FAQ
  19. Next steps
  20. Sources

You patched PHP on October 21. The node binary that runs Vite, npm, and Inertia SSR is a separate install, and Node.js shipped its own security releases the same day. The highest severity is High, including a Brotli decoder bug that can become remote code execution.

TL;DR

  • Install Node.js 22.23.4, 24.22.1, or 26.12.1. Stay on the major you already run. 20.x and older got no build.
  • Four High issues: DNS names cut at a NUL byte, two TLS hostname bypasses, and a Brotli out-of-bounds write in node:zlib that can crash, leak memory, or run code.
  • npm audit and composer audit read lockfiles. They do not inspect the Node binary. node -v does.
  • After the new binary is on PATH, rebuild assets and restart every long-running Node process (Inertia SSR, Supervisor, PM2).

What you'll learn

  • What the four High CVEs actually change
  • Which Node versions are patched
  • Where a Laravel app hides a second Node binary
  • How to update nvm, fnm, Docker, GitHub Actions, Sail, Forge and Coolify, then restart SSR

Who it's for: Laravel + Vue teams who run Node for Vite, npm, Inertia SSR, Sail, or CI asset builds

Applies to: Node.js 22.23.4, 24.22.1 and 26.12.1, from the 21 Oct 2026 security release

PHP, in sixty seconds#

On October 21, 2026 PHP released 8.5.12, 8.4.27, 8.3.36, and 8.2.35. Two bugs, both CVSS v4.0 9.2, both unauthenticated remote code execution: CVE-2026-109618 and CVE-2026-109619. PHP-FPM is in scope. Updating Laravel with Composer does not patch the PHP binary.

If php -v on the server is still older than those releases, do that first. The steps for apt, Docker, Forge, Vapor, Laravel Cloud, and Coolify are in the PHP critical security release. Restart FPM, Octane, Horizon, and queue workers after the PHP upgrade.

Then come back here. A patched PHP-FPM pool and an old node on the same box is a half-finished evening.

What the October 21 release fixes#

The Node.js security release covers the 26.x, 24.x, and 22.x lines. Highest severity on each line is High. These are the builds:

LinePatched releaseWhat to do if you are on it
22.x (LTS)22.23.4Install 22.23.4. Three of the four High bugs affect 22.
24.x (LTS)24.22.1Install 24.22.1. All four High bugs affect 24.
26.x (Current)26.12.1Install 26.12.1 if you already run 26.
20.x and older, plus odd majors such as 23 and 25No releaseThe release schedule marks those lines EOL. Move to 22.23.4 or 24.22.1.

Production apps should stay on an LTS line (22 or 24). Jumping from 22 to 26 is a major upgrade, not the security fix. If you are already on 26, 26.12.1 is the patched Current build.

The release also refreshes libraries that ship inside Node: c-ares on 22, 24, and 26; the CA certificate bundle on those three lines because of the eMudhra intermediate CA compromise; OpenSSL on 22 and 24; SQLite on 22; Undici on 22, 24, and 26. Installing the Node version above is how you pick those up.

Gotcha: npm audit cannot see this

npm audit reads package-lock.json. composer audit reads composer.lock. The bugs below are in the Node binary. A clean audit and a vulnerable node -v fit in the same terminal.

The four High CVEs#

CVESeverityLinesWhat goes wrong
CVE-2026-105482High22, 24, 26dns.resolve*() and dns.reverse() cut a hostname at a NUL byte and return records for the shorter name, with no error.
CVE-2026-105483High24, 26A TLS hostname written with fullwidth digits is not treated as an IP, so a DNS name on the certificate can authenticate an IP the cert was not issued for.
CVE-2026-106069High22, 24, 26A leading-zero IPv4 literal can be checked against a different address than the one the socket connected to.
CVE-2026-111497High22, 24, 26Malformed Brotli written to node:zlib can write past an internal buffer: crash, memory disclosure, or remote code execution.

CVE-2026-105482 (DNS NUL). A hostname that contains a NUL byte is passed to c-ares as a C string, so resolution stops at the byte. dns.resolve() of a name that looks like attacker.example plus a NUL plus .trusted.example can come back with records for attacker.example and no error. An earlier fix covered dns.lookup() and the host option on net and TLS sockets. This release covers the dns.resolve* family and dns.reverse(). An allowlist that checks the original string, then resolves it, can act on a different name than the one it allowed.

CVE-2026-105483 (fullwidth digits). tls.checkServerIdentity() did not treat a host written with fullwidth digits as an IP address, even when that text normalizes to a normal IPv4 address. The check then compared DNS names and the Common Name. A certificate with a DNS name such as a numeric-looking wildcard could be accepted for an IP it was not issued for. This is a regression after an earlier IP-check fix. It affects 24 and 26 only. Node 22 still needs 22.23.4 for the other three High bugs.

CVE-2026-106069 (leading-zero IPv4). The certificate check follows WHATWG URL rules, which read a leading-zero octet as octal (0127.0.0.1 becomes 87.0.0.1). The connection itself goes through the operating system resolver, which on some platforms (the advisory names macOS) reads the same text as decimal (127.0.0.1). Node can then accept a certificate for a different IP than the one on the socket. Affects 22, 24, and 26.

CVE-2026-111497 (Brotli). The Brotli decoder bundled with Node can write past an internal buffer when the input is malformed. One invalid block switch is reported as “need more input” instead of a hard format error, so the streaming decoder keeps appending into an eight-byte buffer. Whoever can supply Brotli bytes that your process decompresses with node:zlib can corrupt memory. That can crash the process, disclose memory, or run code. Affects 22, 24, and 26.

Vite’s own CSS and JS output is not Brotli-decoding untrusted request bodies. The bug matters when a Node process you run decompresses data an attacker can influence: an upload, a proxy, a fetch that inflates a body, an SSR server that pulls remote content through a library using node:zlib. The TLS and DNS bugs matter for any of those processes that also open outbound connections.

Medium and low#

Same release, shorter list. Still fixed only by the new binary.

CVESeverityLinesShort version
CVE-2026-105481Medium22, 24, 26A keep-alive socket can treat bytes that arrived before the new request as that request’s response. Leftover from an earlier fix.
CVE-2026-106072Medium22, 24, 26Non-canonical IPv4 spellings (hex, a single integer, short forms) can match a DNS name on a certificate and authenticate an IP endpoint.
CVE-2026-106074Medium22, 24, 26Entropy caches for crypto.randomUUID(), crypto.randomUUIDv7(), and crypto.randomInt() can be saved into a startup snapshot or a single-file executable, so every process repeats the same values until the cache refills.
CVE-2026-106070Low26After a ZIP header check fails, zlib.ZipFile can return bytes from the start of the archive.
CVE-2026-106071Low26A wildcard --allow-fs-read or --allow-fs-write can also allow the path without the wildcard suffix, in a narrow permission-tree layout.
CVE-2026-106073Low22, 24, 26fs.realpathSync() skips the permission-model read checks. An earlier fix covered fs.realpathSync.native() only.

The snapshot bug matters if you build a Node SEA or pass --build-snapshot and those crypto helpers ran while the snapshot was created. Rebuild the snapshot on the patched Node. The permission-model bugs matter if you start Node with --permission. A normal Laravel + Vite deploy does neither. The keep-alive and non-canonical IP bugs are the medium issues that can show up in an HTTP client.

Where Node lives in a Laravel app#

PHP answers the HTTP request. Node usually shows up in two other places, and they are often different binaries.

Build timeRun time
What it doesnpm ci, Vite, npm run build or npm run build:ssrInertia SSR, and any small Node service you keep up next to PHP
Typical homesYour laptop, GitHub Actions, Sail, a Docker build stage, Forge’s deploy user, a Coolify build containerSupervisor or PM2 running php artisan inertia:start-ssr, a container that stays up
What an old binary exposesnpm’s HTTPS to the registry, and whatever the build script decompresses or resolvesTLS and DNS on outbound calls, Brotli if that process inflates untrusted bytes, plus a process that keeps the old binary in memory until you restart it
What a PHP patch changesNothingNothing

npm run dev on your laptop and npm run build in CI do not share a Node install. Sail’s laravel.test container does not use the Node from nvm on the host. The SSR daemon started last week is still the binary that was on PATH when Supervisor spawned it.

Heads up: engines.node is a note, not an upgrade

"engines": { "node": "22.23.4" } in package.json, and a .nvmrc with the same number, tell tools which version you want. They do not replace the binary on the server. node -v in that environment does.

Find every Node you run#

Run the check in each place that actually executes Node. Skip the lines for tools you do not use.

terminalbash
node -v && which node
./vendor/bin/sail node -v
docker compose exec app node -v

You want v22.23.4, v24.22.1, or v26.12.1. Anything on 22.x below 22.23.4, on 24.x below 24.22.1, or on 26.x below 26.12.1 does not include this fix.

Also open the last GitHub Actions run and read the Node version the setup step installed, SSH to the Forge server as the user the deploy script runs as, and open a shell in the Coolify container. Three older 22.x builds and one v22.23.4 means you found one of the four.

Check this machine

bash
node -v && which node

Then run the same command where the assets are built and where SSR stays up. The laptop is the easy one to fix and the easy one to mistake for the server.

Update the binary#

Pick the patched release on your current major: 22.23.4, 24.22.1, or 26.12.1. Update every install you found above. Then do the restart before you call it done.

nvm and fnm#

terminalbash
nvm install 22.23.4
nvm alias default 22.23.4
nvm use 22.23.4
node -v

fnm is the same shape:

terminalbash
fnm install 22.23.4
fnm default 22.23.4
fnm use 22.23.4
node -v

Swap in 24.22.1 or 26.12.1 if that is your line. Put the version in .nvmrc so the next shell follows it.

Volta: volta install node@22.23.4, then volta pin node@22.23.4 in the repo. asdf: asdf install nodejs 22.23.4 and asdf set nodejs 22.23.4 so .tool-versions records it. Both still need node -v afterwards.

Docker#

Pin the patched tag on the stage that runs npm, and on any runtime stage that still contains Node for SSR:

Dockerfiledockerfile
FROM node:22.23.4-bookworm AS assets

A floating tag such as node:22 moves when you pull. A pin such as node:22.21.0 stays vulnerable until you change it. Rebuild without a cached base layer:

terminalbash
docker build --pull --no-cache -t my-app .
docker compose up -d --force-recreate
docker compose exec app node -v

Use node:24.22.1 or node:26.12.1 if that is the line. Alpine tags follow the same version number.

GitHub Actions#

npm audit in the workflow does not bump Node. The version is the node-version input of actions/setup-node:

.github/workflows/deploy.ymlyaml
- uses: actions/setup-node@v7
  with:
    node-version: 22.23.4

If the workflow says node-version: 22 or node-version-file: .nvmrc, the job installs whatever that major or file currently resolves to. Re-run the workflow after .nvmrc is updated and read the version in the setup step. A pin like 22.20.0 stays on 22.20.0 until the file changes. The action’s own major (@v4, @v7) is not the security fix.

Laravel Sail#

./vendor/bin/sail npm runs inside laravel.test. The Node from nvm on your laptop is a different binary. Check the one Sail uses:

terminalbash
./vendor/bin/sail node -v

Current Sail PHP 8.4 and 8.5 Dockerfiles install Node from NodeSource with ARG NODE_VERSION=24 and setup_$NODE_VERSION.x, which is a major, not a patch. A rebuild installs whatever patch NodeSource is publishing for that major. If you have published the runtime (sail artisan sail:publish), the copy in docker/ is the one that builds. Set NODE_VERSION to 22, 24, or 26, then:

terminalbash
./vendor/bin/sail build --no-cache
./vendor/bin/sail down
./vendor/bin/sail up -d
./vendor/bin/sail node -v

You want that command to print v22.23.4, v24.22.1, or v26.12.1. If the rebuild is still on an older patch, NodeSource has not shipped it yet. Rebuild again once it has, or install the patched binary in that Dockerfile yourself.

Laravel Forge#

Forge runs npm ci and npm run build on the server, as the deploy user, with whatever node is on that user’s PATH. SSH in and run node -v and which node as that user. Update the Node version installed on the server (Forge’s Node install, or nvm if the deploy script loads it). A .nvmrc only helps when the deploy script actually runs nvm use.

Redeploy so the asset build runs on the new binary. If a daemon runs Inertia SSR, restart that daemon after the binary is updated. The deploy guide is the build and the process that has to stay up.

Coolify#

The build container and the running container can disagree. Open a shell in both and run node -v.

Nixpacks picks a Node major from NIXPACKS_NODE_VERSION, from engines.node, or from .nvmrc. Set that to 22, 24, or 26, redeploy, and read node -v in the build log. Nixpacks does not take a patch number, so 22 might still be an older 22.x until the Nixpacks image you use has caught up.

When the log is still behind 22.23.4, 24.22.1, or 26.12.1, build from a Dockerfile that pins node:22.23.4 (or the matching line) and redeploy with the build cache off, the same way you would pull a patched PHP base image. Recreate the container so a long-running SSR process starts on the new binary.

Restart runtime Node#

The new binary does nothing for a process that already started. Inertia SSR is a Node process Supervisor or PM2 keeps alive. npm run build writes public/build. It does not restart that process, and it does not refresh bootstrap/ssr. Use npm run build:ssr when you ship SSR, then restart.

If you start SSR yourself:

terminalbash
php artisan inertia:stop-ssr
php artisan inertia:start-ssr
php artisan inertia:check-ssr

If Supervisor owns it, restart the program name you configured (the deploy guide uses the same inertia:start-ssr command):

terminalbash
sudo supervisorctl restart inertia-ssr
php artisan inertia:check-ssr

PM2 is the same idea. Restarting is enough when the process looks up node or php on PATH. It is not enough when the PM2 config stores an absolute path under ~/.nvm/versions/node/ that still points at the previous install. Point that path at the patched install, then restart:

terminalbash
pm2 restart inertia-ssr
pm2 show inertia-ssr

In the pm2 show output, the interpreter or script path should be the new version. inertia:check-ssr confirms the SSR server answers. The SSR guide covers the build and the window crashes. This page is only the binary and the restart.

Development does not need inertia:start-ssr. npm run dev server-renders through the Vite plugin, using whichever node is on your PATH. Fix that one with nvm or fnm.

Who should treat this as urgent#

Your setupWhat to do
Inertia SSR, PM2, or any Node process that stays up in productionSame night as the PHP patch. New binary, rebuild with npm run build:ssr if you use SSR, restart the process, inertia:check-ssr.
That process decompresses untrusted bytes with node:zlib, or connects out to a host a request can influenceHighest urgency of this release. The Brotli bug is the remote-code-execution case. The TLS and DNS bugs are the outbound case.
Node runs only at build time (Vite in CI, Forge, Coolify, Sail) and production is PHP plus static assetsUpdate before the next deploy. A visitor is not hitting that Node. The next npm ci is.
node -v is already 22.23.4, 24.22.1, or 26.12.1, and SSR was restarted after the upgradeDone.
Node 20 or any line the schedule marks EOLNo patched build. Move to 22.23.4 or 24.22.1, then restart.
You ship a Node SEA or a --build-snapshot binary that called crypto.randomUUID() while it was builtUpdate Node and rebuild the snapshot. Until you do, processes from that snapshot can repeat UUIDs and randomInt() values.

A weekend Vite site with no SSR is not the same incident as the PHP RCEs. It is still this week’s upgrade. The SSR process on a box you already patched for PHP is the one to finish tonight.

One checklist for PHP and Node#

  • PHP. php -v on the server shows 8.5.12, 8.4.27, 8.3.36, or 8.2.35, or your distro’s patched package. FPM, Octane, Horizon, and queue workers were restarted. Steps: PHP critical security release.
  • Every Node. node -v on the laptop, in CI, in Sail, in the Docker image, and on the Forge or Coolify host shows v22.23.4, v24.22.1, or v26.12.1.
  • Same major, unless the old major is EOL.
  • Build. npm ci and npm run build (or npm run build:ssr) ran on that binary.
  • Restart. Supervisor, PM2, and inertia:start-ssr came back after the new binary was on PATH. php artisan inertia:check-ssr answers if you use SSR.
  • Audits. Run composer audit and npm audit for packages. They are the lockfile check. node -v is the binary check. You want both.

Recap#

  • October 21, 2026: Node.js 22.23.4, 24.22.1, and 26.12.1. Highest severity High on each line.
  • Four High CVEs: DNS NUL truncation, fullwidth-digit TLS (24 and 26), leading-zero IP TLS, and a Brotli out-of-bounds write that can lead to remote code execution.
  • npm audit and composer audit will not report them. Install the binary, in every environment that runs Node.
  • Rebuild assets, restart Inertia SSR, and pair it with the PHP update from the same day.

FAQ#

Will npm audit or composer audit report these CVEs?

No. Both commands read lockfiles. These bugs are in the Node binary. Check node -v and install 22.23.4, 24.22.1, or 26.12.1.

We only use Node to compile Vite assets. Is production exposed?

Production PHP is a separate question, covered by the PHP release. The Node risk in a build-only app is the CI and deploy build: registry TLS, and anything the build script resolves or decompresses. Update that binary before the next deploy. If Inertia SSR is running, treat it as production Node and restart it.

We are on Node 22. Does the fullwidth-digit TLS bug affect us?

That one (CVE-2026-105483) affects 24 and 26. Node 22 is still affected by the DNS NUL bug, the leading-zero TLS bug, and the Brotli bug. Install 22.23.4.

Forge still has Node 20. Is there a 20.x patch in this release?

No. The release ships 22.23.4, 24.22.1, and 26.12.1. Node 20 is end of life on the Node.js release schedule. Move the server to 22.23.4 or 24.22.1 and redeploy.

I updated Node but the SSR process still looks old. What did I miss?

A long-running process keeps the binary it started with. Restart Supervisor or PM2, and check for an absolute path to an old nvm install. Then run php artisan inertia:check-ssr. npm run build does not restart SSR, and it does not refresh bootstrap/ssr.

Next steps#

Sources#

Found this useful?

Ask questions, share what you built.

Join thousands of Laravel and Vue developers in our Facebook group, or get new posts in your Telegram feed. Sharing this article helps too.

Keep reading

Newsletter

Get the best of Laravel & Vue, once a month.

One short email with new guides, release notes worth reading and hand-picked community links. No spam, unsubscribe any time.