Authorization Bypass in Next.js Middleware
Description
Next.js is a React framework for building full-stack web applications. Starting in version 1.11.4 and prior to versions 12.3.5, 13.5.9, 14.2.25, and 15.2.3, it is possible to bypass authorization checks within a Next.js application, if the authorization check occurs in middleware. If patching to a safe version is infeasible, it is recommend that you prevent external user requests which contain the x-middleware-subrequest header from reaching your Next.js application. This vulnerability is fixed in 12.3.5, 13.5.9, 14.2.25, and 15.2.3.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
nextnpm | >= 13.0.0, < 13.5.9 | 13.5.9 |
nextnpm | >= 14.0.0, < 14.2.25 | 14.2.25 |
nextnpm | >= 15.0.0, < 15.2.3 | 15.2.3 |
nextnpm | >= 12.0.0, < 12.3.5 | 12.3.5 |
Affected products
1Patches
25fd3ae8f8542[backport] Update middleware request header (#77202)
4 files changed · +34 −0
packages/next/src/server/lib/router-server.ts+6 −0 modified@@ -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 modified@@ -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 modified@@ -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 modified@@ -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)
52a078da3884Update middleware request header (#77201)
4 files changed · +35 −4
packages/next/src/server/lib/router-server.ts+7 −4 modified@@ -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 modified@@ -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 modified@@ -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 modified@@ -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)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- github.com/advisories/GHSA-f82v-jwr5-mffwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-29927ghsaADVISORY
- www.openwall.com/lists/oss-security/2025/03/23/3ghsaWEB
- www.openwall.com/lists/oss-security/2025/03/23/4ghsaWEB
- github.com/vercel/next.js/commit/52a078da3884efe6501613c7834a3d02a91676d2ghsax_refsource_MISCWEB
- github.com/vercel/next.js/commit/5fd3ae8f8542677c6294f32d18022731eab6fe48ghsax_refsource_MISCWEB
- github.com/vercel/next.js/releases/tag/v12.3.5ghsax_refsource_MISCWEB
- github.com/vercel/next.js/releases/tag/v13.5.9ghsax_refsource_MISCWEB
- github.com/vercel/next.js/security/advisories/GHSA-f82v-jwr5-mffwghsax_refsource_CONFIRMWEB
- security.netapp.com/advisory/ntap-20250328-0002ghsaWEB
- vercel.com/changelog/vercel-firewall-proactively-protects-against-vulnerability-with-middlewareghsaWEB
News mentions
3- ‘PCPJack’ Worm Removes TeamPCP Infections, Steals CredentialsSecurityWeek · May 8, 2026
- New PCPJack worm steals credentials, cleans TeamPCP infectionsBleepingComputer · May 7, 2026
- PCPJack Credential Stealer Exploits 5 CVEs to Spread Worm-Like Across Cloud SystemsThe Hacker News · May 7, 2026