CVE-2025-29927

Next.js Middleware Bypass: When 'I'm With The Band' Actually Works

Amit Schendel
Amit Schendel
Senior Security Researcher

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/users

If the application uses a src directory, you adjust your aim:

curl -H "x-middleware-subrequest: src/middleware" https://vulnerable-site.com/admin/users

The "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/protected

It'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.

Fix Analysis (2)

Technical Appendix

CVSS Score
9.1/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
EPSS Probability
92.90%
Top 0% most exploited

Affected Systems

Next.js Framework (Self-Hosted)Node.js Applications using Next.js MiddlewareDockerized Next.js deployments

Affected Versions Detail

Product
Affected Versions
Fixed Version
Next.js
Vercel
11.1.4 - 12.3.412.3.5
Next.js
Vercel
13.0.0 - 13.5.813.5.9
Next.js
Vercel
14.0.0 - 14.2.2414.2.25
Next.js
Vercel
15.0.0 - 15.2.215.2.3
AttributeDetail
CWE IDCWE-285 (Improper Authorization)
CVSS v3.19.1 (Critical)
Attack VectorNetwork (AV:N)
EPSS Score92.90%
Exploit StatusActive / High Availability
KEV StatusNot Listed (Monitoring Recommended)
CWE-285
Improper Authorization

The product does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.

Vulnerability Timeline

Fix commits pushed to repository
2025-03-17
Public disclosure (CVE-2025-29927)
2025-03-21
Public PoC exploits released
2025-03-24

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.