On May 7, Vercel cut a coordinated security release for Next.js — thirteen advisories patched in a single afternoon, fixed in 15.5.18 and 16.2.6. The vulnerability classes read like an OWASP cheat sheet: denial of service, server-side request forgery, cache poisoning, cross-site scripting, auth bypass via routing. One of them — CVE-2026-23870 — is an upstream React Server Components bug, the fourth DoS in the RSC wire format since December.

The count is striking. The pattern is more interesting. Treat the RSC boundary the way you would treat a public JSON API, because that is what it has become.

What shipped

Vercel's own framing is unsentimental:

coordinated security release for Next.js addressing 13 advisories across denial of service, middleware and proxy bypass, server-side request forgery, cache poisoning, and cross-site scripting.

Sorted by class, the patch list looks like this:

  • Auth bypass via routing — segment-prefetch URLs (GHSA-267c-6grr-h53f, with a follow-up in GHSA-26hh-7cqf-hhc6), Pages Router i18n default-locale paths sneaking past proxy authorization (GHSA-36qx-fr4f-26g5), and dynamic-route parameter injection (GHSA-492v-c6pp-mqqv).
  • Denial of service — RSC deserialization (CVE-2026-23870), Cache Components connection exhaustion (GHSA-mg66-mrh9-m8jx), the Image Optimization API, and unbounded postponed-resume buffering in Partial Prerendering (CVE-2026-27979).
  • Cache poisoning — middleware redirect responses, RSC response bodies, and collisions in RSC cache-busting keys.
  • SSRF — applications using WebSocket upgrades.
  • XSS — App Router with CSP nonces, and beforeInteractive scripts that interpolate untrusted input.

If you run anything on Next.js 13 or 14, you are out of the support window: those branches do not get a patched release. Move to 15.5.18 or 16.2.6.

The Flight protocol keeps leaking

CVE-2026-23870 is the headline, and not because it is the worst CVE on the list. It is the fourth time in five months that a denial-of-service has been disclosed in the React Server Components wire format. The React team's December disclosure laid out the bug class plainly:

a malicious HTTP request can be crafted and sent to any Server Functions endpoint that, when deserialized by React, can cause an infinite loop that hangs the server process and consumes CPU.

Each round of patches has been followed by a new vector. Imperva's writeup of the May bug describes the same family: Flight repeatedly consumes maliciously crafted models before marking them as processed, so a small POST body can pin a CPU for seconds. CVSS 7.5, network-accessible, no auth required.

This is the cost of shipping a new RPC protocol. RSC is, mechanically, an RPC: the browser sends a serialized payload to a Server Function endpoint, the server deserializes and runs application code, and a Flight stream comes back. Anyone who has worked on a binary protocol parser knows the failure mode — and the React team is now living through the standard six-month "who else can crash our parser?" loop. Expect a fifth round.

Routing as an authorization surface

The auth-bypass advisories are the ones most likely to actually hurt someone in production. Two are worth highlighting.

The first is the segment-prefetch URL. Next's App Router exposes a separate request path for fetching individual segments — used by <Link prefetch> and the router cache. The advisory describes URL shapes that cause a reverse proxy or middleware authorization check to evaluate one path while Next ultimately serves another. The first fix was incomplete; GHSA-26hh-7cqf-hhc6 is the second pass at it.

The second is the i18n default-locale bypass on the Pages Router. If your proxy authorizes /admin but Next, with a default locale of en, happily resolves /en/admin through a different normalization path, the proxy never sees the path the framework matches.

Both bugs share a premise that frontend frameworks have been getting away with for a decade: the URL the framework matches is the same one upstream sees. It is not, and it has not been for a while — but every new routing feature (locales, parallel routes, segment-level prefetch, intercepting routes) widens the gap. If you authorize in a proxy in front of Next, the action item is concrete: assume the path you inspect is not the path Next will resolve. Authorize inside the app, in a layout or a middleware that runs after route resolution, or both.

RSC responses cache differently than you think

Two of the cache-poisoning advisories are about RSC bodies and RSC cache keys. The framing matters: an RSC response is a Flight payload, not HTML, and it lives at the same URL as the HTML page. The cache layer in front of your app sees two distinct response bodies for the same path — one for navigation, one for prefetch — and the cache key has to encode that distinction. GHSA-vfv6-92ff-j949 describes collisions in the cache-busting parameter Next uses to keep them apart. GHSA-wfc6-r584-vfw7 describes responses that, once cached, are served to the wrong audience.

If you operate a CDN in front of Next, the practical takeaway is to vary your edge cache on the RSC discriminator headers — RSC, Next-Router-State-Tree, Next-Router-Prefetch — and not just on the path. Next's defaults do this; custom edge configs frequently do not.

Partial Prerendering, unbounded buffers

CVE-2026-27979 is the kind of bug that only exists in a feature still being built. Partial Prerendering serializes a postponed state alongside the static shell, then resumes it on the dynamic request. To resume, the framework reads a next-resume: 1 header and buffers the request body before processing. The maxPostponedStateSize config exists precisely to bound that buffer.

In versions 16.0.1 through 16.1.6, the limit was enforced in minimal-mode deployments but not on the non-minimal path. A megabyte-and-growing payload, repeated, exhausts memory. The patch in 16.1.7 enforces the limit across all paths.

// next.config.js — if you have PPR on, this is the line that matters now.
module.exports = {
  experimental: {
    ppr: 'incremental',
    // any inbound `next-resume: 1` request is now bounded by this:
    maxPostponedStateSize: 256 * 1024, // bytes; a default exists, but pin it.
  },
};

Server Functions are a public API

The unifying lesson — and the reason the May release is more than the sum of its CVEs — is that a Server Function endpoint is a public API. It accepts a serialized payload from any client that can reach the URL. There is no fast path that skips deserialization, no privileged caller that bypasses the cache key, no version of the route that does not run through the framework's normalization. The code inside the function is 'use server'; the wire is not.

Cloudflare's changelog for the same disclosure is unusually frank about the limits of a WAF here:

Several of the disclosed vulnerabilities are not possible to block in WAF. Applications should not be purely reliant on WAF mitigations.

Which leaves the work where it belongs: in the framework, and in the threat model of the app authors using it.

What to do this week

  • Upgrade to Next.js 15.5.18 or 16.2.6, and to react-server-dom-* 19.0.6 / 19.1.7 / 19.2.6. If you are still on 13.x or 14.x, this is the upgrade.
  • If you are on PPR, pin maxPostponedStateSize in next.config.js and redeploy.
  • If you authorize in a proxy, audit how you treat locales, segment-prefetch URLs, and dynamic route params. Move at least one check inside the app.
  • If you run a CDN in front of Next, confirm your cache key varies on RSC, Next-Router-State-Tree, and Next-Router-Prefetch.
  • If you have any beforeInteractive scripts, read their inputs end-to-end. The XSS advisory assumed exactly the kind of trust we all extend to those tags.

The interesting question is not whether the next coordinated release comes — it does, on a schedule by now — but whether the Flight protocol gets a formal threat model attached to it. Until it does, every RSC app should ship with the same posture as a public JSON API: bounded inputs, varied caches, authorization checked after the framework, not before.