React and Next.js applications are no longer just front ends: with Server Components, Server Actions, Route Handlers and Proxy (formerly Middleware), a single React codebase now runs privileged code on the server, holds secrets and exposes endpoints that anyone on the internet can call. Two critical flaws in 2025 made the point: a Next.js middleware authorisation bypass (CVE-2025-29927) and an unauthenticated remote code execution flaw in the React Server Components protocol (CVE-2025-55182, widely called React2Shell) that CISA added to its Known Exploited Vulnerabilities catalogue two days after disclosure. This guide covers the React and Next.js trust model, where authorisation must live, data leakage to the client, Server Action hardening, XSS and Content Security Policy, secrets, dependency and patch management, detection and a phased rollout. It is written for application security engineers, front-end and full-stack leads, and the platform teams that host Next.js. It assumes the App Router.
Key takeaways
- Every Server Action and Route Handler is a public endpoint. Next.js states that an exported Server Action is reachable by a direct POST request, so authenticate and authorise inside each one, not on the page that renders the form.
- Proxy and middleware are for optimistic checks only. CVE-2025-29927 showed what happens when middleware is the only gate; do the real authorisation in a server-only Data Access Layer close to the data.
- The server to client boundary leaks data by default. Props passed to Client Components, Server Action return values and
NEXT_PUBLIC_variables all reach the browser; return minimal DTOs and useserver-onlyand the React taint APIs as backstops. - Patch speed is the control that mattered most in 2025. React2Shell was exploited within hours, there was no workaround, and Next.js advised rotating every secret on applications left unpatched; track framework versions like any internet-facing server.
- React's escaping is strong but has documented escape hatches.
dangerouslySetInnerHTML, unsanitised Markdown and third-party scripts need a nonce or hash-based CSP, Trusted Types and Subresource Integrity behind them.
The React and Next.js trust model
The most common design error is treating a Next.js application as a browser application with a little server code. In the App Router, code runs in two isolated module systems with very different assumptions.
| Surface | Where it runs | Who can reach it | Security posture to assume |
|---|---|---|---|
| Server Components | Server only | Rendered per request or at build | Can hold secrets and query databases; anything passed to a Client Component as props is serialised to the browser |
Client Components ('use client') | Prerendered on the server, then in the browser | The user, and anyone who reads the bundle | Treat as browser code; Next.js says they must not access privileged data or server-only modules |
Server Actions / Server Functions ('use server') | Server | Any client sending a POST with a valid action ID | Public API endpoint; validate input, authenticate and authorise every call |
Route Handlers (route.ts) | Server | Anyone who knows the path | Public API endpoint; same rules as any REST API |
Proxy (proxy.ts, formerly middleware.ts) | Server, before routing | Every matched request, including prefetches | Redirects and optimistic checks from the cookie; never the only authorisation layer |
Dynamic segments ([param] folders), searchParams, headers | Server | Attacker controlled | Untrusted input; validate with a schema |
The React Server Components protocol itself is part of the attack surface. CVE-2025-55182 lay in how React decoded payloads sent to Server Function endpoints, and the React team noted that applications could be vulnerable even if they defined no Server Functions of their own, as long as they supported React Server Components. See the Insecure Deserialisation Guide.
Authentication and authorisation: where the checks must live
The Next.js authentication guide separates two kinds of check. Optimistic checks read the session from the cookie and are suitable for redirects and showing or hiding UI. Secure checks verify the session against the data source and gate sensitive data and actions. The guidance is that Proxy should only do optimistic checks, because it runs on every route including prefetches, and that most security checks should happen as close to the data as possible.
- Data Access Layer (DAL): a server-only module (marked with
import 'server-only') that verifies the session, checks permissions and returns Data Transfer Objects. Next.js recommends this for new projects and recommends that only the DAL readsprocess.env. - Re-check inside Server Actions: the Next.js data security guide is explicit that a page-level authentication check does not extend to Server Actions defined within it, because the action is a separate entry point.
- Check ownership, not just login: verify that the user may act on the specific record (for example, the post's author ID matches the session user) to prevent insecure direct object references. This maps to OWASP ASVS 5.0 V8.2.2 (data-specific access) and V8.3.1 (authorisation enforced at a trusted service layer).
- Do not gate in layouts: layouts do not re-render on client-side navigation, and Next.js warns that a layout hiding child segments does not stop them running or appearing in the RSC payload.
- Session cookies: set them on the server with
HttpOnly,Secure, an appropriateSameSitevalue,Max-AgeorExpires, andPath, as the Next.js guide recommends. Consider the__Host-prefix (ASVS V3.3.1 and V3.3.3). Keep the session payload to an ID and role, never personal data. See the Token and Session Security Guide.
Lesson from CVE-2025-29927
NVD describes CVE-2025-29927 as a flaw that allowed authorisation checks to be bypassed when they were implemented in middleware, rated CVSS 3.1 9.1 by GitHub as CNA and classed as CWE-863 (Incorrect Authorization). Vercel's post-mortem says the bypass used an internal, undocumented header that Next.js used to prevent recursion, and that it affected self-hosted applications using next start or output: 'standalone'; applications on Vercel, Netlify and Cloudflare Workers, and static exports, were not exposed. Fixes shipped in 15.2.3 and 14.2.25 with backports to 13.5.9 and 12.3.5. NVD records blocking external requests carrying the x-middleware-subrequest header as the workaround.
Keeping server data out of the browser
Most React data leaks are serialisation, not attacks: whatever a Server Component passes to a Client Component, or a Server Action returns, reaches the browser.
| Leak path | Example | Control |
|---|---|---|
| Whole database row passed as props | <Profile user={userData} /> where the row includes a password hash or internal flags | DTOs that return only the fields the UI renders; narrow prop types on Client Components |
| Server Action return values | Returning the updated ORM record | Return { success: true } or a minimal object |
| Public environment variables | An API key placed in NEXT_PUBLIC_... | Never prefix secrets; Next.js inlines these into the JavaScript bundle at next build |
| Server-only module imported into client code | A database helper imported by a 'use client' file | import 'server-only' makes the build fail |
| Sensitive value reaching render context | A session token stored on a user object | experimental_taintUniqueValue and experimental_taintObjectReference with experimental.taint: true |
| Closed-over variables in inline Server Actions | An action defined inside a component capturing a secret | Next.js encrypts closures, but its documentation says not to rely on encryption alone; keep secrets out of closures |
The React taint APIs are explicitly a second layer: the Next.js documentation says you should still filter data in the DAL.
Hardening Server Actions and Route Handlers
Next.js adds several protections around Server Actions; know what each does and does not give you.
- Encrypted, non-deterministic action IDs: created at build time, cached for at most 14 days and regenerated on a new build. They reduce exposure when an auth check is missing, but Next.js says to treat actions as reachable by direct POST regardless.
- Dead code elimination: unused actions are removed during
next buildand get no public endpoint. Exported but referenced actions remain callable. - POST only plus Origin check: actions can only be invoked with POST, and Next.js compares the
Originheader withHost(orX-Forwarded-Host) and aborts on mismatch. Behind reverse proxies, list legitimate origins inserverActions.allowedOriginsrather than disabling the check. See the CSRF Prevention Guide. - Encryption key for multi-instance hosting: self-hosted fleets can set
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY(a base64 AES key of 16, 24 or 32 bytes) so all instances agree. Treat it as a production secret: store it in a secrets manager, restrict who can read it and rotate it. - Validate every argument: parse
FormData, params and search parameters with a schema library on the server (ASVS V2.2.2). Client-side validation is usability, not security.
Route Handlers have none of the Server Action extras. Apply ordinary API controls: authentication, object-level authorisation, input validation, rate limiting and outbound URL allowlists where a handler fetches user-supplied URLs. See the SSRF Prevention Guide and the Web Application Attacks Guide.
XSS, Content Security Policy and third-party scripts
React escapes values rendered in JSX text, which removes most classic reflected and stored XSS. The residual risk sits in a short, well-known list of escape hatches.
dangerouslySetInnerHTML: React's documentation warns that unless the markup comes from a completely trusted source, it is trivial to introduce an XSS vulnerability this way. Sanitise with a maintained HTML sanitiser, build the{__html}object close to where the HTML is generated (as React advises) and, where Trusted Types is enforced, pass aTrustedHTMLvalue created by your policy.- User-controlled URLs: validate schemes on
hrefandsrcvalues built from user data (allowhttps:and known relative paths), and never pass untrusted strings into script-capable sinks. - Markdown and rich text: renderers that allow raw HTML reintroduce the problem; ASVS V1.3.5 asks that user-supplied scriptable content such as Markdown be sanitised or disabled.
Content Security Policy is the backstop. The Next.js CSP guide offers two strict options.
| Approach | How it works in Next.js | Trade-offs | Choose when |
|---|---|---|---|
| Nonce-based CSP | Proxy generates a fresh nonce per request, sets Content-Security-Policy and x-nonce; Next.js applies the nonce to framework scripts, bundles and <Script nonce> | Requires dynamic rendering; disables static optimisation and ISR; incompatible with Partial Prerendering; no CDN caching by default | Authenticated apps handling sensitive data where 'unsafe-inline' is not acceptable |
| Hash-based CSP with SRI (experimental) | experimental.sri with sha256, sha384 or sha512 adds integrity attributes at build time | App Router only, experimental, cannot cover dynamically generated scripts | Largely static sites that need caching but want a strict script policy |
The baseline directives in the Next.js examples are a good start: default-src 'self', script-src 'self' 'nonce-...' 'strict-dynamic', object-src 'none', base-uri 'self', form-action 'self', frame-ancestors 'none' and upgrade-insecure-requests. Next.js notes that 'unsafe-eval' is needed only in development, because React uses eval for debugging there and neither React nor Next.js uses it in production by default, so a production policy containing it is a finding. Add a reporting endpoint (ASVS V3.4.7) and roll out with Content-Security-Policy-Report-Only first. See the Security Headers Guide and the Cross-Site Scripting (XSS) Prevention Guide.
Third-party scripts loaded from CDNs deserve the same scrutiny as dependencies. ASVS V3.6.1 asks that externally hosted assets use Subresource Integrity. The Lottie Player npm compromise in October 2024 is the illustration: malicious versions published with a developer's stolen access token were served automatically to sites that loaded unpinned versions from public CDNs, and showed visitors fake wallet connection prompts.
Secrets, environment variables and the identity behind the app
A Next.js server holds credentials: database connection strings, OAuth client secrets, session signing keys, the Server Actions encryption key and API keys for payment, email and AI services. These are non-human identities and they outlive any single deployment.
- Know what is public: Next.js inlines
NEXT_PUBLIC_values into the client bundle at build time and freezes them; non-prefixed variables stay on the server. Scan built bundles for secret patterns in CI. - Load from a secrets manager at runtime rather than baking values into images; Next.js reads non-public variables at request time under dynamic rendering, which allows one image across environments (ASVS V13.3.1).
- Plan for rotation before you need it: after React2Shell, Next.js advised teams whose applications were online and unpatched as of 4 December 2025 at 1:00 PM PT to rotate all secrets, starting with the most critical. Teams without an inventory of what each app holds could not do that quickly.
Dependencies, versions and patching
React2Shell is the case study for framework patching. The React advisory listed vulnerable versions 19.0, 19.1.0, 19.1.1 and 19.2.0 of react-server-dom-webpack, react-server-dom-parcel and react-server-dom-turbopack, fixed in 19.0.1, 19.1.2 and 19.2.1, and named downstream frameworks and bundlers including Next.js, React Router, Waku, @parcel/rsc, @vitejs/plugin-rsc and rwsdk. The Next.js advisory (CVE-2025-66478) covered App Router applications on 15.x, 16.x and 14.3.0-canary.77 onwards, with fixes in 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7 and 16.0.7, and stated there was no workaround. Follow-up React advisories added two denial of service issues (CVE-2025-55184 and CVE-2025-67779), a source code exposure issue (CVE-2025-55183) and, in January 2026, a further denial of service issue (CVE-2026-23864). AWS reported exploitation attempts by China-nexus groups it tracks as Earth Lamia and Jackpot Panda within hours of disclosure, and CISA's KEV catalogue records CVE-2025-55182 as known to be used in ransomware campaigns.
- Inventory the framework, not just the app: record the exact
next,reactandreact-server-dom-*versions per service, including transitive copies, in an SBOM. See the SBOM Guide. - Set framework patch SLAs: ASVS V15.1.1 and V15.2.1 ask for documented remediation time frames and for components not to breach them; a CVSS 10 unauthenticated RCE on an internet-facing server warrants an emergency path measured in hours.
- Stay on supported release lines: Vercel's post-mortem notes that triage was slowed partly because early reports concerned older versions, and that Vercel subsequently published an LTS policy.
- Watch the npm supply chain: front-end projects pull hundreds of packages; see the Malicious Package and Dependency Confusion Guide and the Software Supply Chain Security Guide.
Detection and measurement
- Request logs: log Server Action POSTs (the action ID header, origin, user and outcome) and Route Handler calls. AWS recommended reviewing logs for suspicious POST requests carrying Next.js or React server action headers during the React2Shell response.
- Process and file telemetry: a Node.js server process spawning shells or unexpected child processes, or writing to temporary directories, is a strong indicator of server-side code execution (ATT&CK T1190 Exploit Public-Facing Application). See the Endpoint Security Guide.
- CSP violation reports: a sudden rise in
script-srcviolations can indicate injected or tampered third-party script (T1059.007 JavaScript).
Metrics that show the programme is working:
- Median time from a critical React or Next.js advisory to production patch across all services.
- Percentage of Server Actions and Route Handlers whose code calls the DAL authorisation function (measured by static analysis).
- Percentage of internet-facing apps with an enforced (not report-only) CSP without
'unsafe-inline'or'unsafe-eval'inscript-src. - Count of
dangerouslySetInnerHTMLuses without an approved sanitiser, trending to zero. - Number of secret findings in built client bundles and source maps per release.
- Percentage of services on a supported Next.js release line.
Rolling out the controls
- Inventory (week 1 to 2): list every React and Next.js application, its hosting model (managed platform, self-hosted
next start, standalone container, static export), framework versions and the secrets it holds. - Patch and pin: move every service to a supported, patched release line and add automated dependency update pull requests with tests. Roll back by redeploying the previous image, but never back to a vulnerable framework version.
- Introduce a DAL: start with the highest-value data. Add
import 'server-only', move authorisation checks into it and refactor Server Actions to delegate to it. Add a lint or static analysis rule that flags'use server'files and Route Handlers that do not call it. - Minimise client data: replace raw records with DTOs, enable
experimental.taintand taint session tokens and sensitive objects. Test by inspecting the RSC payload and HTML for fields that should not be there. - Deploy CSP in report-only mode: choose nonce or SRI per application, collect violations for two to four weeks, fix third-party script inventory, then enforce. Keep the previous header value ready as the rollback.
- Harden the edge: strip framework-internal headers at the reverse proxy, set
serverActions.allowedOriginscorrectly and add rate limits to expensive actions. - Test continuously: add authorisation tests that call Server Actions and Route Handlers directly with another user's session, and include Next.js specifics in penetration test scope. See the Web Application Security Testing Guide.
Small teams get the most from a managed hosting platform, an auth library rather than custom session code, automated dependency updates and the Next.js audit checklist used in code review. Large organisations should standardise a shared DAL and auth package across apps, enforce CSP and header policies at a central edge, and track framework versions centrally so an advisory yields a complete list of exposed services within an hour.
Common failure patterns
- Authorisation only in middleware or Proxy: a single bypass, such as CVE-2025-29927, opens every route. Keep secure checks in the DAL.
- Checking the page, not the action: an admin page redirects non-admins, but the Server Action it renders performs no check and can be called directly by POST.
- Authentication without ownership: the action confirms a user is signed in but deletes whichever record ID it is given.
- Secrets in
NEXT_PUBLIC_variables: a key added "temporarily" for a client-side call is now in every copy of the bundle and must be revoked, not just removed. - Slow framework patching: internet-facing Next.js services left on vulnerable React Server Components versions after December 2025, then not rotating their secrets after patching.
- Unpinned third-party scripts: loading
latestfrom a CDN without SRI, as in the Lottie Player incident, hands your users to whoever controls the package next.
Framework mapping
| Control area | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | OWASP ASVS 5.0 | MITRE ATT&CK |
|---|---|---|---|---|
| Authorisation in Server Actions and Route Handlers | AC-3 Access Enforcement | PR.PS-06 | V8.2.1, V8.2.2, V8.3.1 | T1190 |
| Input validation and safe deserialisation | SI-10 Information Input Validation | PR.PS-06 | V2.2.2, V1.5.2 | T1190 |
| XSS, CSP and third-party script integrity | SA-8, SR-3 | PR.PS-06 | V1.3.5, V3.4.3, V3.4.6, V3.4.7, V3.6.1 | T1059.007, T1195.002 |
| Session cookies | SC-23 Session Authenticity | PR.AA-03 | V3.3.1, V3.3.4 | T1539 |
| Framework patching and component inventory | SI-2 Flaw Remediation, SR-3 | ID.RA-01, PR.PS-02 | V15.1.1, V15.2.1 | T1190 |
In ISO/IEC 27001:2022 Annex A, this work sits mainly under A.8.25 (secure development life cycle), A.8.26 (application security requirements) and A.8.28 (secure coding); in CIS Controls v8.1 it falls under Control 16, Application Software Security. See the Application Security Guide and the OWASP Top 10 Guide.
Practitioner checklist
- Inventory every React and Next.js app with its hosting model, exact
next,reactandreact-server-dom-*versions, and the secrets it holds. - Confirm all services are on patched versions for CVE-2025-55182, CVE-2025-66478, the follow-up React advisories and CVE-2025-29927, and rotate secrets on any app that was exposed while unpatched.
- Move secure authorisation into a
server-onlyData Access Layer and keep Proxy to optimistic checks. - Re-authenticate and check resource ownership inside every Server Action and Route Handler; add tests that call them directly.
- Return DTOs, filter Server Action return values and enable the React taint APIs for tokens and sensitive objects.
- Audit
NEXT_PUBLIC_variables and built bundles for secrets on every release. - Enforce a nonce or hash-based CSP without
'unsafe-eval'in production, with violation reporting, and SRI on third-party scripts. - Sanitise all
dangerouslySetInnerHTMLand Markdown input with an approved sanitiser. - Strip framework-internal headers at the edge and configure
serverActions.allowedOrigins. - Alert on Node.js server processes spawning shells and on unusual Server Action POST patterns.
Standards and references
- React: Critical Security Vulnerability in React Server Components
- Next.js: Security Advisory CVE-2025-66478
- NIST NVD: CVE-2025-29927 Detail
- Next.js: How to think about data security in Next.js
- Next.js: How to implement authentication in Next.js
- Next.js: How to set a Content Security Policy for your Next.js application
- OWASP: Application Security Verification Standard 5.0
- CISA: Known Exploited Vulnerabilities Catalog
Related NHI Mgmt Group resources: Application Security Guide · OWASP Top 10 Guide · Cross-Site Scripting (XSS) Prevention Guide · Secrets Management Guide · Electron and Desktop App Security Guide