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.
// 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.
// 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.
// 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.
// 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.
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.
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.
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.
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