Skip to main content

Cloudsoft Zone Build, deploy and scale with practical guides to PHP, Node.js, React, DevOps and AI automation. Explore the guides

Cloudsoft Zone
DevelopmentJavaScript

RSC security checklist: audit and patch your Next.js app

React Server Components have had RCE, DoS and proxy-bypass fixes since December 2025. Find the versions you run, patch to 16.3.8, and lock down Server Actions and secrets.

Cloudsoft Zone

Updated 12 min read

Table-style timeline of React Server Components and Next.js advisories from December 2025 to September 2026 with the first fixed Next.js 16 and 15.5 releases.
Image: Cloudsoft Zone
In this article

React Server Components moved part of your back end into the same files as your UI, and the last ten months showed what that costs when the plumbing has a bug: a CVSS 10 remote code execution in December 2025, then a steady run of denial-of-service, proxy-bypass and server-side request forgery fixes. This checklist shows how to find the exact versions you run, which releases are safe on 30 September 2026, and the code-level checks that keep a Next.js app safe when the next advisory lands.

Tested with: Next.js 16.3.6 (App Router, Turbopack), React 19.3.0, TypeScript 7.0.2, zod 4.6.5, Node.js 24.19.0. Advisory data checked against the GitHub Security Advisories for vercel/next.js and facebook/react and the CVE records on 30 September 2026.

The advisories that matter, and the safe floor

The table lists the issues most likely to affect a typical App Router app. The fixed version is the first release on each line that contains the fix; anything newer on the same line is also fixed.

PublishedAdvisoryImpactFixed in
3 Dec 2025CVE-2025-55182 (GHSA-fv66-9v8q-g76r), "React2Shell"Unauthenticated RCE through Server Function payload decoding, CVSS 10.0React 19.0.1, 19.1.2, 19.2.1; Next.js 15.0.5 to 15.5.7, 16.0.7
11 Dec 2025CVE-2025-55184, CVE-2025-67779 (DoS); CVE-2025-55183 (source exposure)Crafted requests hang the server; Server Action source code returnedNext.js 16.0.10, 15.5.9; React 19.2.3
26 Jan 2026CVE-2026-23864RSC denial of serviceNext.js 16.1.5, 15.5.10; React 19.2.4
16 Mar 2026CVE-2026-27978Origin: null bypassed the Server Action CSRF checkNext.js 16.1.7
6 to 7 May 2026CVE-2026-44575, CVE-2026-45109, CVE-2026-44574Middleware/proxy bypass via segment-prefetch URLs and route parametersNext.js 16.2.6, 15.5.18
21 Jul 2026CVE-2026-64643, CVE-2026-64642, CVE-2026-64641, CVE-2026-44907Server Function endpoint disclosure, proxy bypass with one i18n locale, DoSNext.js 16.2.11, 15.5.21; React 19.2.8
25 Aug 2026GHSA-2xp9-vwfh-vxw4, CVE-2026-75604RCE via AVIF image optimisation; RCE on Windows-hosted serversNext.js 16.3.3, 15.5.24
22 Sep 2026CVE-2026-94545 (GHSA-vcvr-r3jv-pc5j)RCE in next/og ImageResponse (Node.js runtime) with user input in SVGNext.js 16.3.6
30 Sep 2026CVE-2026-94483, -94543, -94484, -94544, -103004 and othersImage Optimizer SSRF, ISR cache poisoning, draft-mode and use cache leaksNext.js 16.3.8, 15.5.27

So today the floor is Next.js 16.3.8, or 15.5.27 if you cannot leave 15 yet. Next.js 14 has had no release since 14.2.35 in December 2025, and the 2026 advisories list fixes only for 15.5 and 16, so a 14.x app should be treated as unpatched. The Next.js-side ID for React2Shell, CVE-2025-66478, was rejected as a duplicate of CVE-2025-55182; scanners may still show either.

The React logo: a small cyan dot at the centre of three cyan elliptical orbits that cross each other, like a stylised atom.
The RSC flaws sat in React's server packages, but Next.js bundles its own copy: patch next, not just react.

1. Find out what you actually run

The version in package.json is a range; the lockfile and the deployed build are what count. Check every app, including internal admin tools:

Shell
# What the lockfile actually resolved (run in every app of a monorepo)
npm ls next react react-dom
npx next info

# Apps using RSC without Next.js (React Router, Waku, Vite RSC, Parcel)
npm ls react-server-dom-webpack react-server-dom-turbopack react-server-dom-parcel

