SecureStartKit
SecurityFeaturesPricingDocsBlogChangelog
Sign inBuy Now
Home/Security/Next.js Middleware Auth Bypass
CWE-285Critical severityNext.js

Next.js Middleware Auth Bypass

◐SecureStartKit uses the database as the authorization boundary, so a middleware bypass does not expose data. You must still patch Next.js and verify your handler-level session checks.

Last reviewed June 13, 2026 by SecureStartKit Team

The short answer

CVE-2025-29927 allowed any attacker to set the x-middleware-subrequest header and cause Next.js to skip middleware entirely. Any app that enforced authorization only inside middleware was exposed. The fix is two parts: upgrade to Next.js 14.2.25 or 15.2.3 (or later), and enforce authorization at the data layer and inside route handlers, not only in middleware.

Where it shows up: Authorization is implemented only in middleware.ts or proxy.ts, with no second check at the data layer or inside the Server Action, route handler, or page component.

The vulnerable patterns and their fixes

Auth enforced only in middleware (vulnerable to the header bypass)

✗Vulnerablets
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
import { createServerClient } from '@supabase/ssr'

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const supabase = createServerClient(/* ... */)
  const { data } = await supabase.auth.getClaims()

  // The ONLY authorization check is here.
  if (!data?.claims) {
    return NextResponse.redirect(new URL('/login', req.url))
  }
  return res
}

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

All authorization lives in middleware. An attacker sets x-middleware-subrequest, Next.js skips the middleware function, the check never runs, and the protected route is served without authentication.

↓the fix
✓Securets
// app/dashboard/page.tsx - the real gate, re-checked in the handler
import { redirect } from 'next/navigation'
import { getClaims } from '@/lib/auth/get-claims'
import { createAdminClient } from '@/lib/supabase/server'

export default async function DashboardPage() {
  // getClaims verifies the JWT server-side. A middleware bypass
  // does not bypass this check.
  const claims = await getClaims()
  if (!claims) redirect('/login')

  const supabase = createAdminClient()
  // RLS scopes the query to the owner as a third, independent layer.
  const { data } = await supabase
    .from('user_data')
    .select('*')
    .eq('user_id', claims.sub)

  return <pre>{JSON.stringify(data)}</pre>
}

getClaims verifies the session JWT on the server. Even if middleware is skipped entirely, an unauthenticated request fails here and is redirected. Row Level Security at the database adds an independent layer. Middleware is left only for optimistic redirects.

Patch the framework: upgrade to a fixed Next.js version

✗Vulnerablejson
// package.json - vulnerable version range
{
  "dependencies": {
    "next": "15.1.0"
  }
}

Any version below 14.2.25 on the 14.x line, or below 15.2.3 on the 15.x line, ships the unvalidated x-middleware-subrequest header and is exploitable regardless of your authorization architecture.

↓the fix
✓Securejson
// package.json - patched version
{
  "dependencies": {
    "next": "15.2.3"
  }
}

Upgrading closes the specific header bypass. Pair the upgrade with the architectural fix in the first case: patching alone does not change a design that trusts middleware as the only authorization boundary.

SecureStartKit ships these defenses by default. RLS, Zod-validated Server Actions, and verified webhooks, already wired in.

Get SecureStartKit→

How it’s exploited

Next.js uses an internal header, x-middleware-subrequest, to track recursive middleware calls and prevent infinite loops. In versions before 14.2.25 and 15.2.3, this header was never validated or stripped on incoming requests from the outside world.

An attacker sending a request with x-middleware-subrequest set to a value such as middleware would cause Next.js to treat the request as an internal subrequest and skip middleware execution altogether. The middleware function never ran, so any redirect, session check, or role gate placed inside it was silently bypassed.

The attack requires no credentials, no special tooling, and no knowledge of the application internals. A single curl command with one extra header was enough to reach any route that relied on middleware as its only authorization gate. Public routes were unaffected. The damage was proportional to how much trust the application placed in middleware as a security boundary.

How to find it in your code

Open middleware.ts or proxy.ts and inspect every route listed in the matcher config. For each matched route, confirm that a route handler, page component, or Server Action independently verifies the session. If a protected route only has a check inside middleware, it was vulnerable before patching and remains structurally fragile.

Grep your pages and route handlers for getClaims, getUser, or getSession. A page under a protected matcher with none of these is relying entirely on middleware. That is the pattern to fix.

