Vercel has shipped the patches it pre-announced last week. The November 2026 security release is out as Next.js 16.4.5 (Active LTS) and 15.5.29 (Maintenance LTS), and it fixes seven vulnerabilities. One is rated High and six are Medium. The headline problem isn't remote code execution. It's data leaking between users, and the source is the caching layer many teams adopted with Cache Components. If you run a multi-tenant, multi-site or localized app on use cache, upgrade today.
The upgrade
The post's install commands:
1npm install next@16.4.5 # 16.x line
2npm install next@15.5.29 # 15.5 lineThe announcement has a small inconsistency to be aware of. Its terminal snippet labels the 16.4.5 command "for 16.5", but the prose calls 16.4.5 the Active LTS release, and npm currently lists 16.4.5 as latest. Read it as "upgrade the 16.x line to 16.4.5." After you bump the version, check your lockfile and confirm the version you actually deploy:
1npm ls nextThe post gives no fixed version for anything older than 15.5. If you're on an earlier 15.x minor or on 14, you'll have to move up to a patched line.
Most of the problems are in use cache
Four of the seven advisories involve Cache Components. They share one failure mode: a cache entry gets stored under an identity that's missing something that should have separated it from other entries. In practice that's a root parameter such as a tenant ID or locale, or the contents of a binary argument.
**Mixing unstable_cache with use cache (High, GHSA-mfr8-8qww-v76h).** This is the serious one. It applies when the same public use cache function reads a root parameter and gets called both directly and from inside unstable_cache. During a cold cache fill, a concurrent call that comes through unstable_cache can pick up another request's result. That result is then stored and served until revalidation. The advisory describes the consequence:
"Where root parameters separate tenants, the result can contain another tenant's data." (Next.js Blog)
**Nested use cache functions (Medium, GHSA-vc7v-qv4w-wrv8).** An inner cached function reads a root parameter and returns an early value along with a slower async value. If the outer function finishes before the slow value resolves, the outer entry can be saved before the inner function's root-parameter dependencies are recorded. Tenant-, site- or locale-specific content can then show up across those boundaries.
**Binary arguments (Medium, GHSA-rhh4-g77r-r9qj).** Different binary arguments passed to a use cache function can produce the same cache key. If those bytes are what tells users apart, one user can get another user's result, or push their own result to someone else.
Shared shell poisoning (Medium, no advisory link in the post). This affects self-hosted next start apps that have Cache Components on and Partial Prefetching off or unset. A crafted URL against a specific route shape can poison the fallback shell that other URLs share. The result is wrong content or persistent 404s. The trigger needs a narrow combination: an unprerendered dynamic param, two deeper never-prerendered params, a blocking fallback shell, and a layout that reads one of those params inside Suspense. The post says configurations with Partial Prefetching enabled weren't affected in the reported tests. That's a test result, not a guarantee, so upgrading is still the fix.
Patterns to clean up anyway
Upgrading fixes the bugs. Even so, two of these advisories point at patterns that deserved a review before any of this.
First, stop wrapping use cache functions in unstable_cache. Pick one caching model per function. If you've enabled Cache Components, that model is use cache.
1// Before: same cached function reached two ways
2import { unstable_cache } from 'next/cache';
3
4async function getTenantSettings(tenantId: string) {
5 'use cache';
6 return db.settings.findUnique({ where: { tenantId } });
7}
8
9export const getSettingsLegacy = unstable_cache(
10 async (tenantId: string) => getTenantSettings(tenantId),
11 ['tenant-settings'],
12);Advertisement
1// After: one entry point, one cache layer
2async function getTenantSettings(tenantId: string) {
3 'use cache';
4 return db.settings.findUnique({ where: { tenantId } });
5}
6
7export { getTenantSettings };Second, don't let raw bytes carry identity into a cache key. Pass an explicit string ID so the boundary between users shows up in the key and in code review:
1// Before: identity hidden inside a Uint8Array
2async function getStatement(sessionToken: Uint8Array, month: string) {
3 'use cache';
4 return loadStatement(sessionToken, month);
5}
6
7// After: resolve the user first, cache on a plain string key
8async function getStatement(accountId: string, month: string) {
9 'use cache';
10 return loadStatementForAccount(accountId, month);
11}Libraries that put locale or site in the root segment, such as i18n setups built around app/[locale], fit the "locale-specific content" scenario if they also use nested cached functions. The advisories don't name third-party packages, so check your own call graph rather than waiting for library maintainers to tell you.
The non-cache fixes
**Edge cookies exposed to JavaScript (GHSA-f3mh-rxg3-f5g2).** In Edge Route Handlers and Pages Router Edge API routes, NextResponse.cookies.set() can copy the serialized cookie into the internal x-middleware-set-cookie header. Same-origin JavaScript can read that header even when the cookie is HttpOnly. Exploiting it requires attacker-controlled script on your origin, so it turns an XSS into session theft rather than creating new access on its own. Node.js handlers and cookies().set() weren't affected in the reproduction. If you can't upgrade right away, this is a reasonable stopgap:
1// app/api/login/route.ts — before (Edge, affected)
2import { NextResponse } from 'next/server';
3export const runtime = 'edge';
4
5export async function POST() {
6 const res = NextResponse.json({ ok: true });
7 res.cookies.set('session', await createSession(), { httpOnly: true, secure: true });
8 return res;
9}1// after: Node runtime + cookies() from next/headers
2import { cookies } from 'next/headers';
3
4export async function POST() {
5 const cookieStore = await cookies();
6 cookieStore.set('session', await createSession(), { httpOnly: true, secure: true });
7 return Response.json({ ok: true });
8}**Stale compressed framing (GHSA-pc58-hwjq-p385).** A self-hosted app that returns an attacker-controlled fetched Response unchanged can send decompressed bytes with the original compression headers still attached. Behind an HTTP/1.1 proxy that pipelines requests over a shared backend connection, responses can then reach the wrong clients. Only that specific proxy behavior is affected. Passing upstream responses straight through was always fragile, and rebuilding the headers is cheap defense-in-depth:
1export async function GET(req: Request) {
2 const upstream = await fetch(resolveUpstream(req));
3 const headers = new Headers(upstream.headers);
4 headers.delete('content-encoding');
5 headers.delete('content-length');
6 return new Response(upstream.body, { status: upstream.status, headers });
7}**SVG bypass in Image Optimization (GHSA-7qx7-33qx-rqx2).** An image from an allowed remote source can be misclassified as AVIF and get around images.dangerouslyAllowSVG: false. It then reaches an SVG decoder if your native image backend has one. Your remotePatterns allowlist still applies, so the less you allow there, the less exposure you have. The post says it hasn't demonstrated XSS or RCE through this path, only greater exposure to SVG parser bugs and resource exhaustion.
What's still unclear
The shell-poisoning issue has no linked GHSA in the post, so you have no CVE identifier or affected-version range to put in your tracking. The post also doesn't say whether Vercel's hosted platform mitigated any of these at the infrastructure level. The self-hosted issues are explicitly scoped to next start, but the four cache advisories aren't scoped by host. Until the advisories say otherwise, assume they affect you wherever you deploy.