# Known advisories for production dependencies
npm audit --omit=dev

Two traps. First, the App Router does not use your installed react: Next.js bundles its own React server build, so upgrading react alone does not fix an RSC flaw in a Next.js app. Upgrade next. Second, the running container may be older than the repository. Record the Next.js version in your build output or release notes so you can answer "which servers are vulnerable?" in minutes. The default X-Powered-By: Next.js header does not include a version, and you should switch it off anyway.

2. Patch, redeploy, and rotate if you were exposed

Shell
# Next.js 16.x
npm install next@16.3.8
# Still on 15.x and cannot move yet
npm install next@15.5.27
# Then rebuild and redeploy: the fix is in the server bundle, not the browser
npx next build

For the December 2025 issue only, Vercel published an interactive helper, npx fix-react2shell-next, which bumps each app to the patched version for its line. For everything since, upgrade normally.

Patching is not the end if you were exposed to an RCE. The Next.js advisory asked anyone online and unpatched as of 4 December 2025, 1:00 pm Pacific, to rotate every secret the app could read, starting with the most critical. Treat the August and September RCEs the same way if you ran an affected configuration: Image Optimization with AVIF input, a Windows host, or next/og on the Node.js runtime with request data in the SVG.

A practical rule for small teams: subscribe to the repository advisories (GitHub "Watch", then "Security alerts"), let Dependabot or Renovate open the pull request, and keep the deploy pipeline fast enough that a patch reaches production the same day. If your app has drifted several releases behind and nobody owns upgrades, a maintenance and support arrangement is cheaper than an incident.

3. Treat every Server Action as a public POST endpoint

A function marked 'use server' and used by your UI becomes an HTTP endpoint. The July 2026 advisory CVE-2026-64643 confirmed that action IDs can be found in public JavaScript chunks; the official workaround is the rule you should already follow: never assume authentication at the use server boundary; authenticate within it. A page that redirects signed-out users does not protect the actions it renders.

Request flow from browser through proxy.ts, page or Server Action and a server-only data access layer, showing that direct POSTs skip the page check.
The proxy can only make optimistic redirects; authorisation and ownership checks belong next to the data.

Keep actions thin and put authentication, ownership and the database call in a server-only data access layer:

TypeScript
// lib/session.ts
import 'server-only'
import { cache } from 'react'
import { cookies } from 'next/headers'

export type Viewer = { id: string; role: 'member' | 'admin' }

async function verifySessionToken(token: string): Promise<Viewer | null> {
  // Replace with your session store or JWT verification.
  return token ? { id: 'u1', role: 'member' } : null
}

// cache() dedupes the lookup within one request.
export const getViewer = cache(async (): Promise<Viewer | null> => {
  const token = (await cookies()).get('session')?.value
  return token ? verifySessionToken(token) : null
})
TypeScript
// lib/invoices.ts
import 'server-only'
import { experimental_taintUniqueValue } from 'react'
import { db } from './db'
import { getViewer } from './session'

export type InvoiceDTO = { id: string; total: number; status: 'draft' | 'sent' | 'paid' }

export async function getInvoiceForViewer(id: string): Promise<InvoiceDTO | null> {
  const viewer = await getViewer()
  if (!viewer) return null
  const row = await db.invoice.findUnique({ where: { id } })
  if (!row || (row.ownerId !== viewer.id && viewer.role !== 'admin')) return null

  // Belt and braces: React throws if this exact string reaches a Client Component.
  experimental_taintUniqueValue('Do not send Stripe customer IDs to the client', row, row.stripeCustomerId)

  // Return only what the UI needs: no internalNotes, no stripeCustomerId.
  return { id: row.id, total: row.total, status: row.status }
}

export async function markInvoiceSent(id: string): Promise<boolean> {
  const viewer = await getViewer()
  if (!viewer) return false
  const row = await db.invoice.findUnique({ where: { id } })
  if (!row || row.ownerId !== viewer.id || row.status !== 'draft') return false
  await db.invoice.update({ where: { id }, data: { status: 'sent' } })
  return true
}
TypeScript
// app/invoices/actions.ts
'use server'

import { z } from 'zod'
import { revalidatePath } from 'next/cache'
import { markInvoiceSent } from '@/lib/invoices'

const input = z.object({ invoiceId: z.uuid() })

export type SendResult = { ok: true } | { ok: false; error: string }