Verify your Next.js version with next --version or by reading package.json. Any version below 14.2.25 on the 14.x line, or below 15.2.3 on the 15.x line, is unpatched and must be upgraded regardless of your authorization architecture.

Common mistakes

  • Myth“Middleware is a valid security boundary for protecting routes.”

    Middleware is designed for optimistic redirects and edge-level UX logic. CVE-2025-29927 proved why it is not an authorization boundary: a single header was enough to skip it entirely. Authorization must live at the data layer and inside handlers.

  • Myth“Upgrading Next.js to a patched version is all that is needed.”

    Patching fixes the header bypass for this CVE, but it does not change the underlying structural risk. If all authorization lives in middleware, the next bypass (a proxy misconfiguration, a future CVE) exposes you again. Upgrade and fix the architecture.

  • Myth“Row Level Security alone is enough, so I do not need a session check in the handler.”

    RLS is a strong last line of defense, but only when the correct role is active. If your handler uses the service_role client (which bypasses RLS) without re-verifying the session, a middleware bypass hands the attacker the full service_role query surface. Pair RLS with a server-side session check in sensitive handlers.

  • Myth“This only affects self-hosted setups, not standard Vercel deployments.”

    CVE-2025-29927 affected all Next.js deployments, including Vercel. The vulnerability was in the Next.js runtime itself. All affected versions needed patching regardless of where the app ran.

Does SecureStartKit prevent this?

The kit enforces authorization through Row Level Security at the database and by calling getClaims inside Server Actions and route handlers. Middleware is used only for optimistic redirects, so a bypass does not reach protected data. You must still upgrade Next.js to 14.2.25 or 15.2.3 (or later) to close the CVE, and you should audit any handler that uses the service_role client to confirm it re-checks the session before acting on user input.

How the kit protects routes→

Frequently asked questions

Which Next.js versions are affected by CVE-2025-29927?
All versions before 14.2.25 on the 14.x line and before 15.2.3 on the 15.x line. Backport patches were released for older supported minor versions. Check the GitHub advisory for the full list and upgrade immediately if you are on an older version.
Can I block the x-middleware-subrequest header at the CDN instead of upgrading?
Stripping the header at the edge (Vercel firewall rules, nginx, Cloudflare) is a valid short-term mitigation if an immediate upgrade is not possible. It is not a permanent fix. You should still upgrade Next.js and address any structural reliance on middleware for authorization.
Does Vercel automatically protect against this at the platform level?
Vercel stripped the x-middleware-subrequest header at the edge for all deployments shortly after disclosure. That mitigation reduced exposure for Vercel-hosted apps, but it does not excuse skipping the patch, and it does not help developers running Next.js on other infrastructure.
If I call getClaims in every protected page, do I still need to upgrade?
Yes. The architectural fix reduces the blast radius if middleware is bypassed, but the CVE is a published vulnerability with a public proof of concept. Running an unpatched version signals to scanners that your infrastructure is behind. Upgrade regardless of your authorization architecture.

References

  • GitHub Advisory GHSA-f82v-jwr5-mffw (CVE-2025-29927) ↗
  • CWE-285: Improper Authorization ↗
  • Next.js Middleware documentation ↗

Related weaknesses

  • Missing or Disabled RLS PolicyA table holding user data has RLS disabled, or has a policy whose USING expression is not scoped to the current user (for example USING (true)), allowing the anon or authenticated role to read or modify every row.
  • IDOR: Missing Ownership CheckA Server Action or route handler reads or writes a record using createAdminClient() with only an id filter and no ownership filter. Because service_role skips Row Level Security, any authenticated user can access any row by supplying an arbitrary id.
  • Unvalidated Server Action InputA Server Action reads FormData fields or typed arguments and passes them directly to a database query, or spreads them with the spread operator, without first running them through a Zod schema.

Defined terms

  • Server Actions
  • getClaims
  • Supabase SSR

Go deeper

  • Next.js proxy.ts Authentication with Supabase

Ship these defenses by default

SecureStartKit is a Next.js, Supabase, and Stripe starter with Row Level Security, Zod-validated Server Actions, verified Stripe webhooks, and backend-only data access already wired in. Start from a secure baseline instead of hardening by hand.

Get SecureStartKit→Browse all patterns
← Back to all security patterns