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.
Updated 12 min read

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

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:
# 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=devTwo 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
# 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 buildFor 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.

Keep actions thin and put authentication, ownership and the database call in a server-only data access layer:
// 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
})// 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
}// 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_KEYso all instances share a key. - Rate-limit expensive actions such as sending email or calling paid APIs.
Then tighten the transport settings:
// 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 nextConfigallowedOrigins 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.envreads in the data layer. Anything prefixedNEXT_PUBLIC_is inlined into browser JavaScript at build time; treat it as published. - Type Client Component props narrowly. A prop typed as
Userinvites someone to pass the full record later. - Use the taint APIs as a backstop. With
experimental.taint: true,experimental_taintObjectReferenceandexperimental_taintUniqueValuemake 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-subrequestheader made Next.js skip middleware entirely (fixed in 15.2.3 and 14.2.25). - CVE-2026-44575 and CVE-2026-45109 (May 2026):
.rscand 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:
// 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:
# 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
reactandreact-dombut notnext, 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
nextpackage. Upgradenextto 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.
Your next customer could start right here.
Introduce your tool, product or business to readers exploring technology.
Explore placements and request a private quote
Sources
- Critical Security Vulnerability in React Server Components — React, 2025-12-03
- Security Advisory: CVE-2025-66478 — Vercel (Next.js blog), 2025-12-03
- vercel/next.js security advisories — GitHub
- facebook/react security advisories — GitHub
- GHSA-f82v-jwr5-mffw: Authorization Bypass in Next.js Middleware (CVE-2025-29927) — GitHub, 2025-03-21
- How to think about data security in Next.js — Next.js documentation
- taint — Next.js documentation
- 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.
Related tools
- Directory profile
Figma
Browser-based design and prototyping tool with FigJam whiteboards, Dev Mode for developer handoff, Slides and AI prompt-to-code features.
A tool connected to a service selected for this article. Compare its fit for your project; inclusion is not an endorsement.
- Directory profile
Snyk
Developer security platform that scans open-source dependencies, code, containers and infrastructure as code for vulnerabilities.
A tool connected to a service selected for this article. Compare its fit for your project; inclusion is not an endorsement.
- Directory profile
Vercel
A hosting platform for web applications and frontend frameworks such as Next.js, with Git-based deployments, serverless functions and a global CDN.
A tool connected to a service selected for this article. Compare its fit for your project; inclusion is not an endorsement.
More from Cloudsoft Zone

Architecture guideAI and Automation
How to build a safe AI content pipeline with human fact review
An architecture for AI-assisted publishing where people stay responsible: untrusted source snapshots, claim checking with citations, a review queue AI cannot skip, labels and rollback.
By Cloudsoft Zone

TutorialNode.js
How to test a Node.js API with the built-in test runner (node:test)
Write, run, mock and measure coverage for a small Node.js HTTP API using only node:test and node:assert. Covers port-0 servers, fetch(), spies, t.mock.method(), fake dates and coverage thresholds.
By Cloudsoft Zone

TutorialNode.js
Node.js streams in practice: pipeline(), backpressure and async generators
Process files of any size in Node.js with flat memory use. A hands-on guide to stream/promises pipeline(), backpressure, async generator transforms, AbortSignal timeouts and streaming downloads over HTTP.
By Cloudsoft Zone
