← All guides

Next.js Middleware Auth Bypass (CVE-2025-29927): Am I Affected?

Updated 2026-10-22

Short answer: CVE-2025-29927 let attackers skip Next.js middleware entirely by sending an x-middleware-subrequest header. If your app protects pages or API routes only in middleware.ts, an attacker could reach them without logging in. You're affected if you self-host (for example next start or output: "standalone" on a VPS, Docker, Fly.io, Railway or Render) on Next.js 11.1.4 through 13.5.8, 14.x before 14.2.25, or 15.x before 15.2.3. Upgrade to 12.3.5, 13.5.9, 14.2.25, 15.2.3 or later, and add auth checks next to your data so a middleware problem can't expose it again. Apps hosted on Vercel or Netlify, and static exports, were not affected.

The vulnerability was disclosed in March 2025, but it still turns up in AI-built apps because templates and starter repos pin old Next.js versions, and because tools like Cursor, v0 and Bolt very often put all of the auth logic in middleware.

How the bypass worked

Next.js uses an internal header, x-middleware-subrequest, to stop middleware from calling itself in an infinite loop. When middleware makes a request that would run middleware again, Next.js adds this header, and if it sees the header with enough repetitions it skips middleware for that request.

The bug: Next.js trusted that header on incoming requests from the internet too. An attacker could send it with the right value, and your middleware simply didn't run. Whatever middleware was doing (redirecting logged-out users, checking roles, setting security headers, blocking countries) was skipped.

A typical vulnerable setup looks like this:

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

export function middleware(req: NextRequest) {
  const token = req.cookies.get("session")?.value;
  if (!token) return NextResponse.redirect(new URL("/login", req.url));
  return NextResponse.next();
}

export const config = { matcher: ["/dashboard/:path*", "/admin/:path*", "/api/admin/:path*"] };
// app/api/admin/users/route.ts
export async function GET() {
  // No auth check here, because "middleware handles it"
  const users = await db.user.findMany();
  return Response.json(users);
}

With the bypass, /api/admin/users returns every user to anyone.

☐ 1. Check your Next.js version

npm ls next
# or
grep '"next"' package.json package-lock.json | head

You need at least one of these, depending on your major version:

Major Fixed in
15.x 15.2.3
14.x 14.2.25
13.x 13.5.9
12.x 12.3.5

Versions before 11.1.4 didn't have the vulnerable behavior in this form, but they're long out of support and have other issues, so upgrade them too.

Check the lockfile, not just package.json. A range like "next": "^14.1.0" in package.json can still resolve to an old version in package-lock.json if nobody has reinstalled.

☐ 2. Check how you're hosted

You were exposed if your app runs with the Next.js server itself facing traffic:

You were not affected if:

If you use another platform or adapter (for example Cloudflare through OpenNext), check that platform's own advisory for CVE-2025-29927, and still upgrade Next.js.

☐ 3. Upgrade

# pick the line that matches your major version
npm install next@15 --save    # gets the latest 15.x
npm install next@14 --save    # gets the latest 14.x

Then rebuild and redeploy. Run your app locally and click through login, logout and any protected pages, since minor version bumps occasionally change behavior.

☐ 4. If you can't upgrade today, strip the header

As a stopgap, drop x-middleware-subrequest from incoming requests before they reach Next.js. In nginx:

location / {
  proxy_set_header x-middleware-subrequest "";
  proxy_pass http://localhost:3000;
}

Many CDNs and WAFs, including Cloudflare, shipped rules for this header after disclosure. Treat this as temporary. The upgrade is the fix.

☐ 5. Test it yourself

On your own app, with an old version, a request like this shows whether middleware can be skipped. The header value Next.js checked differed by version; for 15.x it was the middleware name repeated five times:

# should redirect to /login (307) if middleware runs
curl -i https://your-app.example.com/dashboard

# if this returns 200 with dashboard content, you're vulnerable
curl -i https://your-app.example.com/dashboard \
  -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware"

If your middleware lives in src/, the name is src/middleware. After upgrading, both requests should behave the same. Only test apps you own.

☐ 6. Move auth next to the data

The deeper lesson is that middleware is a convenience layer, not a security boundary on its own. Next.js's own guidance is to verify the user close to where data is read or changed. Every protected route handler, Server Action and server component that loads private data should check the session itself:

// lib/auth.ts
import { cookies } from "next/headers";
export async function requireUser() {
  const token = (await cookies()).get("session")?.value;
  const user = token ? await verifySession(token) : null;
  if (!user) throw new Response("Unauthorized", { status: 401 });
  return user;
}
// app/api/admin/users/route.ts
import { requireUser } from "@/lib/auth";
export async function GET() {
  const user = await requireUser();
  if (user.role !== "admin") return new Response("Forbidden", { status: 403 });
  return Response.json(await db.user.findMany());
}

Keep middleware for redirects and nice UX. Put the real decision in the handler. The guide to securing Next.js API routes and Next.js Server Actions security walk through this pattern in more detail.

If you use Supabase, the database is another layer: with Row Level Security turned on and the anon key used for user requests, a skipped middleware check still can't read other users' rows.

☐ 7. Look for signs it was used

If you were on a vulnerable version and self-hosted, search your reverse proxy or app logs for requests containing x-middleware-subrequest. Real users never send it. If you find hits on protected paths, treat it as a possible data exposure: review what those routes return, rotate any secrets they could reveal (see how to rotate leaked Supabase and OpenAI keys), and consider whether you need to tell affected users.

Related guides

Check your repo

PreflightPR checks AI-built apps for API routes without auth checks, leaked keys, missing Supabase RLS and other launch blockers. Run a free scan on a public repo for a launch score and a prioritized fix list, or install the PreflightPR GitHub App to review every pull request before it merges. It's an automated best-effort review, so still check your Next.js version and run the curl test above on your own deployment.

Check your repo in about a minute

Paste a public GitHub URL and get a free launch score.

Run a free scan

More guides