Feb 21, 2026·6 min read·144 visits
Next.js implemented a loop-prevention mechanism that trusted a specific header to track middleware recursion. Attackers can manually inject this header (`x-middleware-subrequest`) to convince the server that the limit has been reached, causing Next.js to skip security middleware and serve protected content.
A critical authorization bypass vulnerability in Next.js allows attackers to skip middleware execution entirely. By spoofing an internal recursion-tracking header, attackers can trick the server into believing a request has already been processed multiple times, causing it to bypass authentication checks and access protected routes directly. This affects all versions from 11.1.4 up to 15.2.2.
Middleware in Next.js is the unsung hero of modern React applications. It sits at the edge, acting as the bouncer for your routes. It checks if you have a session token, validates your permissions, rewrites paths based on your locale, and injects security headers like CSP. It is the gatekeeper. If the middleware doesn't run, the gate is effectively left wide open.
But middleware has a problem: recursion. Since middleware can rewrite requests to other paths which might trigger more middleware, there is a risk of creating an infinite loop that crashes the server (or at least the Vercel function, costing you a fortune in compute time). To solve this, Next.js needs a way to track how deep the rabbit hole goes.
The engineers implemented a solution that is logical on paper but catastrophic in practice: they used an HTTP header to count the loops. Specifically, x-middleware-subrequest. The server reads this header, counts the occurrences of the middleware name, and if it hits a hardcoded limit (usually 5), it assumes a loop is happening and... stops executing the middleware to save the process.
Here is the kicker: they trusted the client to provide the truth. They assumed that this header would only ever be present if the server put it there during an internal rewrite. They forgot the first rule of web security: Input is evil. If an attacker simply sends this header in their initial request, the server throws its hands up, says 'Whoa, too many loops!', skips the security checks, and hands over the data.
The vulnerability stems from a classic logic flaw: relying on client-controlled data for server-side state management without validation. In versions of Next.js ranging from v12.2 to v15.2.2, the logic for handling the x-middleware-subrequest header shifted from a simple path check to a recursion depth check. The code looks for a colon-separated list of middleware names in the header.
Technically, the server parses x-middleware-subrequest. It splits the string by colons and counts how many times the current middleware's name appears. If that count exceeds MAX_RECURSION_DEPTH (which is set to 5), the runMiddleware function essentially returns early, passing the request downstream to the page handler or API route without running the user-defined logic in middleware.ts.
This is the architectural equivalent of a bank vault that locks automatically at 5:00 PM, but checks the time by looking at the customer's wristwatch. If you walk in at noon and tell the teller 'It's 5:01 PM', they unlock the vault and go home, leaving you inside. Because the header is processed before any user code runs, there is nothing a developer can write in their own middleware.ts to stop this. The framework fails before your code even loads.
Let's look at the logic that caused the issue. While the exact file paths vary by version, the core logic in the adapter.ts or next-server components effectively did this:
// Pseudocode of the vulnerable logic
const MAX_RECURSION_DEPTH = 5;
function getMiddlewareRecursion(req) {
const header = req.headers.get('x-middleware-subrequest') || '';
const depth = header.split(':').filter(n => n === middlewareName).length;
return depth;
}
// Later in the request lifecycle...
if (getMiddlewareRecursion(req) >= MAX_RECURSION_DEPTH) {
// Assume infinite loop, skip middleware execution
return { finished: true };
}The fix, introduced in commit 52a078da3884efe6501613c7834a3d02a91676d2 (and backported in 5fd3ae8f8542677c6294f32d18022731eab6fe48), introduces a shared secret. The server now generates a random token at startup. Internal sub-requests must sign the header with this token.
// The Fix: Validation of the source
if (requestHeaders.has('x-middleware-subrequest')) {
const subrequestId = requestHeaders.get('x-middleware-subrequest-id');
// Check if the secret matches what the server generated at startup
if (subrequestId !== internalSecret) {
// If it doesn't match, the client is spoofing it. Strip the header.
requestHeaders.delete('x-middleware-subrequest');
}
}This ensures that only the Next.js server itself can increment the recursion counter. If an external attacker tries to inject the header, they won't know the random 8-byte hex string (stored in a Symbol in globalThis), so the server strips their fake header and runs the middleware as normal.
Exploiting this requires zero coding skills and standard command-line tools. You don't need buffer overflows; you just need to know how to count to five.
The Scenario: You have a Next.js application protecting an admin panel at /admin. The middleware checks for a session cookie and redirects to /login if it's missing.
The Attack:
/admin. Receive 307 Temporary Redirect to /login.middleware or src/middleware.curl -v http://target.com/admin \
-H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware"The Result: The server sees the header, calculates depth = 5, triggers the loop prevention logic, skips the auth check, and returns 200 OK with the contents of the admin dashboard.
> [!WARNING]
> In some setups, the middleware name might include the path, like pages/_middleware. Exploitation tools like Nuclei simply spray variants: src/middleware:middleware:pages/_middleware... to cover all bases.
The immediate impact is obvious: broken access control. If you use Next.js middleware to gate content, that gate is gone. Admin panels, user dashboards, and premium content are accessible to anyone.
However, the secondary impact is potentially worse: Web Cache Poisoning.
Many Next.js sites sit behind CDNs (Vercel Edge Network, Cloudflare, Fastly). If an attacker sends the exploit request and the CDN is configured to cache the response (even briefly), the CDN might cache the bypassed version of the page (the 200 OK admin panel) instead of the redirect.
Now, legitimate administrators or users navigating to that URL will be served the cached, exploited content. Or worse, if the page renders user-specific data (PII) relying on the middleware to filter it, an attacker could force the server to generate a page with their own malicious state, cache it, and serve it to victims. This turns a targeted bypass into a broad Denial-of-Service or Information Disclosure event.
The vulnerability is patched in Next.js versions 15.2.3, 14.2.25, 13.5.9, and 12.3.5. The patch logic relies on stripping the malicious header if a corresponding secret ID is absent.
Immediate Remediation Strategy:
next dependency immediately. npm install next@latest.Mitigation (If you cannot upgrade): If you are stuck on a legacy version or cannot deploy immediately, you must implement a block at your network edge.
Nginx Configuration:
# Drop the header before it hits the node process
proxy_set_header x-middleware-subrequest "";Cloudflare WAF Rule:
Create a firewall rule blocking any request where the header x-middleware-subrequest exists. Since this header is internal-only, legitimate clients (browsers) never send it. It is safe to drop all external traffic containing it.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N| 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) |
| Attack Vector | Network (HTTP Headers) |
| CVSS | 9.1 (Critical) |
| EPSS Score | 0.92896 (92.90%) |
| Impact | Full Auth Bypass |
| Exploit Status | Active / POC Available |
The software does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.
An uncontrolled resource consumption vulnerability exists in the Scala-based http4s-blaze-server package of the http4s/blaze library. The vulnerability allows remote, unauthenticated attackers to cause an Out of Memory Error (OOM) and JVM crash by streaming a continuous sequence of small or empty WebSocket continuation frames with the FIN bit set to 0. This bypasses typical payload size checks because of the JVM's per-object allocation overhead, leading to rapid heap exhaustion with minimal network bandwidth.
A critical path traversal vulnerability has been identified in the OpenList Go-based backend package. The vulnerability exists within the batch rename handler because the application does not validate the source filename parameter before constructing filesystems paths. This omission allows authenticated users to escape their designated directory and rename files in sibling paths.
OpenList version 4.2.3 and prior is vulnerable to an authorization bypass and metadata leakage. When configured with the Bleve search engine backend, OpenList fails to perform separator-aware path matching when validating tenant containment. This allows authenticated users to access sibling directories sharing similar name prefixes. Furthermore, the search backend returns unfiltered global result counts, leaking existence verification data of unauthorized files via side-channel analysis.
An authorization bypass vulnerability in OpenList version 4.2.3 and below allows authenticated users to read arbitrary files outside of their designated base directories due to an insecure path prefix check using Go's standard strings.HasPrefix function.
A security policy bypass vulnerability exists in the AWS API MCP Server (awslabs-aws-api-mcp-server) from version 0.2.13 through 1.3.46. When the server fails to load the read-only operations index during startup (due to transient network failures, file permission issues, or other exceptions), it logs a warning but continues running in an insecure, degraded state. Under this condition, the security policy engine fails open, silently skipping all subsequent security checks and consent prompts for the lifetime of the process. This permits unauthorized mutating AWS CLI commands to execute via indirect prompt injection attacks.
An incomplete escaping vulnerability in the npm package 'shescape' allows unauthenticated users to trigger dynamic shell expansions, absolute path disclosure, and command block break-outs on Unix and Windows systems.