• Agents
  • Pricing
  • Blog
Log in
Get started

Security for apps built with AI. Paste a URL, get a report, fix what matters.

Product

  • How it works
  • What we find
  • Pricing
  • Agents
  • MCP Server
  • CLI
  • GitHub Action

Resources

  • Guides
  • Blog
  • Docs
  • OWASP Top 10
  • Glossary
  • FAQ

Security

  • Supabase Security
  • Next.js Security
  • Lovable Security
  • Cursor Security
  • Bolt Security

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • Imprint
© 2026 Flowpatrol. All rights reserved.
Back to Blog

Apr 1, 2026 · 12 min read

CVE-2025-29927: The Next.js Middleware Bypass That Broke Auth With One Header

A single HTTP header could skip every middleware check in Next.js — authentication, authorization, CSP, rate limiting, all of it. Here's exactly how CVE-2025-29927 works, who's affected, and what to do about it.

FFlowpatrol Team·Case Study
CVE-2025-29927: The Next.js Middleware Bypass That Broke Auth With One Header

One header to bypass every security check

In March 2025, a single HTTP header was enough to skip every middleware guard in Next.js. Authentication. Authorization. Rate limiting. CSP headers. All of it — bypassed with one curl command.

Here's the exploit:

curl -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" \
     https://your-app.com/admin/dashboard

That's it. The /admin/dashboard route loads as if middleware doesn't exist. No login required. No role checks. No redirects.

The vulnerability, CVE-2025-29927, scored 9.1 on the CVSS scale (Critical). It affected every Next.js version from 11.x through 15.x — spanning millions of production apps. If you rely on middleware for access control (and most Next.js builders do), you were exposed.

This article walks through exactly how the bypass works, why Next.js trusted the wrong thing, and what you need to do right now.


Are you affected? A 30-second check

Before diving into the technical details, check your app:

# Look for middleware.ts or middleware.js in your project root or src/ directory
grep -r "middleware" package.json
ls -la middleware.ts middleware.js 2>/dev/null || echo "No middleware file found"

# Check your Next.js version
grep '"next"' package.json | grep -oE '"\d+\.\d+'

If you're running Next.js 11–15.x and have a middleware.ts file defining auth checks, rate limiting, or other security rules — you were likely exposed until March 2025. Scroll to "Affected versions" below to see if your exact version is patched.


What Next.js middleware does (and why this matters)

Next.js introduced middleware in version 12 as a way to run code before a request completes. You write a middleware.ts file, and it executes on every matching route before the page or API handler runs.

Most teams use middleware for security-critical functions:

// middleware.ts — a typical auth guard
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("session-token");

  if (!token) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  // Verify the token, check roles, etc.
  const user = verifyToken(token.value);
  if (!user) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  // User is authenticated — allow the request
  return NextResponse.next();
}

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

This is the standard pattern. If you've ever built a Next.js app with protected routes, you've probably written something like this. It feels solid — every request to /dashboard, /admin, or /api/protected hits the middleware first, and unauthenticated users get redirected to login.

CVE-2025-29927 made all of it irrelevant.


How the bypass works

The recursion guard that trusted the client

Next.js uses an internal mechanism to prevent infinite loops when middleware calls other routes. It works like this:

When middleware needs to make a subrequest (like fetching data or checking another endpoint), Next.js attaches a header called x-middleware-subrequest to keep track of the recursion depth. If a request goes too deep, the framework stops running middleware to prevent infinite loops. This is a safety valve.

The logic is simple:

  1. Middleware runs on every request
  2. If middleware makes a subrequest, Next.js adds x-middleware-subrequest to it
  3. If that subrequest goes too deep, Next.js skips middleware and returns the response

The fatal flaw: Next.js never validated whether this header came from the framework itself or from an external attacker.

Diagram showing two request paths: normal requests pass through middleware, bypass requests skip it entirely
Diagram showing two request paths: normal requests pass through middleware, bypass requests skip it entirely

An attacker can forge the header on an external request, and Next.js will treat it like an internal subrequest and skip middleware entirely.

Why this matters

To understand the impact, look at how middleware-based auth actually protects applications:

// middleware.ts — the standard auth guard
export function middleware(request: NextRequest) {
  const token = request.cookies.get("session-token");
  if (!token) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
  return NextResponse.next(); // Auth passed, continue
}