export async function sendInvoice(_prev: SendResult | null, formData: FormData): Promise<SendResult> {
  // 1. Validate: anyone can POST to this action with any body.
  const parsed = input.safeParse({ invoiceId: formData.get('invoiceId') })
  if (!parsed.success) return { ok: false, error: 'Invalid invoice' }

  // 2. Authenticate and authorise inside the data layer, every time.
  const done = await markInvoiceSent(parsed.data.invoiceId)
  if (!done) return { ok: false, error: 'Not allowed' }

  revalidatePath('/invoices')
  // 3. Return the minimum: never the database row.
  return { ok: true }
}

Checks for each action:

  • Validate every argument with a schema. Form fields, bound arguments and hidden inputs are all attacker-controlled.
  • Re-check the user and their right to this record inside the call (authorisation, not just "is logged in"). Missing ownership checks are the classic insecure direct object reference bug that OWASP has warned about for years.
  • Return the minimum. Return values are serialised to the browser; never return a database row.
  • Do not keep secrets in closures. Next.js encrypts closed-over variables of inline actions, but the docs say not to rely on that alone. If you self-host on several instances, set NEXT_SERVER_ACTIONS_ENCRYPTION_KEY so all instances share a key.
  • Rate-limit expensive actions such as sending email or calling paid APIs.

Then tighten the transport settings:

TypeScript
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  poweredByHeader: false, // do not advertise the framework
  productionBrowserSourceMaps: false, // the default; keep it that way
  experimental: {
    taint: true,
    serverActions: {
      bodySizeLimit: '256kb', // default is 1 MB; lower it if forms are small
      // Only origins you control. Never 'null'.
      allowedOrigins: ['app.example.com'],
    },
  },
}

export default nextConfig

allowedOrigins is for reverse proxies whose host differs from the public domain. Leave it out if you do not need it, and never add 'null': CVE-2026-27978 was exactly that origin slipping through the CSRF check.

4. Stop secrets reaching the client

RSC leaks are rarely clever. They are a whole user object passed as a prop, a NEXT_PUBLIC_ prefix on the wrong variable, or a Server Action that returns the row it just updated.

  • Mark server modules with import 'server-only'. Importing them from a Client Component then fails the build instead of shipping the code.
  • Keep process.env reads in the data layer. Anything prefixed NEXT_PUBLIC_ is inlined into browser JavaScript at build time; treat it as published.
  • Type Client Component props narrowly. A prop typed as User invites someone to pass the full record later.
  • Use the taint APIs as a backstop. With experimental.taint: true, experimental_taintObjectReference and experimental_taintUniqueValue make React throw when a marked object or value crosses into a Client Component. They track by reference: a copy, a spread or a derived string (`id:${secret}`) is not protected, and the flag switches the app directory to React's experimental channel. Use them for data you cannot remove, not instead of DTOs.
  • Keep production browser source maps off (the default) or upload them privately to your error tracker.

5. Do not trust proxy.ts (or middleware) for authorisation

Next.js 16 renamed middleware.ts to proxy.ts; the history is the same. Request-level checks have been bypassed repeatedly:

  • CVE-2025-29927 (March 2025): an x-middleware-subrequest header made Next.js skip middleware entirely (fixed in 15.2.3 and 14.2.25).
  • CVE-2026-44575 and CVE-2026-45109 (May 2026): .rsc and segment-prefetch URLs reached protected pages without matching the middleware rule.
  • CVE-2026-44574 (May 2026): dynamic route parameter injection; CVE-2026-44573: Pages Router apps using i18n.
  • CVE-2026-64642 (July 2026): App Router apps built with Turbopack and a single entry in i18n.locales.

Every one of these advisories gives the same workaround: enforce authorisation in the page's server-side data path. Use the proxy for cheap, optimistic redirects and keep the real check next to the data:

TypeScript
// proxy.ts
import { NextResponse, type NextRequest } from 'next/server'

