Back to Blog
Security29 April 2025Michael Hettwer5 min read

Next.js CVE-2025-29927: what we checked in our deployments afterwards

Middleware authorization bypass in Next.js: what we checked in our deployments afterwards, and how the fix belongs in the patch routine.

In March 2025 a middleware authorization bypass was published for the Next.js framework, recorded as CVE-2025-29927 on 2025-03-21. The short version: applications that perform their authorization check inside middleware could be bypassed by a crafted request carrying the x-middleware-subrequest header. This post is our follow-through after the disclosure: what we checked on our own deployments, what the fix really demands, and the operating rule we took away.

The vulnerability, in plain terms

Next.js runs middleware code on the edge of the request pipeline, exactly where a lot of applications put "is this user allowed in here?" checks. When such a check lives inside middleware and the framework skips middleware execution for requests that look like internal sub-requests, an attacker can reach the application's pages with the gatekeeper absent. The published mitigation for unrepatchable deployments is equally plain: prevent external requests carrying the x-middleware-subrequest header from reaching the application. The affected version ranges and the fixed versions, 12.3.5, 13.5.9, 14.2.25, and 15.2.3, are listed in the advisory the CVE record points to.

What makes this class of bug instructive is not the header but the architecture. Defense that lives in one framework layer, invisible to the application code behind it, produces the recurring temptation: the check exists somewhere, so the deployment "handles auth". The verification questions change accordingly.

What we checked on our own deployments

Our Next.js deployments are few and self-hosted, which is both a comfort and a duty. The walk went as follows:

  • Inventory first: which deployments run Next.js at all, and at which versions. For us this meant three projects, two of them long-lived. Version inventory took minutes because the state lives in manifest files; where it had been missing, it is now part of the repository.
  • Middleware usage: does the application place authorization inside middleware? Two deployments did not use middleware for that purpose at all; one did, exactly in the classic pattern. That one got priority.
  • Patch: the affected deployment went to the fixed version in the same week, after the staging pass. The staging pass exists because framework upgrades have a way of surfacing unrelated breakage, and an emergency patch that breaks the checkout flow is not a fix, it is a second incident. The staging pass also became the place where the mitigation rule was rehearsed once, so the emergency path was not being learned during the emergency.
  • Mitigation check: for the days between disclosure and patch, the reverse-proxy rule dropping external requests with that header was in place. It is a five-line change and it buys real time; we verified it with a manual request rather than assuming.

One honest confession: our inventory was not perfect before this disclosure. One project's framework version was "whatever the lockfile says", which turns out to be an answer, not an inventory. Writing the versions down, in the repo where the app lives, took a quarter of an hour and made the next disclosure cheaper.

What the fix really demands

The patch itself was uneventful, and that is the good outcome of a fast-moving disclosure: framework maintainers ship fixes, distributors rebuild, and the job of the operator is to be patchable. Being patchable means three dull things: knowing what you run, being able to build from source, and having a staging path that does not itself require heroics. None of these are framework-specific; they are the same muscle that any other service needs for a patch routine, from the kernel to the print stack.

The framework's own release cadence reinforces the point. Minor versions arrive quickly, and a deployment riding three minors behind is not a stable choice but a stack of unresolved patch debt. Our cadence after this event: framework upgrades run through the same window as package updates, with the staging pass sized to a fixed afternoon instead of an open-ended effort. Knowing the budget in advance turned an intimidating category into a schedulable one.

The other demand is architectural. A middleware check is convenient, but the applications that fared best in our quick review were those whose authorization is enforced at the resource as well. A route gate plus a resource-level check survives the day when one layer's assumption breaks, framework-level or not.

The rule we took away

When a framework keeps the auth check in the middleware layer, running and patching that layer belongs in the same patch routine as any other service we operate, with an inventory entry, a fixed cadence, and a tested emergency mitigation we can apply in minutes. Security updates in the framework layer used to feel like an application team's concern; this disclosure settled that question for us.

The critical security updates post covers the same theme from the operating side, and if you want an outside pair of eyes on your own deployment inventory, the contact page is the direct way to start that conversation.

Sources

  • NVD entry CVE-2025-29927 (Next.js middleware authorization bypass), https://nvd.nist.gov/vuln/detail/CVE-2025-29927 (accessed 2026-11-03)
  • Next.js security advisory GHSA-f82v-jwr5-mffw (affected ranges, fixed versions, mitigation), https://github.com/vercel/next.js/security/advisories/GHSA-f82v-jwr5-mffw (accessed 2026-11-03)