Vulnerabilities
7 min readUpdatedCVE-2025-29927: the Next.js middleware authorisation bypass
Quick answer
CVE-2025-29927 is an authorisation bypass in Next.js. The framework used an internal header, x-middleware-subrequest, to stop middleware recursing; by sending that header, an external attacker could make Next.js skip middleware entirely. Apps that enforced authentication in middleware could be bypassed with a single request. It scored CVSS 9.1, was disclosed on 21 March 2025, and affects Next.js from 11.1.4 up to the fixes in 12.3.5, 13.5.9, 14.2.25 and 15.2.3. This is a rare case where an edge rule genuinely helps.
At a glance
critical severity- Identifier
- CVE-2025-29927
- Also known as
- Next.js middleware authorisation bypass
- Product
- Next.js (self-hosted, App and Pages Router)
- Affected
- 11.1.4 ≤ Next.js < 12.3.5; 13.0.0 ≤ Next.js < 13.5.9; 14.0.0 ≤ Next.js < 14.2.25; 15.0.0 ≤ Next.js < 15.2.3
- Fixed in
- 12.3.5; 13.5.9; 14.2.25; 15.2.3
- CVSS
- 9.1 (CVSS 3.1, GitHub as CNA)
- Disclosed
- Known exploited (CISA KEV)
- Not listed
Patched. Public analysis and proof-of-concept exist; trivially exploitable where auth is enforced only in middleware.
01What is CVE-2025-29927?
CVE-2025-29927 is an authorisation bypass in Next.js. Middleware in Next.js runs before routing and rendering, which made it a tempting place to put authentication and access checks — redirect unauthenticated users, block admin routes, and so on. The framework used an internal header, x-middleware-subrequest, to detect and short-circuit its own internal sub-requests so middleware would not run recursively.
The flaw was that this header was trusted when it arrived from outside. An attacker who simply added x-middleware-subrequest to a request could make Next.js believe middleware had already run and skip it. If your authorisation lived in that middleware, it was skipped too, and the request reached the protected route unchecked. No credentials, session or special tooling were needed.
02Which versions and deployments are affected?
Affected releases span from Next.js 11.1.4 up to the fixes. What matters as much as the version is how you deploy: the bypass needs your own server to make the middleware decision, so self-hosted deployments using next start or output: 'standalone' were affected, while some managed platforms that run routing separately were not.
| Deployment / version | Affected? | Note |
|---|---|---|
| Self-hosted next start / standalone < fix | Yes | The server itself evaluates middleware |
| Static export (output: 'export') | No | No middleware runtime exists |
| Vercel-hosted | No | Routing runs decoupled from the app |
| Netlify / Cloudflare Workers adapters | No | Routing decoupled for the same reason |
| Next.js ≥ 12.3.5 / 13.5.9 / 14.2.25 / 15.2.3 | Fixed | Header validated and filtered |
03How the bypass works, conceptually
Next.js read the x-middleware-subrequest header and, past a depth threshold, concluded that middleware had already executed and should be skipped to avoid an infinite loop. Because the check did not distinguish an internal marker from a client-supplied one, a request arriving from the internet with that header set would satisfy the condition and bypass middleware. In later versions the depth was a small fixed number, so a short, static header value was enough — there was nothing to brute-force.
The impact depended entirely on what the middleware did. If it only rewrote URLs or set headers, the bypass was mostly cosmetic. If it enforced authentication or authorisation — the common pattern the advisory warned against — then skipping it exposed whatever it was protecting. That is why the fix came with the standing advice that middleware should never be the sole line of defence.
04How to check if you are affected
Confirm two things: the resolved Next.js version is at or above the fix for your line, and whether you self-host the server runtime. If you are below the fix and running next start or a standalone output, and any of your middleware performs authorisation, assume you are exploitable until patched.
# Resolved version, not the range in package.json
node -p "require('next/package.json').version"
npm ls next 2>/dev/null
# Are you self-hosting the server runtime?
grep -E '"(build|start)"|output' package.json next.config.* 2>/dev/null
# output: 'export' -> static, not affected
# next start / standalone -> affected if version < fix05How to fix it: patch and defend in depth
Upgrade Next.js to 15.2.3, 14.2.25, 13.5.9 or 12.3.5, whichever matches your line. The patched versions validate the internal header and filter it out when it fails validation, so an external client can no longer use it to skip middleware. Treat those as minimums for this CVE only: Next.js published further middleware and proxy bypass advisories in 2026, such as CVE-2026-44573 and CVE-2026-45109, that were fixed only in the 15.5.x and 16.x lines, so the safe target is the newest release of a supported line. Then fix the design issue the bug exposed: enforce authorisation where the data is served, not only in middleware.
- Upgrade to the fixed release for your major line, and pick up CVE-2025-30218's fix at the same time
- Move authentication and authorisation checks into route handlers, server actions and the data layer — the places that actually return data
- Treat middleware as an optimisation and a first filter, never as your only gate
- As an immediate, independent mitigation, strip the x-middleware-subrequest header at your reverse proxy or WAF
06An edge rule that genuinely helps
Most of the vulnerabilities on these pages cannot be meaningfully filtered at the edge. This one can, which makes it the clearest example of a virtual patch that works. Because the attack depends on a single, specific request header that ordinary browsers never send, stripping or rejecting that header at a reverse proxy in front of Next.js stops the bypass — and it is exactly the mitigation the vendor recommended for teams that could not upgrade immediately. One caveat: Next.js attaches the header to fetch requests made from middleware, so a reject rule can break middleware that calls an API or auth service behind the same proxy, and stripping removes the loop guard if middleware fetches its own site through it. Cloudflare made its managed rule opt-in for that reason, after sites using third-party auth vendors saw failing requests. Run a new rule in log-only mode first.
You have two clean options at the edge: refuse any external request that carries the header, or quietly drop the header before forwarding so the application never sees a client-supplied value. The nginx snippet below shows both; it was validated with nginx -t and checked functionally against a loopback upstream. It is a genuine stopgap, but it is still a stopgap — upgrade Next.js and move authorisation into your handlers so you are not relying on a single header rule.
server {
# Option A: refuse any request that arrives carrying the internal header.
if ($http_x_middleware_subrequest) {
return 403;
}
location / {
proxy_pass http://nextjs_upstream; # your upstream {} block
proxy_http_version 1.1;
# proxy_set_header here replaces any inherited from server/http: repeat them.
proxy_set_header Host $host;
# Option B: never forward the header, whatever the client sent.
# An empty value tells nginx not to pass the field upstream.
proxy_set_header X-Middleware-Subrequest "";
}
}07Disclosure timeline
The episode is a clean lesson in defence in depth. A single misplaced trust decision in a framework turned a convenience header into an authorisation bypass, and the systems that survived unscathed were the ones that did not rely on middleware alone. Pair your patch with real checks at the data layer, and keep an edge rule in place as cheap insurance.
| Date | Event |
|---|---|
| 27 Feb 2025 | Reported to the Next.js team via private disclosure |
| 14 Mar 2025 | Vulnerability confirmed; remediation begins |
| 17–18 Mar 2025 | 14.2.25 and 15.2.3 released; CVE issued |
| 21 Mar 2025 | CVE-2025-29927 made public |
| 22–23 Mar 2025 | 13.5.9 and 12.3.5 back-ports released |
Questions
Frequently asked questions
Sources
Sources and verification
The nginx header-strip and header-reject snippet was syntax-checked with nginx -t (nginx 1.22.1, with an upstream block added) and verified functionally against a loopback echo upstream (403 when the header is present; header absent upstream when stripped); the node -p and grep checks were run against a sample project with Node.js 22 on Debian 12, October 2026. npm ls was not executed.
- 01NVD — CVE-2025-29927 · NVD
- 02GHSA-f82v-jwr5-mffw — Authorization Bypass in Next.js Middleware · GitHub Security Advisories
- 03Postmortem on Next.js Middleware bypass · Vercel
- 04Next.js and the corrupt middleware (researcher write-up) · zhero;
- 05nginx proxy_set_header documentation · nginx.org
- 06New Managed WAF rule for Next.js CVE-2025-29927 (opt-in note) · Cloudflare
- 07GHSA-223j-4rm8-mrmf — x-middleware-subrequest-id may be leaked to external hosts (CVE-2025-30218) · GitHub Security Advisories
- 08GHSA-26hh-7cqf-hhc6 — Middleware / Proxy bypass via segment-prefetch routes (CVE-2026-45109) · GitHub Security Advisories
- 09GHSA-36qx-fr4f-26g5 — Middleware / Proxy bypass in Pages Router applications using i18n (CVE-2026-44573) · GitHub Security Advisories
Last updated