Nouvelle réglementation de cybersécurité 2026 en vigueur au Maroc. Obtenir un audit de conformité gratuit →
← Retour au blog
OWASP 2026-01-22 ⏱️ 12 min

Next.js Middleware Bypass (CVE-2025-29927): Why Authorization Belongs in the Route, Not the Edge

Next.js Middleware Bypass (CVE-2025-29927): Why Authorization Belongs in the Route, Not the Edge

In March 2025, a critical vulnerability was disclosed in Next.js: an attacker could bypass middleware by sending a single HTTP header. The flaw, tracked as CVE-2025-29927 and scored 9.1, affected applications that implemented authentication or authorization checks in Next.js middleware. The lesson is larger than the patch. Middleware is a routing and request-processing layer, not an authorization boundary, and treating it as one turns a convenience feature into a single point of failure.


1. What Next.js Middleware Does

Middleware runs before a request is completed. It can rewrite, redirect, add headers, or reject a request. A common pattern is:

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('session')?.value;
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*']
};

This looks like an access control gate. It is not. It is a request filter that runs in the same process as the application, and it can be skipped if the framework believes the request is an internal subrequest.


2. The Internal Subrequest Header

Next.js uses an internal header, x-middleware-subrequest, to prevent infinite recursion when middleware triggers a fetch that re-enters the application. The header value encodes the middleware path, repeated to a depth limit. In affected versions, an external request that supplied the expected value was treated as an internal subrequest and skipped middleware entirely.

# Illustration against an affected, authorized test instance
curl -i https://target.example.com/dashboard \
  -H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware'

The exact value depends on the Next.js version and project layout (for example, src/middleware when the file lives under src/). The depth limit is five. If the header matches, the middleware function does not execute, and the request proceeds directly to the route handler or page.


3. Affected Versions and Fixes

  • Next.js 12: affected before 12.3.5; fixed in 12.3.5.
  • Next.js 13: affected before 13.5.9; fixed in 13.5.9.
  • Next.js 14: affected before 14.2.25; fixed in 14.2.25.
  • Next.js 15: affected before 15.2.3; fixed in 15.2.3.

The advisory is GHSA-f82v-jwr5-mffw. The fix prevents external requests from supplying the internal header, so middleware cannot be skipped by a client. Upgrading is necessary but not sufficient if the application still relies on middleware as its only authorization control.


4. Why the Impact Is Severe

Many Next.js applications use middleware for exactly the checks that matter most:

  • Redirecting unauthenticated users away from /dashboard.
  • Blocking /admin unless the session contains an admin role.
  • Enforcing tenant isolation by rewriting requests to a tenant-specific backend.
  • Adding security headers or rejecting requests from blocked regions.

If middleware is skipped, the page or route handler still executes. A dashboard page that fetches data server-side may render the victim's data to an unauthenticated requester. An admin API route that assumed middleware had already checked the role may perform privileged actions. The vulnerability does not grant a session; it removes the check that was supposed to require one.


5. Testing Safely

Only test applications you own or are authorized to assess. A minimal verification sequence:

  1. Request a protected route without a session and confirm the expected redirect or 401/403.
  2. Repeat the request with the x-middleware-subrequest header and the version-appropriate value.
  3. Compare status codes, response bodies, and Location headers.
  4. If the protected content appears, confirm that the response contains data the unauthenticated user should not see.
  5. Check whether the route handler independently verifies the session. If it does, the bypass may be limited to page rendering; if it does not, the impact is broader.
# Baseline
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' https://target.example.com/admin

# Probe (authorized testing only)
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' \
  -H 'x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware' \
  https://target.example.com/admin

6. The Durable Fix: Authorize in the Data Layer

Middleware can remain useful for redirects, locale detection, and header manipulation. It should not be the only place where a security decision is made. Put the authorization check next to the data access:

// app/api/invoices/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getSession } from '@/lib/auth';
import { db } from '@/lib/db';

export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
  const session = await getSession(req);
  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const invoice = await db.invoice.findFirst({
    where: { id: params.id, tenantId: session.tenantId }
  });

  if (!invoice) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 });
  }

  return NextResponse.json(invoice);
}

This handler is safe even if middleware is bypassed, misconfigured, or removed. The session check and the tenant filter are in the same function that returns the data. That is the property to aim for: defense in depth, with the authoritative check at the point of use.


7. Additional Hardening

  • Block the header at the edge. As a temporary mitigation for unpatched deployments, strip or reject x-middleware-subrequest from all external requests at the reverse proxy or CDN.
  • Patch all Next.js instances. Include self-hosted deployments, preview environments, and internal apps. A staging instance on an old version is still an entry point.
  • Inventory middleware usage. Search for middleware.ts, middleware.js, and src/middleware. For each matcher, ask: what happens if this function never runs?
  • Add regression tests. Test protected routes without a session, with an expired session, with a different tenant, and with the internal header present.
  • Log authorization decisions. Record the authenticated subject, the resource, and the decision. A sudden cluster of 200 responses on protected routes without a session is a detection opportunity.

8. Conclusion

CVE-2025-29927 is a reminder that frameworks optimize for developer experience, not for security invariants. Middleware is an elegant place to express routing rules, but it is a fragile place to enforce authorization. The header that caused the bypass was an internal implementation detail; the real vulnerability was the assumption that a request filter could not be skipped. Patch the framework, block the header, and move the authoritative check to the function that actually reads or mutates the protected resource. Then a bypassed middleware layer becomes a missed redirect, not a data breach.

Besoin d'un audit de sécurité ?

Contactez Cayvora pour une consultation gratuite et protégez votre entreprise contre les cybermenaces.

✉️ Contact via Email

Articles connexes