The middleware runs on every request to /admin/*, /api/protected/*, etc. If middleware is skipped entirely, this check is skipped entirely. No authentication. No role validation. Nothing.

Craft the bypass for your version

The header value must match your Next.js directory structure. For Next.js 15.x (default structure):

curl -H "x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware" \
     https://your-app.com/admin/protected-route

For Next.js 14.x with src/ directory:

curl -H "x-middleware-subrequest: src/middleware:src/middleware:src/middleware" \
     https://your-app.com/admin

For pages router (older Next.js):

curl -H "x-middleware-subrequest: pages/_middleware" \
     https://your-app.com/api/sensitive

The response will be identical to a request that passed authentication — because middleware was never executed.



Why frameworks can't trust HTTP headers

This vulnerability reveals a fundamental principle: HTTP has no concept of "internal" traffic. Every header in a request — whether it came from Next.js internals or an attacker's laptop — arrives through the exact same channel. Without cryptographic validation or network-level isolation, a server cannot distinguish one from the other.

Next.js was using x-middleware-subrequest as an internal signal, but there was nothing internal about it. An attacker could forge it. Validate it. Spoof it. The framework had no way to know the difference.

This falls under two CWE classifications:

  • CWE-290: Authentication Bypass by Spoofing
  • CWE-285: Improper Authorization

The fix in patched versions validates that the header actually came from an internal subrequest before trusting it. External requests can no longer trigger the recursion limit bypass.


What an attacker could do

The impact depends on what your middleware protects. For most Next.js apps, that means everything:

Authentication bypass. Access any protected page without logging in. Admin panels, user dashboards, internal tools — if the auth check lives in middleware, it's gone.

Authorization bypass. Even authenticated users have roles and permissions enforced in middleware. An attacker can escalate from a regular user to an admin, or access data they shouldn't see. (This is closely related to IDOR — another access control flaw that's rampant in AI-generated code.)

CSP bypass. Many apps set Content Security Policy headers in middleware. Bypassing middleware means the response comes back without CSP, opening the door to cross-site scripting attacks.

Rate limiting bypass. Middleware-based rate limiters stop working. Brute-force attacks against login endpoints, API abuse, scraping — all become possible without throttling.

Cache poisoning. If a CDN caches the response from a bypassed request, the poisoned response (without security headers, without auth checks) can be served to legitimate users.

Real attack chains

These aren't theoretical. Here's how an attacker would chain this:

Chain 1: Admin panel takeover
1. Identify the app runs Next.js (check for /_next/ paths)
2. Send GET /admin with the bypass header
3. Access the admin dashboard without credentials
4. Modify settings, export data, create backdoor accounts

Chain 2: Data exfiltration via API
1. Bypass auth middleware on /api/* routes
2. Hit user data endpoints directly
3. Enumerate and download sensitive records
4. No authentication logs generated (middleware never ran)

Chain 3: XSS via CSP bypass
1. Bypass middleware that sets CSP headers
2. Response arrives without Content-Security-Policy
3. Exploit a stored or reflected XSS vector
4. Steal session tokens, credentials, or user data

The third chain is particularly nasty. Even if you have other XSS mitigations, losing CSP removes a critical defense layer.


Affected versions

Every major version branch was vulnerable:

VersionVulnerablePatched
15.x< 15.2.315.2.3
14.x< 14.2.2514.2.25
13.x< 13.5.913.5.9
12.x< 12.3.512.3.5
11.x and earlierAll versionsNo patch (EOL)

If you're running Next.js 11 or earlier, there is no fix. You need to upgrade or mitigate at the network layer.


The timeline

The disclosure and response moved fast:

DateEvent
February 27, 2025Vulnerability reported to Next.js team via GitHub
March 17, 2025Next.js 14.2.25 released with fix
March 18, 2025Next.js 15.2.3 released with fix
March 18, 2025CVE-2025-29927 assigned
March 21, 2025Public disclosure and PoC exploits published
March 22, 2025Mass scanning observed in the wild

Note the gap: PoC code was public on March 21, and mass scanning started the next day. If you weren't patched by March 22, automated tools were already probing your app.


What you should do right now

1. Update Next.js

This is the primary fix. Update to the patched version for your branch:

# Latest (recommended)
npm install next@latest

# Or pin to a specific patched version
npm install next@15.2.3    # for 15.x
npm install next@14.2.25   # for 14.x
npm install next@13.5.9    # for 13.x
npm install next@12.3.5    # for 12.x

After updating, test your middleware. Make sure auth redirects still work, CSP headers are still set, and your protected routes are still protected.

2. Strip the header at your proxy (if you can't patch immediately)

If you're behind Nginx, Apache, or a reverse proxy, you can strip the dangerous header before it reaches Next.js:

# Nginx — strip the header from all incoming requests
proxy_set_header x-middleware-subrequest "";

Or block requests that include it entirely:

# Nginx — reject requests with the header
if ($http_x_middleware_subrequest) {
    return 403;
}

For Cloudflare, create a WAF rule:

Rule: (http.request.headers["x-middleware-subrequest"] exists)
Action: Block

3. Add detection logging

Even after patching, you should log attempts. This helps you understand if anyone was trying to exploit this before you patched:

// Add to the top of your middleware.ts
export function middleware(request: NextRequest) {
  const bypassHeader = request.headers.get("x-middleware-subrequest");
  if (bypassHeader) {
    console.warn("CVE-2025-29927 exploit attempt detected:", {
      ip: request.ip,
      path: request.nextUrl.pathname,
      header: bypassHeader,
      timestamp: new Date().toISOString(),
    });
    return new NextResponse("Forbidden", { status: 403 });
  }

  // ... rest of your middleware
}

4. Stop relying on middleware as your only security layer

This is the bigger lesson. Middleware is a convenience layer, not a security boundary. It's valuable for redirects, setting headers, and adding a first line of defense. But it should never be the only place you check authentication.

Add auth checks in your API routes:

// app/api/protected/route.ts
import { getServerSession } from "next-auth";

export async function GET(request: Request) {
  // Server-side auth check — independent of middleware
  const session = await getServerSession();
  if (!session) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  // ... handle the authenticated request
}

Add auth checks in your server components:

// app/admin/page.tsx
import { redirect } from "next/navigation";
import { getServerSession } from "next-auth";

export default async function AdminPage() {
  const session = await getServerSession();
  if (!session || session.user.role !== "admin") {
    redirect("/login");
  }

  return <AdminDashboard />;
}

These server-side checks run regardless of middleware. Even if middleware is bypassed — through this CVE or a future one — the route itself still enforces access control.


Defense in depth: the pattern that saves you

The Moltbook breach (Supabase RLS disabled), the Lovable platform vulnerability (no RLS on AI-generated apps), and now CVE-2025-29927 all share a root cause: a single security layer was the only security layer.

The pattern that protects against this entire class of problems is defense in depth:

LayerWhat it doesNext.js example
Edge/CDNStrip dangerous headers, block known attack patternsCloudflare WAF rule, Nginx header stripping
MiddlewareFirst-pass auth checks, redirects, security headersmiddleware.ts with session validation
Route handlerServer-side auth verification per endpointgetServerSession() in API routes
DatabaseRow-level access controlSupabase RLS, Prisma middleware

If any single layer fails, the others still hold. That's the point. Security isn't about having one perfect lock — it's about having locks at every door.


For AI-built apps: middleware is not enough

If you built your Next.js app with Cursor, Lovable, Bolt, or v0, your AI assistant probably generated middleware-based auth. It's the documented pattern. It's what every tutorial shows. It's what LLMs have been trained on.

But CVE-2025-29927 is a reminder: a single recommended pattern — even when it's the framework's own pattern — is not a security boundary. The middleware guards are valuable. They're just not complete.

When an AI generates middleware.ts with auth checks, it's not also thinking about defense in depth. It's not adding redundant server-side checks in every API route. It's not saying "and also configure your CDN to strip this header."

The fix is easy: Ask your AI to add server-side auth checks alongside the middleware. Two layers are harder to bypass than one.

// Ask your AI to add this in ADDITION to middleware auth
export async function GET(request: Request) {
  // Check auth server-side, independent of middleware
  const session = await getServerSession();
  if (!session) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }
  // ... rest of handler
}

Middleware remains useful — it's fast, it can redirect early, it can set headers. But it shouldn't be your only lock on the door.


Scan for this before you ship

CVE-2025-29927 is a textbook case for what automated scanning catches. Here's how Flowpatrol detects it:

  1. Fingerprint Next.js — the /_next/ path structure, response headers, and build artifacts are reliable signals
  2. Test the bypass directly — send requests with x-middleware-subrequest headers at different recursion depths and compare responses
  3. Verify auth enforcement — check whether protected routes actually require authentication at the server level, not just at middleware
  4. Check for defense in depth — verify that multiple layers exist, so a single bypass doesn't compromise everything

Paste your Next.js app's URL. Get results in minutes. If you're vulnerable, you see exactly which routes bypass auth. If you're patched, we confirm it. That certainty — knowing you're solid before you launch — is what matters.

Remember: This CVE affected every Next.js version 11–15.x. If your app deployed before March 18, 2025 and you haven't updated, this is the first thing to check.


CVE-2025-29927 is documented in detail by Vercel's postmortem, ProjectDiscovery's technical analysis, Datadog Security Labs, and the NVD entry.

Back to all posts

More in Case Study

The app making $100K a month had no auth middleware. It took 2 minutes to find out.
Apr 30, 2026

The app making $100K a month had no auth middleware. It took 2 minutes to find out.

Read more
Lovable Builds Your App. For 48 Days, Anyone on Lovable Could Read It.
Apr 30, 2026

Lovable Builds Your App. For 48 Days, Anyone on Lovable Could Read It.

Read more
The AI Took 9 Seconds. The Recovery Took 30 Hours.
Apr 30, 2026

The AI Took 9 Seconds. The Recovery Took 30 Hours.

Read more