Next.js Improper Middleware Redirect Handling Leads to SSRF
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
Details
A vulnerability in **Next.js Middleware** has been fixed in **v14.2.32** and **v15.4.7**. The issue occurred when request headers were directly passed into `NextResponse.next()`. In self-hosted applications, this could allow Server-Side Request Forgery (SSRF) if certain sensitive headers from the incoming request were reflected back into the response. All users implementing custom middleware logic in self-hosted environments are strongly encouraged to upgrade and verify correct usage of the `next()` function. More details at [Vercel Changelog](https://vercel.com/changelog/cve-2025-57822)
The fix
fix router handling when setting a location response header
packages/next/src/server/lib/router-utils/resolve-routes.ts+33 −9
@@ -18,7 +18,10 @@ import { formatHostname } from '../format-hostname'import { toNodeOutgoingHttpHeaders } from '../../web/utils'import { isAbortError } from '../../pipe-readable'import { getHostname } from '../../../shared/lib/get-hostname'-import { getRedirectStatus } from '../../../lib/redirect-status'+import {+getRedirectStatus,+allowedStatusCodes,+} from '../../../lib/redirect-status'import { normalizeRepeatedSlashes } from '../../../shared/lib/utils'import { getRelativeURL } from '../../../shared/lib/router/utils/relativize-url'import { addPathPrefix } from '../../../shared/lib/router/utils/add-path-prefix'@@ -659,15 +662,36 @@ export function getResolveRoutes(if (middlewareHeaders['location']) {const value = middlewareHeaders['location'] as string-const rel = getRelativeURL(value, initUrl)-resHeaders['location'] = rel-parsedUrl = url.parse(rel, true)-return {-parsedUrl,-resHeaders,-finished: true,-statusCode: middlewareRes.status,+// Only process Location header as a redirect if it has a proper redirect status+// This prevents a Location header with non-redirect status from being treated as a redirect+const isRedirectStatus = allowedStatusCodes.has(+middlewareRes.status+)++if (isRedirectStatus) {+// Process as redirect: update parsedUrl and convert to relative URL+const rel = getRelativeURL(value, initUrl)+resHeaders['location'] = rel+parsedUrl = url.parse(rel, true)++return {+parsedUrl,+resHeaders,+finished: true,+statusCode: middlewareRes.status,+}+} else {+// Not a redirect: just pass through the Location header+resHeaders['location'] = value++return {+parsedUrl,+resHeaders,+finished: true,+bodyStream,+statusCode: middlewareRes.status,+}}}
test/e2e/app-dir/app-middleware/app-middleware.test.ts+24 −1
@@ -6,7 +6,7 @@ import { nextTestSetup, FileRef } from 'e2e-utils'import type { Response } from 'node-fetch'describe('app-dir with middleware', () => {-const { next } = nextTestSetup({+const { next, isNextDeploy } = nextTestSetup({files: __dirname,})@@ -248,6 +248,29 @@ describe('app-dir with middleware', () => {await browser.deleteCookies()})++// TODO: This consistently 404s on Vercel deployments. It technically+// doesn't repro the bug we're trying to fix but we need to figure out+// why the handling is different.+if (!isNextDeploy) {+it('should not incorrectly treat a Location header as a rewrite', async () => {+const res = await next.fetch('/test-location-header')++// Should get status 200 (not a redirect status)+expect(res.status).toBe(200)++// Should get the JSON response associated with the route,+// and not follow the redirect+const json = await res.json()+expect(json).toEqual({ foo: 'bar' })++// Ensure the provided location is still on the response+const locationHeader = res.headers.get('location')+expect(locationHeader).toBe(+'https://next-data-api-endpoint.vercel.app/api/random'+)+})+}})describe('app dir - middleware without pages dir', () => {
test/e2e/app-dir/app-middleware/middleware.js+11 −0
@@ -78,6 +78,17 @@ export async function middleware(request) {return res}+if (request.nextUrl.pathname === '/test-location-header') {+return NextResponse.json(+{ foo: 'bar' },+{+headers: {+location: 'https://next-data-api-endpoint.vercel.app/api/random',+},+}+)+}+return NextResponse.next({request: {headers: headersFromRequest,
References
- WEBhttps://github.com/vercel/next.js/security/advisories/GHSA-4342-x723-ch2f
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2025-57822
- WEBhttps://github.com/vercel/next.js/commit/9c9aaed5bb9338ef31b0517ccf0ab4414f2093d8
- PACKAGEhttps://github.com/vercel/next.js
- WEBhttps://vercel.com/changelog/cve-2025-57822