// Optimistic check only: redirects signed-out users early.
// Real authorisation lives in lib/invoices.ts, next to the data.
export function proxy(request: NextRequest) {
  if (!request.cookies.has('session')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
  return NextResponse.next()
}

export const config = { matcher: ['/invoices/:path*'] }

With the data layer above, a request that slips past the proxy still gets a 404 from getInvoiceForViewer. In our test build, a request without the cookie got a 307 to /login, and a request with a cookie for a record the user did not own got a 404.

6. Audit the risky surfaces in an afternoon

The Next.js data security guide lists what an auditor should read first: the data layer, 'use client' files, 'use server' files, dynamic route folders, proxy.ts and route.ts. A quick inventory:

Shell
# Every Server Action file and inline action
grep -rln "^['\"]use server['\"]" app src lib --include=*.ts --include=*.tsx
grep -rn "'use server'" app src --include=*.tsx | grep -v "^.*:1:"

# Client Components, then read their props for over-broad types
grep -rln "^['\"]use client['\"]" app src --include=*.tsx

# Secrets that are deliberately public at build time
grep -rn "NEXT_PUBLIC_" --include=*.ts --include=*.tsx --include=.env* .

For each action, write down: who may call it, which record it touches, where ownership is checked and what it returns. For each route.ts handler, check the same as you would for any API. Then look at the features named in recent advisories: if you use next/og with request input, the Image Optimizer with remote patterns, ISR on a self-hosted server, draft mode or use cache, confirm you are on 16.3.8. For headers and nonces, follow our strict CSP guide for Next.js 16 and make sure you are past 16.2.5, which fixed an XSS affecting apps that use CSP nonces (CVE-2026-44581).

Common mistakes

  • Upgrading react and react-dom but not next, so the bundled server React stays vulnerable.
  • Patching the repository and forgetting the long-running preview deployments and internal tools built from old branches.
  • Checking the session in the page and assuming the actions on that page are protected.
  • Returning ORM objects from actions "because TypeScript is happy".
  • Relying on a WAF rule instead of patching. Both React and Next.js said hosting-provider mitigations were not a substitute for upgrading.
  • Skipping secret rotation after running an RCE-affected version on the internet.

When this checklist is not enough

If your app handles payments, health or other regulated data, a checklist is a starting point, not an assessment: commission a penetration test after the fixes. If your team cannot patch within days, consider whether you need Server Actions at all; a separate, conventionally secured API (and Server Components that only read from it) narrows what an RSC flaw can reach. And a Pages Router app without React Server Components was not affected by the RSC-protocol issues, but it still needs the image, rewrite and middleware fixes in the table.

Cloudsoft Zone keeps Next.js apps patched and audited for small teams: version inventory across every deployment, same-week security upgrades, and a written review of Server Actions, proxy rules and data exposure. See our application maintenance and support service.

Frequently asked questions

  • Which Next.js version is safe on 30 September 2026?

    Next.js 16.3.8 on the 16 line, or 15.5.27 on the 15 line. Both include the fixes published on 30 September 2026. Next.js 14 has had no patch since 14.2.35 and should be upgraded.

  • Is my app affected if it has no Server Actions?

    For CVE-2025-55182, yes, potentially. The React team said apps that support React Server Components may be vulnerable even without their own Server Function endpoints. Pages Router apps and apps without an RSC framework or bundler were not affected by that issue.

  • Does upgrading react and react-dom fix a Next.js app?

    No. The App Router uses a React build bundled inside the next package. Upgrade next to a patched release and redeploy.

  • Are the taint APIs enough to stop secrets leaking?

    No. They track objects and values by reference, so copies and derived strings escape them, and they are experimental. Use them as a backstop behind a data access layer that returns only the fields the UI needs.

  • Can I rely on proxy.ts to protect admin pages?

    Only for redirects. Several advisories since 2025 bypassed middleware or proxy checks. Check the session and the user's rights in the data layer that every page, action and route handler calls.

Advertise on CloudSoftZone

Your next customer could start right here.

Introduce your tool, product or business to readers exploring technology.

Advertise with us

Explore placements and request a private quote

Sources

  1. Critical Security Vulnerability in React Server Components — React, 2025-12-03
  2. Security Advisory: CVE-2025-66478 — Vercel (Next.js blog), 2025-12-03
  3. vercel/next.js security advisories — GitHub
  4. facebook/react security advisories — GitHub
  5. GHSA-f82v-jwr5-mffw: Authorization Bypass in Next.js Middleware (CVE-2025-29927) — GitHub, 2025-03-21
  6. How to think about data security in Next.js — Next.js documentation
  7. taint — Next.js documentation
  8. CVE-2025-55182 — CVE Program, 2025-12-03

Written with substantial AI assistance. Facts checked against cited sources. Editorial policy.

Comments

No comments yet. Be the first to join the conversation.

Leave a comment

Every comment is read by a moderator before it appears. Your email address is never published; it is used only to contact you about this comment.

Never published. Used only to contact you about this comment.

Loading the security check…

Comments are moderated before publication.