Next.js security update October 2026: what 16.4.1 and 15.5.28 fix

Summary

The October 14, 2026 Next.js security release patches two critical RCEs and one high-severity DoS in 16.4.1 and 15.5.28, with exposure depending on Cache Components and use cache, Image Optimization handling SVG on glibc Linux, and App Router Server Actions. Upgrade next, but inspect the deployed lockfile for every next and sharp copy because 15.x can still resolve sharp to 0.34.x after the bump, and 13.x/14.x have no patched release. Treat clean scanners cautiously since two advisories lack CVE IDs and npm audit may lag, and don't rely on react-server-dom-* packages in a Next.js tree because Next bundles its own renderer. After upgrading, force sharp to 0.35.5 or later where needed, rebuild, and redeploy the production image.

The October 14, 2026 Next.js security update fixes two critical remote code execution bugs and one high-severity denial of service, and the fixed releases are 16.4.1 and 15.5.28. Upgrading next is the main fix, but it isn't the whole check. One of the RCEs lives in sharp, which your lockfile may keep at an old version after the upgrade, and two of the three advisories have no CVE ID, so a scanner can report clean while you're still exposed.

This guide separates the three advisories by the app features they need, then shows what to look at in a lockfile to know whether you're actually patched.

The three advisories and what they need

The Next.js announcement calls this an out-of-band release. It carries two fixes postponed from the September security release because of upstream dependency delays, plus a third issue.

Advisory Severity Affected next versions Needs
GHSA-673q-mjm6-r5xr (no CVE) Critical, RCE 16.0.0 to 16.4.0 App Router with Cache Components
CVE-2026-96889 / GHSA-2464-fhr4-w9xw Critical, RCE 10.0.0 to 15.5.27, 16.0.0 to 16.4.0 Image Optimization API handling SVG, on glibc Linux
GHSA-p3m5-59mp-jv5r (no CVE) High, DoS 13.0.0 to 15.5.27, 16.0.0 to 16.4.0 App Router with at least one Server Action

The use cache RCE is rated CVSS 9.5. It's triggered when a use cache function returns multiple typed arrays, so it only matters on 16.x apps that turned on cacheComponents. The advisory says there's no workaround for apps that have such functions, and Vercel's own advice is to upgrade even if you don't think you have one. The root cause is in React Server Components: React published a matching advisory, GHSA-xqc2-xf4c-x3h3, for react-server-dom-webpack, -turbopack and -parcel, fixed in 19.0.9, 19.1.10, 19.2.9 and 19.3.1.

The Image Optimization RCE is the broadest of the three by version range: everything from 10.0.0 up. The advisory describes it as a memory bug in an upstream library used for image optimization, exploitable when Next.js runs on glibc-based Linux. It doesn't name the library, but the workaround it gives is to make sure the sharp that Next.js uses is 0.35.5 or later. Debian- and Ubuntu-based images use glibc. Alpine is built around musl, which puts it outside the stated scope, but that isn't a reason to skip the upgrade.

The Server Actions denial of service is rated 8.2. A crafted request causes CPU use that blocks every other request in the same process. Pages Router apps and App Router apps with no Server Actions aren't affected, and there's no workaround besides upgrading.

Next.js 14 and older have no patched release

The fixes exist only on the two LTS lines. If you're on 13.x or 14.x, you're inside the ranges for the image RCE and the DoS, and no 13.x or 14.x release fixes them. Your options are to move to 15.5.28 or 16.4.1, or for the image bug to apply the sharp workaround and accept the DoS risk until you can upgrade.

Upgrading next doesn't always upgrade sharp

sharp is an optional dependency of next, and the range it allows is what decides whether the image fix reaches you. Reading the published package metadata from the npm registry:

On 16.x the new range excludes the vulnerable 0.35.4, so the package manager has to move sharp forward. On 15.x it doesn't: the 0.34 branch is still allowed. A project whose lockfile resolved sharp to some 0.34.x release still satisfies ^0.34.3 || ^0.35.5 after the upgrade, so nothing forces it up, yet 0.34.x is below the 0.35.5 the advisory names as the fixed version. The sharp releases page shows 0.35.5 on September 27 and no newer 0.34.x release, so there's no patched release on that branch to land on.

If you're on 15.x, make sharp 0.35.5 or later explicitly. One way is an npm override in the root package.json, which applies to the whole dependency tree. We didn't run npm for this article (the session couldn't install packages), so this snippet follows the npm docs rather than a test run:

{
  "overrides": {
    "sharp": "^0.35.5"
  }
}

Check the lockfile, not package.json

Your package.json says what you asked for. The lockfile says what you got, including nested copies. This script reads an npm v3 package-lock.json, reports every next and sharp it finds against the fixed versions, and looks for the features that decide which advisories apply. It uses only Node built-ins.

// check-next-oct-2026.mjs: report exposure to the October 14, 2026 Next.js advisories
// Usage: node check-next-oct-2026.mjs [project-dir]
import { readFileSync, readdirSync, existsSync } from 'node:fs';
import { join } from 'node:path';

const dir = process.argv[2] ?? '.';
const lock = JSON.parse(readFileSync(join(dir, 'package-lock.json'), 'utf8'));

const parse = (v) => v.split('-')[0].split('.').map(Number);
const lt = (a, b) => {
  const [x, y] = [parse(a), parse(b)];
  for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i];
  return false;
};

