Next.js Middleware Bypass: When 'I'm With The Band' Actually Works
Jan 12, 2026·5 min read
Executive Summary (TL;DR)
By sending the `x-middleware-subrequest` header, attackers can trick the Next.js router into believing a request has already passed security checks. This bypasses authentication and access controls defined in `middleware.ts`. Patched versions introduce a server-side secret to validate this header.
A critical authorization bypass in the Next.js framework allows attackers to skip middleware execution entirely by injecting a specific internal HTTP header. This effectively removes the 'bouncer' from the door of your application, granting unauthorized access to protected routes.
The Hook: The Middleware Myth
In the modern React ecosystem, Next.js Middleware is the sacred gatekeeper. It’s the code that runs before a request completes, allowing developers to rewrite paths, check authentication tokens, and return 401s to uninvited guests. We trust it implicitly. We put our most sensitive logic there: if (!session) return NextResponse.redirect('/login').
But what if I told you that the router—the very mechanism responsible for directing traffic—had a gullible streak? Imagine a bouncer at an exclusive club who doesn't check the guest list but instead checks if you're wearing a generic stick-on nametag that says "STAFF".
CVE-2025-29927 is exactly that stick-on nametag. It turns out that the mechanism Next.js uses to prevent infinite loops in internal routing was trusting a header that anyone could send from the outside. If you told the server, "Don't worry, I'm an internal sub-request," the server would simply nod and wave you through, bypassing the middleware entirely.
The Flaw: Trust Issues and Recursion
To understand this bug, you have to understand the problem Next.js was trying to solve: Recursion. When middleware rewrites a URL (e.g., /admin -> /dashboard), it effectively starts a new request processing cycle. If that new cycle triggers the middleware again, and the middleware rewrites it again, you get an infinite loop that crashes the server.
To prevent this, Next.js implemented a flag—specifically, the x-middleware-subrequest HTTP header. When the server sees this header, it thinks: "Ah, I've already processed the middleware for this request. I shouldn't run it again. I'll just serve the content directly."
The fatal flaw? Trust. The server didn't verify who put that header there. It assumed that only the internal routing logic could set it. But HTTP headers are user-controlled input. By manually adding x-middleware-subrequest to a request, an external attacker can masquerade as an internal system process. The router sees the header, assumes the security checks have already passed, and serves the protected content on a silver platter.
The Code: The Smoking Gun
The fix implementation reveals just how simple the oversight was. The remediation involves creating a "secret handshake" that the client cannot know. In the patched versions, the server generates a random, process-specific secret at startup using crypto.getRandomValues. This secret is stored in a global symbol.
Here is the logic logic that effectively killed the bug. The server now intercepts incoming requests and scrubs the x-middleware-subrequest header if it isn't accompanied by a valid ID proving it originated internally.
// packages/next/src/server/lib/server-ipc/utils.ts
// The "fix" logic:
if (
header === 'x-middleware-subrequest' &&
// Check if the request has the secret ID matching the server's memory
headers['x-middleware-subrequest-id'] !==
(globalThis as any)[Symbol.for('@next/middleware-subrequest-id')]
) {
// If the secret is missing or wrong, DELETE the header.
// This forces the middleware to run again, catching the attacker.
delete headers['x-middleware-subrequest']
}Before this patch, that delete statement didn't exist. The router simply read headers['x-middleware-subrequest'], saw a value, and skipped the middleware. Now, if you try to fake the flag, the server strips it out and treats you like any other untrusted request.
The Exploit: Knocking on the Back Door
Exploiting this is embarrassingly simple. You don't need complex memory corruption or race conditions. You just need curl. The only nuance is knowing the directory structure of the target application, as the value of the header often needs to match the middleware's file name.
For a standard Next.js 12+ application, the attack looks like this:
# Target: A dashboard that requires login
# Attack: Bypass the auth middleware
curl -H "x-middleware-subrequest: middleware" https://vulnerable-site.com/admin/usersIf the application uses a src directory, you adjust your aim:
curl -H "x-middleware-subrequest: src/middleware" https://vulnerable-site.com/admin/usersThe "Depth Check" Bypass: In later versions (v13.2+), Next.js added a "depth check" to limit recursion depth. It was a functional fix, not a security fix, but it accidentally made exploitation slightly harder. To bypass this, attackers found that repeating the identifier tricks the counter logic:
curl -H "x-middleware-subrequest: middleware:middleware:middleware" https://target.com/protectedIt's the digital equivalent of wearing three fake mustaches to get past facial recognition.
The Impact: Total Access
The impact here is Critical because middleware is the de facto standard for authentication in Next.js applications (especially those using NextAuth.js or Clerk). When you bypass middleware, you aren't just skipping a redirect; you are often stripping away the entire identity layer.
If your application relies solely on middleware to protect /admin routes (a common pattern), an attacker can access those routes as an unauthenticated user. They can read databases, trigger administrative actions, or access PII.
However, there is a silver lining: this mostly affects self-hosted deployments (Docker, Node.js). Applications hosted on Vercel's infrastructure are protected by their edge network, which strips these headers before they reach the application logic. If you are running Next.js in a container on AWS or DigitalOcean, you are the primary target.
The Fix: Secret Handshakes
The remediation strategy adopted by the Next.js team is robust. By requiring a x-middleware-subrequest-id that corresponds to a cryptographically secure random value generated at runtime, they ensure that no external actor can forge a valid sub-request header.
If you cannot upgrade immediately, you must replicate this logic at your network edge. Configure your WAF (Cloudflare, AWS WAF) or reverse proxy (Nginx) to drop any incoming request containing the x-middleware-subrequest header. This header should never legitimately originate from a client browser.
Official Patches
Fix Analysis (2)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Next.js Vercel | 11.1.4 - 12.3.4 | 12.3.5 |
Next.js Vercel | 13.0.0 - 13.5.8 | 13.5.9 |
Next.js Vercel | 14.0.0 - 14.2.24 | 14.2.25 |
Next.js Vercel | 15.0.0 - 15.2.2 | 15.2.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-285 (Improper Authorization) |
| CVSS v3.1 | 9.1 (Critical) |
| Attack Vector | Network (AV:N) |
| EPSS Score | 92.90% |
| Exploit Status | Active / High Availability |
| KEV Status | Not Listed (Monitoring Recommended) |
MITRE ATT&CK Mapping
The product does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.