One header to bypass every security check
In March 2025, a single HTTP header was enough to skip every middleware guard in Next.js. Authentication. Authorization. Rate limiting. CSP headers. All of it — bypassed with one curl command.
Here's the exploit:
curl -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" \
https://your-app.com/admin/dashboard
That's it. The /admin/dashboard route loads as if middleware doesn't exist. No login required. No role checks. No redirects.
The vulnerability, CVE-2025-29927, scored 9.1 on the CVSS scale (Critical). It affected every Next.js version from 11.x through 15.x — spanning millions of production apps. If you rely on middleware for access control (and most Next.js builders do), you were exposed.
This article walks through exactly how the bypass works, why Next.js trusted the wrong thing, and what you need to do right now.
Are you affected? A 30-second check
Before diving into the technical details, check your app:
# Look for middleware.ts or middleware.js in your project root or src/ directory
grep -r "middleware" package.json
ls -la middleware.ts middleware.js 2>/dev/null || echo "No middleware file found"
# Check your Next.js version
grep '"next"' package.json | grep -oE '"\d+\.\d+'
If you're running Next.js 11–15.x and have a middleware.ts file defining auth checks, rate limiting, or other security rules — you were likely exposed until March 2025. Scroll to "Affected versions" below to see if your exact version is patched.
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
The recursion guard that trusted the client
Next.js uses an internal mechanism to prevent infinite loops when middleware calls other routes. It works like this:
When middleware needs to make a subrequest (like fetching data or checking another endpoint), Next.js attaches a header called x-middleware-subrequest to keep track of the recursion depth. If a request goes too deep, the framework stops running middleware to prevent infinite loops. This is a safety valve.
The logic is simple:
- Middleware runs on every request
- If middleware makes a subrequest, Next.js adds
x-middleware-subrequestto it - If that subrequest goes too deep, Next.js skips middleware and returns the response
The fatal flaw: Next.js never validated whether this header came from the framework itself or from an external attacker.
An attacker can forge the header on an external request, and Next.js will treat it like an internal subrequest and skip middleware entirely.
Why this matters
To understand the impact, look at how middleware-based auth actually protects applications:
// middleware.ts — the standard auth guard
export function middleware(request: NextRequest) {
const token = request.cookies.get("session-token");
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next(); // Auth passed, continue
}
The middleware runs on every request to /admin/*, /api/protected/*, etc. If middleware is skipped entirely, this check is skipped entirely. No authentication. No role validation. Nothing.
Craft the bypass for your version
The header value must match your Next.js directory structure. For Next.js 15.x (default structure):
curl -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" \
https://your-app.com/admin/protected-route
For Next.js 14.x with src/ directory:
curl -H "x-middleware-subrequest: src/middleware:src/middleware:src/middleware" \
https://your-app.com/admin
For pages router (older Next.js):
curl -H "x-middleware-subrequest: pages/_middleware" \
https://your-app.com/api/sensitive
The response will be identical to a request that passed authentication — because middleware was never executed.
Why frameworks can't trust HTTP headers
This vulnerability reveals a fundamental principle: HTTP has no concept of "internal" traffic. Every header in a request — whether it came from Next.js internals or an attacker's laptop — arrives through the exact same channel. Without cryptographic validation or network-level isolation, a server cannot distinguish one from the other.
Next.js was using x-middleware-subrequest as an internal signal, but there was nothing internal about it. An attacker could forge it. Validate it. Spoof it. The framework had no way to know the difference.
This falls under two CWE classifications:
- CWE-290: Authentication Bypass by Spoofing
- CWE-285: Improper Authorization
The fix in patched versions validates that the header actually came from an internal subrequest before trusting it. External requests can no longer 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.
For AI-built apps: middleware is not enough
If you built your Next.js app with Cursor, Lovable, Bolt, or v0, your AI assistant probably generated middleware-based auth. It's the documented pattern. It's what every tutorial shows. It's what LLMs have been trained on.
But CVE-2025-29927 is a reminder: a single recommended pattern — even when it's the framework's own pattern — is not a security boundary. The middleware guards are valuable. They're just not complete.
When an AI generates middleware.ts with auth checks, it's not also thinking about defense in depth. It's not adding redundant server-side checks in every API route. It's not saying "and also configure your CDN to strip this header."
The fix is easy: Ask your AI to add server-side auth checks alongside the middleware. Two layers are harder to bypass than one.
// Ask your AI to add this in ADDITION to middleware auth
export async function GET(request: Request) {
// Check auth server-side, independent of middleware
const session = await getServerSession();
if (!session) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
// ... rest of handler
}
Middleware remains useful — it's fast, it can redirect early, it can set headers. But it shouldn't be your only lock on the door.
Scan for this before you ship
CVE-2025-29927 is a textbook case for what automated scanning catches. Here's how Flowpatrol detects it:
- Fingerprint Next.js — the
/_next/path structure, response headers, and build artifacts are reliable signals - Test the bypass directly — send requests with
x-middleware-subrequestheaders at different recursion depths and compare responses - Verify auth enforcement — check whether protected routes actually require authentication at the server level, not just at middleware
- Check for defense in depth — verify that multiple layers exist, so a single bypass doesn't compromise everything
Paste your Next.js app's URL. Get results in minutes. If you're vulnerable, you see exactly which routes bypass auth. If you're patched, we confirm it. That certainty — knowing you're solid before you launch — is what matters.
Remember: This CVE affected every Next.js version 11–15.x. If your app deployed before March 18, 2025 and you haven't updated, this is the first thing to check.
CVE-2025-29927 is documented in detail by Vercel's postmortem, ProjectDiscovery's technical analysis, Datadog Security Labs, and the NVD entry.