CVE-2025-29927: The Next.js Middleware Bypass That Broke Auth With One Header
A single HTTP header could skip every middleware check in Next.js — authentication, authorization, CSP, rate limiting, all of it. Here's exactly how CVE-2025-29927 works, who's affected, and what to do about it.
One header to skip them all
In March 2025, security researcher Rachid Allam disclosed one of the most consequential web framework vulnerabilities in recent memory. By adding a single HTTP header to any request, an attacker could completely bypass all Next.js middleware — authentication checks, authorization rules, CSP headers, rate limiting, geo-restrictions. Everything.
No credentials needed. No complex exploit chain. One curl command.
The vulnerability, CVE-2025-29927, scored a 9.1 on the CVSS scale (Critical). It affected every Next.js version from 11.x through 15.x, spanning years of production deployments across millions of applications. TikTok, Twitch, Hulu, Nike, and thousands of smaller apps all run on Next.js. If any of them relied on middleware for access control, they were exposed.
This is the full technical breakdown: what the vulnerability is, how it works under the hood, who was affected, and what you should do right now if you're building with Next.js.
What Next.js middleware does (and why this matters)
Next.js introduced middleware in version 12 as a way to run code before a request completes. You write a middleware.ts file, and it executes on every matching route before the page or API handler runs.
Most teams use middleware for security-critical functions:
// middleware.ts — a typical auth guard
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("session-token");
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
// Verify the token, check roles, etc.
const user = verifyToken(token.value);
if (!user) {
return NextResponse.redirect(new URL("/login", request.url));
}
// User is authenticated — allow the request
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/admin/:path*", "/api/protected/:path*"],
};
This is the standard pattern. If you've ever built a Next.js app with protected routes, you've probably written something like this. It feels solid — every request to /dashboard, /admin, or /api/protected hits the middleware first, and unauthenticated users get redirected to login.
CVE-2025-29927 made all of it irrelevant.
How the bypass works
Under the hood, Next.js uses an internal HTTP header called x-middleware-subrequest to manage recursive middleware calls. When middleware makes a subrequest (like fetching data or calling another route), Next.js attaches this header to prevent infinite loops. Each subrequest increments a counter in the header value, and once it hits a threshold, the framework skips middleware execution entirely.
The problem: Next.js never validated where this header came from.
The header was designed for internal use between Next.js components. But nothing stopped an external client from setting it. And when an attacker sent a request with the right value in x-middleware-subrequest, the framework saw the recursion counter at its limit and skipped the middleware — exactly as designed, but for a request that should never have been trusted.
Here's the exploit:
# Bypass all middleware on a Next.js 15.x app
curl -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" \
https://target.com/admin/dashboard
That's it. The /admin/dashboard route loads as if the middleware doesn't exist. No authentication check. No role validation. No redirect.
The exact header value varies slightly by version. For Next.js 14.x, where middleware can be in a src/ directory:
# Next.js 14.x with src/ directory
curl -H "x-middleware-subrequest: src/middleware:src/middleware:src/middleware" \
https://target.com/admin
For older apps using the pages router:
# Pages router format
curl -H "x-middleware-subrequest: pages/_middleware" \
https://target.com/api/sensitive
In every case, the result is the same: middleware never runs.
Why internal headers are a trap
This vulnerability falls into a well-known category: trusting data from the client. HTTP headers are entirely client-controlled. A server can't assume that any header value is authentic unless it has been cryptographically signed or validated at the network edge.
Next.js treated x-middleware-subrequest as an internal signal, but HTTP doesn't have a concept of "internal" headers. Every header in a request arrives through the same channel, whether it came from Next.js internals or from an attacker's terminal. Without validation, there's no way to distinguish one from the other.
The root cause maps to two CWE classifications:
- CWE-285: Improper Authorization — the middleware bypass eliminates authorization entirely
- CWE-290: Authentication Bypass by Spoofing — the spoofed header circumvents authentication checks
The fix in the patched versions was to either strip or validate the header before it reaches the middleware execution logic, ensuring external requests can't trigger the recursion-limit bypass.
What an attacker could do
The impact depends on what your middleware protects. For most Next.js apps, that means everything:
Authentication bypass. Access any protected page without logging in. Admin panels, user dashboards, internal tools — if the auth check lives in middleware, it's gone.
Authorization bypass. Even authenticated users have roles and permissions enforced in middleware. An attacker can escalate from a regular user to an admin, or access data they shouldn't see. (This is closely related to IDOR — another access control flaw that's rampant in AI-generated code.)
CSP bypass. Many apps set Content Security Policy headers in middleware. Bypassing middleware means the response comes back without CSP, opening the door to cross-site scripting attacks.
Rate limiting bypass. Middleware-based rate limiters stop working. Brute-force attacks against login endpoints, API abuse, scraping — all become possible without throttling.
Cache poisoning. If a CDN caches the response from a bypassed request, the poisoned response (without security headers, without auth checks) can be served to legitimate users.
Real attack chains
These aren't theoretical. Here's how an attacker would chain this:
Chain 1: Admin panel takeover
1. Identify the app runs Next.js (check for /_next/ paths)
2. Send GET /admin with the bypass header
3. Access the admin dashboard without credentials
4. Modify settings, export data, create backdoor accounts
Chain 2: Data exfiltration via API
1. Bypass auth middleware on /api/* routes
2. Hit user data endpoints directly
3. Enumerate and download sensitive records
4. No authentication logs generated (middleware never ran)
Chain 3: XSS via CSP bypass
1. Bypass middleware that sets CSP headers
2. Response arrives without Content-Security-Policy
3. Exploit a stored or reflected XSS vector
4. Steal session tokens, credentials, or user data
The third chain is particularly nasty. Even if you have other XSS mitigations, losing CSP removes a critical defense layer.
Affected versions
Every major version branch was vulnerable:
| Version | Vulnerable | Patched |
|---|---|---|
| 15.x | < 15.2.3 | 15.2.3 |
| 14.x | < 14.2.25 | 14.2.25 |
| 13.x | < 13.5.9 | 13.5.9 |
| 12.x | < 12.3.5 | 12.3.5 |
| 11.x and earlier | All versions | No patch (EOL) |
If you're running Next.js 11 or earlier, there is no fix. You need to upgrade or mitigate at the network layer.
The timeline
The disclosure and response moved fast:
| Date | Event |
|---|---|
| February 27, 2025 | Vulnerability reported to Next.js team via GitHub |
| March 17, 2025 | Next.js 14.2.25 released with fix |
| March 18, 2025 | Next.js 15.2.3 released with fix |
| March 18, 2025 | CVE-2025-29927 assigned |
| March 21, 2025 | Public disclosure and PoC exploits published |
| March 22, 2025 | Mass scanning observed in the wild |
Note the gap: PoC code was public on March 21, and mass scanning started the next day. If you weren't patched by March 22, automated tools were already probing your app.
What you should do right now
1. Update Next.js
This is the primary fix. Update to the patched version for your branch:
# Latest (recommended)
npm install next@latest
# Or pin to a specific patched version
npm install next@15.2.3 # for 15.x
npm install next@14.2.25 # for 14.x
npm install next@13.5.9 # for 13.x
npm install next@12.3.5 # for 12.x
After updating, test your middleware. Make sure auth redirects still work, CSP headers are still set, and your protected routes are still protected.
2. Strip the header at your proxy (if you can't patch immediately)
If you're behind Nginx, Apache, or a reverse proxy, you can strip the dangerous header before it reaches Next.js:
# Nginx — strip the header from all incoming requests
proxy_set_header x-middleware-subrequest "";
Or block requests that include it entirely:
# Nginx — reject requests with the header
if ($http_x_middleware_subrequest) {
return 403;
}
For Cloudflare, create a WAF rule:
Rule: (http.request.headers["x-middleware-subrequest"] exists)
Action: Block
3. Add detection logging
Even after patching, you should log attempts. This helps you understand if anyone was trying to exploit this before you patched:
// Add to the top of your middleware.ts
export function middleware(request: NextRequest) {
const bypassHeader = request.headers.get("x-middleware-subrequest");
if (bypassHeader) {
console.warn("CVE-2025-29927 exploit attempt detected:", {
ip: request.ip,
path: request.nextUrl.pathname,
header: bypassHeader,
timestamp: new Date().toISOString(),
});
return new NextResponse("Forbidden", { status: 403 });
}
// ... rest of your middleware
}
4. Stop relying on middleware as your only security layer
This is the bigger lesson. Middleware is a convenience layer, not a security boundary. It's valuable for redirects, setting headers, and adding a first line of defense. But it should never be the only place you check authentication.
Add auth checks in your API routes:
// app/api/protected/route.ts
import { getServerSession } from "next-auth";
export async function GET(request: Request) {
// Server-side auth check — independent of middleware
const session = await getServerSession();
if (!session) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
// ... handle the authenticated request
}
Add auth checks in your server components:
// app/admin/page.tsx
import { redirect } from "next/navigation";
import { getServerSession } from "next-auth";
export default async function AdminPage() {
const session = await getServerSession();
if (!session || session.user.role !== "admin") {
redirect("/login");
}
return <AdminDashboard />;
}
These server-side checks run regardless of middleware. Even if middleware is bypassed — through this CVE or a future one — the route itself still enforces access control.
Defense in depth: the pattern that saves you
The Moltbook breach (Supabase RLS disabled), the Lovable platform vulnerability (no RLS on AI-generated apps), and now CVE-2025-29927 all share a root cause: a single security layer was the only security layer.
The pattern that protects against this entire class of problems is defense in depth:
| Layer | What it does | Next.js example |
|---|---|---|
| Edge/CDN | Strip dangerous headers, block known attack patterns | Cloudflare WAF rule, Nginx header stripping |
| Middleware | First-pass auth checks, redirects, security headers | middleware.ts with session validation |
| Route handler | Server-side auth verification per endpoint | getServerSession() in API routes |
| Database | Row-level access control | Supabase RLS, Prisma middleware |
If any single layer fails, the others still hold. That's the point. Security isn't about having one perfect lock — it's about having locks at every door.
The vibe coding angle
If you're building with Cursor, Lovable, Bolt, v0, or any AI coding tool, this vulnerability has a specific implication: your AI assistant probably generated middleware-based auth as your primary security layer.
That's not a knock on the tools. Middleware auth is the documented, recommended pattern in Next.js. It's what the tutorials show. It's what AI models have been trained on. But CVE-2025-29927 proves that the recommended pattern isn't sufficient on its own.
When you ask an AI to "add authentication to my Next.js app," it'll generate middleware. It won't automatically add redundant server-side checks in every route handler. It won't configure WAF rules. It won't set up header stripping at the proxy layer. Those are defense-in-depth measures that you need to explicitly request — or explicitly verify.
This doesn't mean middleware auth is bad. It means middleware auth alone is incomplete. The fix is straightforward: after your AI generates the middleware, ask it to also add server-side auth checks in your route handlers and server components. Two layers instead of one.
How Flowpatrol catches this
CVE-2025-29927 is a textbook example of what automated security scanning should catch. Flowpatrol's scanner:
- Detects Next.js by fingerprinting the framework (the
/_next/path structure, response headers, and build artifacts are reliable signals) - Tests for header-based bypasses by sending requests with the
x-middleware-subrequestheader and comparing responses with and without it - Validates auth enforcement by checking whether protected routes actually require authentication at the server level, not just the middleware level
- Checks for defense in depth by verifying that multiple security layers are present, so a single bypass doesn't compromise everything
A scan takes minutes. The vulnerability it catches could have left your entire app wide open.
CVE-2025-29927 is documented in detail by Vercel's postmortem, ProjectDiscovery's technical analysis, Datadog Security Labs, and the NVD entry.