// Every copy of a package in the lockfile, including nested ones.
const copies = (name) =>
  Object.entries(lock.packages ?? {})
    .filter(([path]) => path === `node_modules/${name}` || path.endsWith(`/node_modules/${name}`))
    .map(([path, meta]) => ({ path, version: meta.version }));

function nextStatus(v) {
  const [major] = parse(v);
  if (major === 16) return lt(v, '16.4.1') ? 'VULNERABLE: upgrade to 16.4.1' : 'patched';
  if (major === 15) return lt(v, '15.5.28') ? 'VULNERABLE: upgrade to 15.5.28' : 'patched';
  if (major >= 10) return 'NO PATCHED RELEASE on this line: move to 15.5.28 or 16.4.1';
  return 'outside advisory ranges';
}

for (const { path, version } of copies('next')) console.log(`next   ${version.padEnd(8)} ${path}: ${nextStatus(version)}`);
const sharp = copies('sharp');
if (sharp.length === 0) console.log('sharp  (not in lockfile)');
for (const { path, version } of sharp) {
  const s = lt(version, '0.35.5') ? 'VULNERABLE: needs >= 0.35.5' : 'ok';
  console.log(`sharp  ${version.padEnd(8)} ${path}: ${s}`);
}

// Which advisories apply to this code base? Look for the directives and the config flag.
function* files(d) {
  if (!existsSync(d)) return;
  for (const e of readdirSync(d, { withFileTypes: true })) {
    if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
    const p = join(d, e.name);
    if (e.isDirectory()) yield* files(p);
    else if (/\.(js|jsx|ts|tsx|mjs)$/.test(e.name)) yield p;
  }
}
const hits = { 'use cache': [], 'use server': [] };
for (const root of ['app', 'src']) {
  for (const f of files(join(dir, root))) {
    const src = readFileSync(f, 'utf8');
    for (const d of Object.keys(hits)) if (src.includes(`'${d}`) || src.includes(`"${d}`)) hits[d].push(f);
  }
}
const config = ['next.config.ts', 'next.config.js', 'next.config.mjs'].map((f) => join(dir, f)).find(existsSync);
const cacheComponents = config ? /cacheComponents\s*:\s*true/.test(readFileSync(config, 'utf8')) : false;
console.log(`cacheComponents: ${cacheComponents}; 'use cache' in ${hits['use cache'].length} file(s); 'use server' in ${hits['use server'].length} file(s)`);

const header = process.report?.getReport().header;
const libc = process.platform !== 'linux' ? `${process.platform} (advisory scope is glibc Linux)` : header.glibcVersionRuntime ? `glibc ${header.glibcVersionRuntime}` : 'musl or other non-glibc libc';
console.log(`this host: ${libc}`);

The libc line reads glibcVersionRuntime from the diagnostic report header. The Node docs show that field on Linux but don't promise it, so treat a "musl" answer as a hint and confirm by checking your base image. Run the script inside the production container, not on your laptop.

We ran it on Node 23.9.0 against two sample projects. The first is a 16.x app with Cache Components on, one use cache module and one Server Action, still on 16.4.0:

next   16.4.0   node_modules/next: VULNERABLE: upgrade to 16.4.1
sharp  0.35.4   node_modules/sharp: VULNERABLE: needs >= 0.35.5
cacheComponents: true; 'use cache' in 1 file(s); 'use server' in 1 file(s)
this host: darwin (advisory scope is glibc Linux)

That app is exposed to all three advisories. The second is a 15.x app where next was bumped to 15.5.28 but the lockfile kept sharp at 0.34.5, which the new range still allows:

next   15.5.28  node_modules/next: patched
sharp  0.34.5   node_modules/sharp: VULNERABLE: needs >= 0.35.5
cacheComponents: false; 'use cache' in 0 file(s); 'use server' in 0 file(s)
this host: darwin (advisory scope is glibc Linux)

The next line says patched, and a quick look at package.json would agree. The sharp line is the one that matters on 15.x. The directive scan is a plain string search, so a comment that mentions 'use server' counts as a hit. It's there to tell you which advisories to think about, not to rule any out.

Don't look for the React package in a Next.js app

The React advisory lists version ranges for react-server-dom-webpack and its siblings, and it's tempting to check those in your tree. In a Next.js app you won't find them. Next.js ships its own copy: in the published 16.4.1 package, dist/compiled/react-server-dom-webpack/package.json is named react-server-dom-webpack-builtin and has no version field. The react and react-dom versions in your package.json don't tell you whether the bundled server renderer is fixed. For Next.js apps, the next version is what counts. The React ranges are for other setups that install those packages directly.

Why a clean scan doesn't settle it

Two of the three advisories have no CVE ID. If your vulnerability tracking is keyed on CVEs, whether that's a scanner fed from NVD or a spreadsheet, the use cache RCE and the Server Actions DoS won't show up in it at all.

Timing is the other gap. npm audit gets its data from the GitHub Advisory Database, which GitHub announced in 2021. The use cache advisory was published on the Next.js repository on October 14, but its global database entry shows it was added on October 22. A team that ran npm audit during that week, saw nothing, and moved on should look again. Going forward, compare resolved versions against the vendor's advisory list rather than trusting one tool's silence.

If you have Next.js on a glibc Linux host, upgrade to 16.4.1 or 15.5.28, confirm sharp resolved to 0.35.5 or later in the lockfile you deploy, then rebuild and redeploy the image.

SECURITY ADVISORY NEXT.JS REACT SERVER COMPONENTS SHARP USE CACHE SERVER ACTIONS NPM AUDIT

  RELATED

  COMMENTS

0

No comment for this article.