Safeguard
Application Security

Securing Next.js applications and middleware

CVE-2025-29927 let attackers bypass Next.js middleware auth with one header. Here's how that and three other real CVEs expose middleware, Server Actions, and caching.

James
Principal Security Architect
7 min read

On March 21, 2025, Vercel disclosed CVE-2025-29927: a Next.js middleware authorization bypass that let attackers skip auth checks entirely by sending a single crafted header. Any app on Next.js before 12.3.5, 13.5.9, 14.2.25, or 15.2.3 that used middleware to gate access to protected routes was exposed — no authentication, no exploit chain, just x-middleware-subrequest: middleware on the request. It's the clearest example yet of a pattern that keeps recurring in Next.js applications: framework internals (middleware, Server Actions, the Image Optimization API, the Data Cache) sit closer to the network edge than most teams realize, and each one has shipped a real, dated CVE in the last two years. This post walks through four of those vulnerabilities with version numbers and root causes, then lays out concrete hardening steps for teams running Next.js in production today.

What was CVE-2025-29927 and who was affected?

CVE-2025-29927 let an attacker bypass Next.js middleware-based authorization by setting the internal header x-middleware-subrequest to a value matching the middleware's own skip list. Next.js uses this header internally to prevent middleware from recursively invoking itself on internal subrequests; the bug was that the check trusted the header on inbound external requests too. Send x-middleware-subrequest: middleware:middleware:middleware:middleware:middleware (the exact depth needed varied by version) and the middleware chain — including any NextResponse.redirect() or NextResponse.rewrite() used to block unauthenticated users — was skipped, and the request passed straight through to the protected page or API route. It affected self-hosted Next.js deployments (not Vercel's platform, which had edge-level mitigations) running versions prior to the patched releases: 12.3.5, 13.5.9, 14.2.25, and 15.2.3. Any app that used middleware as the sole gate for /admin, /dashboard, or internal APIs was fully exposed to unauthenticated access.

Why shouldn't middleware be the only authorization layer?

Because middleware runs before Next.js knows anything about the destination route, and it can be bypassed by anything that alters how the request reaches that route — a header, a rewrite rule, a CDN misconfiguration, or a future parsing bug like CVE-2025-29927. Next.js's own documentation added an explicit warning after the disclosure: "Middleware... is not a substitute for proper authorization checks. Always verify user identity in your data access layer or API routes." The practical fix is defense in depth — check session validity in the middleware for UX (fast redirects, no page flash) but re-check it again in the Server Component, Route Handler, or Server Action that actually touches data. A team running an internal finance tool on Next.js 14.2.20 in early 2025, for example, would have had its /api/invoices route return real invoice data to an unauthenticated caller if authorization lived only in middleware.ts — the route handler itself never verified who was asking.

How did CVE-2024-34351 turn Server Actions into an SSRF vector?

CVE-2024-34351 allowed an attacker to trigger server-side requests to arbitrary internal hosts by manipulating the Host header on a request that invoked a Server Action, because Next.js used that header to construct the redirect URL after the action ran, in versions before 14.1.1. Server Actions are functions marked "use server" that the client can call directly over a special POST request; the framework builds a follow-up redirect based on the request's origin. By spoofing the Host header, an attacker could make the server issue a redirect — and in vulnerable configurations, a server-side fetch — to an internal address such as 169.254.169.254 (the cloud metadata endpoint on AWS/GCP/Azure) or a service on the private VPC. Teams that deployed Server Actions behind a reverse proxy without stripping or validating the Host header, and hadn't yet patched to 14.1.1, had a functioning SSRF primitive reachable from any public form that used a Server Action for its submit handler.

What caused the Next.js cache-poisoning denial-of-service (CVE-2024-46982)?

CVE-2024-46982 let an attacker poison the Next.js Data Cache so that other users received a 500 error instead of the real page, because a race condition in the cache-revalidation logic in versions before 14.2.10 allowed a malformed response to be cached and served to subsequent requests. The bug lived in how Next.js handled concurrent revalidation of a cached route: a request that arrived mid-revalidation could receive — and then have cached — an internal error state rather than valid HTML. Because the Data Cache is shared across all users hitting that route on the same server (and, with a CDN in front, across the CDN's cache too), a single crafted or unlucky request could cause every subsequent visitor to see a broken page until the cache entry expired or was manually purged. This is a low-complexity denial-of-service: no credentials, no injection, just concurrent requests against a route using Incremental Static Regeneration or fetch with time-based revalidation.

How can Image Optimization and next.config.js expose internal infrastructure?

The built-in /_next/image optimizer will fetch and proxy any URL you allow through images.remotePatterns in next.config.js, so an overly broad pattern turns it into an SSRF gateway for your own server. Next.js added remotePatterns (replacing the older, even looser domains array) specifically to scope which hostnames the optimizer may fetch from, but teams frequently configure a wildcard like hostname: '**' or a broad protocol match to unblock a CMS integration during development and never tighten it before shipping. Because the optimizer runs server-side and returns the fetched image bytes to the client, a wildcard pattern lets an attacker submit /_next/image?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/&w=256&q=75 and read back cloud credentials or internal service responses through an image-shaped request that most WAF rules never inspect.

What are the concrete steps to harden a Next.js deployment today?

Patch to the latest minor release on your major version first — 15.2.3+, 14.2.25+, 13.5.9+, or 12.3.5+ close the middleware bypass, and 14.1.1+ and 14.2.10+ close the Server Actions SSRF and cache-poisoning bugs described above. Beyond patching: (1) move authorization checks into the data access layer (Server Components, Route Handlers, Server Actions themselves) rather than trusting middleware alone; (2) explicitly set images.remotePatterns to specific hostnames and paths, never a bare wildcard; (3) set experimental.serverActions.allowedOrigins and validate the Host/Origin headers at your reverse proxy so Server Actions can't be redirected off-origin; (4) disable or scope the x-middleware-subrequest risk by upgrading rather than attempting to filter the header yourself, since the internal contract changed between patched versions; and (5) treat next.config.js as a security-sensitive file requiring the same review as IAM policy, since a single misconfigured rewrite or header rule can silently disable protections the rest of the app assumes are in place.

How Safeguard Helps

Safeguard maps these Next.js CVEs to your actual deployed code paths with reachability analysis, so you see immediately whether your middleware, Server Actions, or next.config.js image patterns are exploitable in your specific build rather than chasing every advisory that mentions the framework. Griffin AI, Safeguard's security reasoning engine, reads your middleware chain and route handlers to flag places where authorization logic lives only in middleware.ts — the exact pattern CVE-2025-29927 turned into a full bypass — and explains the risk in plain language for engineering teams who didn't write the original code. Safeguard generates and ingests SBOMs across your Next.js dependency tree to catch outdated framework versions the moment a new advisory drops, and where the fix is mechanical — bumping next to a patched version, tightening remotePatterns, or adding an origin check — Safeguard opens an auto-fix pull request instead of leaving it as a ticket in a backlog.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.