Next.js Middleware Auth Bypass: CVE-2025-29927
TL;DR — CVE-2025-29927 is a critical (CVSS 9.1) authorization bypass in Next.js. The framework used an internal header, x-middleware-subrequest, to stop middleware from recursing — and it trusted that header even on requests coming from the outside. So an attacker could add x-middleware-subrequest to any request and Next.js would skip middleware entirely, sailing straight past any auth or redirect logic that lived there. The fix: upgrade Next.js to the patched release for your major (15.2.3, 14.2.25, 13.5.9, or 12.3.5). The deeper fix: never make middleware the sole authorization gate — re-check auth in the route handler, server action, and data layer too.
Is my Next.js app vulnerable to CVE-2025-29927?
If you run a self-hosted Next.js app (using next start or output: 'standalone') on an affected version, and you gate access to protected routes in middleware.ts, then yes — you were exposed.
The affected and patched ranges, straight from the GitHub advisory (GHSA-f82v-jwr5-mffw) and NVD:
| Release line | Affected range | Fixed in |
|---|---|---|
| 15.x | ≥ 15.0.0, < 15.2.3 | 15.2.3 |
| 14.x | ≥ 14.0.0, < 14.2.25 | 14.2.25 |
| 13.x | ≥ 13.0.0, < 13.5.9 | 13.5.9 |
| 12.x / 11.x | ≥ 11.1.4, < 12.3.5 | 12.3.5 |
The bug was introduced at 11.1.4. There is no separate 11.x patch — if you're on 11.x, you must move to 12.3.5 (or apply the header workaround and move auth out of middleware).
Check the resolved version, not the range in package.json — a caret range like "next": "^14.1.0" tells you what's allowed, not what's installed:
npx next --version # the actually-resolved binary
npm ls next # the lockfile-resolved version in the tree
npm audit # flags GHSA-f82v-jwr5-mffw until you're patched
Who was NOT exposed: apps on Vercel and Netlify. Vercel's routing runs decoupled from the Next.js server, so the client-supplied header never reached the middleware evaluation the way it did in self-hosted setups. Static exports and Cloudflare Workers deployments were likewise not impacted.
How does the middleware bypass work?
Next.js middleware runs before a request reaches a route — perfect for auth gating, redirects, and header rewriting. Internally, Next.js can re-invoke middleware as part of its own request handling, so it needs a way to stop that from recursing forever. It used an internal request header, x-middleware-subrequest, to flag a request as an internal subrequest and tell the framework to skip running middleware.
The flaw: that header was trusted unconditionally. Nothing verified it originated internally rather than from the client. Add the header to an inbound request and Next.js skips middleware — while still serving the underlying route. Any authorization decision in middleware was silently bypassed.
The expected value tracked how Next.js evolved its recursion guard (per source-level analyses from Datadog Security Labs and ProjectDiscovery):
- Pre-12.2: the middleware file path, e.g.
x-middleware-subrequest: pages/_middleware. - ~12.2–13.1.x: a single value like
middleware(orsrc/middlewarefor asrc/layout). - 13.2.0+ / 14.x / 15.x: a recursion-depth guard that counts occurrences and skips once the count exceeds
MAX_RECURSION_DEPTH(default 5), so the payload became the identifier repeated five times, colon-separated:middleware:middleware:middleware:middleware:middleware.
The advisory confirms the header name and that stripping it defeats the attack; the MAX_RECURSION_DEPTH = 5 matching detail comes from those third-party source analyses, not the advisory text.
Remediation: upgrade first
The real remedy is upgrading Next.js to the patched release for your major line, and GuardLayer's general/vulnerable-dependency rule flags exactly this — a Next.js version in package.json that sits below the patched release and is therefore exposed to this CVE. Pin to at least the patched version so you get the fix without an unwanted major jump:
npm install next@14.2.25 # or next@^14.2.25 to take later patches on your major
yarn add next@14.2.25
pnpm add next@14.2.25
That same check fires on a project still carrying an outdated, vulnerable Next.js:
- Warningpackage.json:3
Dependency with a known advisory
Upgrade to the patched version and run npm audit to confirm the advisory is resolved.
Be precise about what that scan does: GuardLayer flags the outdated version — the honest, high-signal tie-in to this CVE. It does not detect the x-middleware-subrequest header spoof at the code level; nothing static in your handlers reveals that a header was trusted at the framework layer. The version flag is the actionable signal, and upgrading is the fix. For the broader dependency picture, see our guide on auditing vulnerable npm dependencies.
If you can't upgrade immediately: strip the header at the edge
The advisory's documented workaround is to prevent external requests carrying x-middleware-subrequest from reaching your app. Because the header is what causes middleware to be skipped, you can't fix this from inside Next.js — the stripping must happen at a layer in front of the app.
nginx reverse proxy — clear any client-supplied value before forwarding upstream:
location / {
proxy_set_header x-middleware-subrequest "";
proxy_pass http://nextjs_upstream;
}
Cloudflare / generic WAF — block the request outright:
(any(http.request.headers.names[*] == "x-middleware-subrequest"))
# Action: Block
Caveats to state plainly: this only protects paths that actually pass through that proxy — if your origin is reachable directly, the header sails through, so strip it at every ingress. HTTP header names are case-insensitive, so make sure your rule matches regardless of casing. And it's a stopgap, not a repair of the underlying trust bug. (Cloudflare shipped a managed WAF rule on March 22, 2025 that blocks this header regardless of Next.js version — but it's opt-in, not on by default, because auto-blocking broke sites whose third-party auth middleware legitimately relied on the header.)
The durable lesson: middleware is not your only authorization layer
Vercel's postmortem says it directly: "We do not recommend Middleware to be the sole method of protecting routes." Middleware is best for cheap, coarse redirects — bouncing an unauthenticated visitor toward a login page for nicer UX — not the last line of defense. Re-check authorization at the layer that actually returns the data:
// app/api/admin/users/route.ts
import { NextResponse } from "next/server";
import { getSession } from "@/lib/auth";
export async function GET() {
const session = await getSession(); // re-check HERE, not just in middleware
if (!session) return NextResponse.json({ error: "Unauthenticated" }, { status: 401 });
if (session.user.role !== "admin") return NextResponse.json({ error: "Forbidden" }, { status: 403 });
return NextResponse.json({ users: await db.user.findMany() });
}
Do the same in server components (redirect("/login") at render time) and in server actions, where auth must be verified inside the action itself — GuardLayer's nextjs/server-action-no-auth rule flags server actions with no auth check, precisely because a bypassed middleware layer must not be game over. The strongest backstop is pushing authorization into a data-access layer and enabling database Row-Level Security, so a request that skips every application-layer check still can't read rows it doesn't own. That layered posture — covered in our Next.js app-layer security guide — is what turns a single-layer bug like CVE-2025-29927 into a non-event.
Confirm you're patched
Send the exploit header at a protected route and compare it to the same request without the header. On a patched build they must be identical — both denied:
curl -i https://your-app.example.com/admin -H "x-middleware-subrequest: middleware"
# Patched: same 302/401/403 as a request WITHOUT the header
# Vulnerable: returns the protected page (bypass succeeded)
If adding the header changes the outcome, you're still vulnerable.
FAQ
Is CVE-2025-29927 fixed by upgrading Next.js alone? Yes — upgrading to 15.2.3, 14.2.25, 13.5.9, or 12.3.5 (for your major line) closes the bypass. But you should also stop relying on middleware as the sole authz gate, so any future middleware issue isn't a total compromise.
What Next.js versions are patched? Exactly 15.2.3, 14.2.25, 13.5.9, and 12.3.5. The GitHub advisory, NVD, and Vercel's postmortem all designate these versions as the fix.
Was my Vercel-hosted app affected?
No. Vercel's routing runs in a separate, globally distributed system, so the client header never reached middleware evaluation. Netlify, Cloudflare Workers, and static exports were also unaffected. Only self-hosted (next start / output: 'standalone') deployments were exposed.
Can I mitigate this from next.config.js or middleware?
No. The whole point of the bug is that middleware is skipped, so any in-app rule that depends on middleware running is useless. The stopgap must run in front of the app (proxy, CDN, or WAF); the real fix is upgrading.
When was it disclosed? Privately reported Feb 27, 2025; fixed releases published March 18, 2025; public disclosure March 21, 2025. Credited to Allam Rachid ("zhero;") and Allam Yasser ("inzo_").
Catch this before it ships — free
GuardLayer scans every push for this and 23 other Next.js + Supabase issues, with the exact fix inline.
No signup, no card — your code is scanned in memory and never stored.