Security context
Critical· 9.1GHSA-f82v-jwr5-mffw CVE-2025-29927CWE-285CWE-863Published Mar 21, 2025

Authorization Bypass in Next.js Middleware

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

13.0.0 → fixed in 13.5.914.0.0 → fixed in 14.2.2515.0.0 → fixed in 15.2.312.0.0 → fixed in 12.3.5

Details

# Impact It is possible to bypass authorization checks within a Next.js application, if the authorization check occurs in middleware. # Patches * For Next.js 15.x, this issue is fixed in `15.2.3` * For Next.js 14.x, this issue is fixed in `14.2.25` * For Next.js 13.x, this issue is fixed in 13.5.9 * For Next.js 12.x, this issue is fixed in 12.3.5 * For Next.js 11.x, consult the below workaround. _Note: Next.js deployments hosted on Vercel are automatically protected against this vulnerability._ # Workaround If patching to a safe version is infeasible, we recommend that you prevent external user requests which contain the `x-middleware-subrequest` header from reaching your Next.js application. ## Credits - Allam Rachid (zhero;) - Allam Yasser (inzo_)

The fix

Update middleware request header (#77201)

JJ Kasper· Mar 17, 2025, 08:56 PM+35452a078da38
packages/next/src/server/lib/router-server.ts+7 4
@@ -166,10 +166,13 @@ export async function initialize(opts: {
renderServer.instance =
require('./render-server') as typeof import('./render-server')
- const allowedOrigins = [
- 'localhost',
- ...(config.experimental.allowedDevOrigins || []),
- ]
+ const randomBytes = new Uint8Array(8)
+ crypto.getRandomValues(randomBytes)
+ const middlewareSubrequestId = Buffer.from(randomBytes).toString('hex')
+ ;(globalThis as any)[Symbol.for('@next/middleware-subrequest-id')] =
+ middlewareSubrequestId
+
+ const allowedOrigins = ['localhost', ...(config.allowedDevOrigins || [])]
if (opts.hostname) {
allowedOrigins.push(opts.hostname)
}
packages/next/src/server/lib/server-ipc/utils.ts+11 0
@@ -57,5 +57,16 @@ export const filterInternalHeaders = (
if (INTERNAL_HEADERS.includes(header)) {
delete headers[header]
}
+
+ // If this request didn't origin from this session we filter
+ // out the "x-middleware-subrequest" header so we don't skip
+ // middleware incorrectly
+ if (
+ header === 'x-middleware-subrequest' &&
+ headers['x-middleware-subrequest-id'] !==
+ (globalThis as any)[Symbol.for('@next/middleware-subrequest-id')]
+ ) {
+ delete headers['x-middleware-subrequest']
+ }
}
}
packages/next/src/server/web/sandbox/context.ts+4 0
@@ -373,6 +373,10 @@ Learn More: https://nextjs.org/docs/messages/edge-dynamic-code-evaluation`),
store.headers.get('x-middleware-subrequest') ?? ''
)
}
+ init.headers.set(
+ 'x-middleware-subrequest-id',
+ (globalThis as any)[Symbol.for('@next/middleware-subrequest-id')]
+ )
const prevs =
init.headers.get(`x-middleware-subrequest`)?.split(':') || []
test/e2e/middleware-general/test/index.test.ts+13 0
@@ -144,6 +144,19 @@ describe('Middleware Runtime', () => {
}
}
+ it('should filter request header properly', async () => {
+ const res = await next.fetch('/redirect-to-somewhere', {
+ headers: {
+ 'x-middleware-subrequest':
+ 'middleware:middleware:middleware:middleware:middleware',
+ },
+ redirect: 'manual',
+ })
+
+ expect(res.status).toBe(307)
+ expect(res.headers.get('location')).toContain('/somewhere')
+ })
+
it('should handle 404 on fallback: false route correctly', async () => {
const res = await next.fetch('/ssg-fallback-false/first')
expect(res.status).toBe(200)

[backport] Update middleware request header (#77202)

JJ Kasper· Mar 17, 2025, 09:02 PM+3405fd3ae8f85
packages/next/src/server/lib/router-server.ts+6 0
@@ -149,6 +149,12 @@ export async function initialize(opts: {
renderServer.instance =
require('./render-server') as typeof import('./render-server')
+ const randomBytes = new Uint8Array(8)
+ crypto.getRandomValues(randomBytes)
+ const middlewareSubrequestId = Buffer.from(randomBytes).toString('hex')
+ ;(globalThis as any)[Symbol.for('@next/middleware-subrequest-id')] =
+ middlewareSubrequestId
+
const requestHandlerImpl: WorkerRequestHandler = async (req, res) => {
// internal headers should not be honored by the request handler
if (!process.env.NEXT_PRIVATE_TEST_HEADERS) {
packages/next/src/server/lib/server-ipc/utils.ts+11 0
@@ -57,5 +57,16 @@ export const filterInternalHeaders = (
if (INTERNAL_HEADERS.includes(header)) {
delete headers[header]
}
+
+ // If this request didn't origin from this session we filter
+ // out the "x-middleware-subrequest" header so we don't skip
+ // middleware incorrectly
+ if (
+ header === 'x-middleware-subrequest' &&
+ headers['x-middleware-subrequest-id'] !==
+ (globalThis as any)[Symbol.for('@next/middleware-subrequest-id')]
+ ) {
+ delete headers['x-middleware-subrequest']
+ }
}
}
packages/next/src/server/web/sandbox/context.ts+4 0
@@ -365,6 +365,10 @@ Learn More: https://nextjs.org/docs/messages/edge-dynamic-code-evaluation`),
store.headers.get('x-middleware-subrequest') ?? ''
)
}
+ init.headers.set(
+ 'x-middleware-subrequest-id',
+ (globalThis as any)[Symbol.for('@next/middleware-subrequest-id')]
+ )
const prevs =
init.headers.get(`x-middleware-subrequest`)?.split(':') || []
test/e2e/middleware-general/test/index.test.ts+13 0
@@ -102,6 +102,19 @@ describe('Middleware Runtime', () => {
}
function runTests({ i18n }: { i18n?: boolean }) {
+ it('should filter request header properly', async () => {
+ const res = await next.fetch('/redirect-to-somewhere', {
+ headers: {
+ 'x-middleware-subrequest':
+ 'middleware:middleware:middleware:middleware:middleware',
+ },
+ redirect: 'manual',
+ })
+
+ expect(res.status).toBe(307)
+ expect(res.headers.get('location')).toContain('/somewhere')
+ })
+
it('should handle 404 on fallback: false route correctly', async () => {
const res = await next.fetch('/ssg-fallback-false/first')
expect(res.status).toBe(200)

References