> Episode 1 — Next.js Middleware Vulnerability (CVE-2025-29927)_
This is the first episode of Revisiting — a series where we unpack recent, nasty vulnerabilities and extract practical lessons for developers, defenders, and site owners.
In March 2025 a critical authorization-bypass affecting Next.js middleware (tracked as CVE-2025-29927) was disclosed. The bug allowed specially crafted external requests to bypass middleware logic in many common Next.js deployments. The result: middleware-based authentication and authorization checks could be skipped, exposing protected routes and data. This episode explains what happened, who was affected, how defenders detected it, and how you should fix and harden your apps today.
## TL;DR
- >What: A Next.js middleware authorization bypass (CVE-2025-29927) that could let attackers skip middleware checks in many apps.
- >Root cause (high level): Framework trusted an internal-use header (
x-middleware-subrequest
) and allowed internal-style requests to skip middleware logic when that header was present. External requests could be crafted to mimic it. - >Impact: Unauthorized access to routes protected only by middleware, possible data exposure, and increased risk of chained attacks.
- >Fix: Upgrade Next.js to patched versions, or block the offending header at the edge / WAF if immediate patching isn't possible. Use defense-in-depth: canonicalize URLs, validate headers, enforce checks in downstream handlers.
If you manage Next.js apps, treat this as an urgent patch-and-audit task.
## Background & timeline
- >Discovery / disclosure: Public advisories and vendor notices appeared in March 2025 after coordinated disclosure by security researchers. The vulnerability received CVE-2025-29927 and a high criticality score.
- >Affected versions: Many Next.js releases prior to the patched builds were affected. Official advisories list affected ranges for several major versions; if your app used middleware for auth and relied solely on it, you were at risk.
- >Mitigations published: Next.js released patches, and edge vendors (Cloudflare, others) rolled out WAF rules to block exploit traffic. Security vendors published detection and mitigation guidance.
## What exactly went wrong? (technical, defensive view)
Middleware role in Next.js: middleware runs early in the request lifecycle and is commonly used for authentication gates, redirects, A/B tests, or header rewrites. Many developers assumed middleware is a reliable, single place to enforce access control for protected routes.
Root of the bypass (high level): the middleware/runtime pipeline included logic for handling internal subrequests (used internally by the framework or by rewrites). An internal HTTP marker header (documented internally) — x-middleware-subrequest
— was used to mark these internal requests and alter how middleware logic behaved. The framework trusted this header under certain configurations, and attackers could craft external requests that included the same header, or otherwise cause the app to treat an external request as an internal one. That trust allowed the route to bypass the middleware's checks.
Why that’s dangerous: middleware is often written as "if not authenticated -> redirect" and then returns. If an attacker can present the request in a way that skips that branch (or short-circuits execution), the route will continue as if authentication succeeded. That’s a straight path to unauthorized access.
Quote:Note: This is a defensive description. No PoC exploit code is published here—only mitigation and detection guidance.
## Who was at risk?
- >Self-hosted Next.js apps running server middleware modes were widely impacted. Static-only apps without server middleware were not affected.
- >Apps that rely solely on middleware for auth — common in many codebases where middleware centralizes session checks — were the primary victims. If you also enforced checks in server handlers (defense-in-depth), exposure was reduced.
## Detection — how defenders found exploitation attempts
Look for these signals in logs and edge telemetry (adjust to your logging format):
- >Unexpected presence of an internal marker header (e.g. requests containing
x-middleware-subrequest
) coming from public IPs. Legit internal subrequests should not appear from the public Internet. - >Mismatch between authenticated session cookie and observed access — e.g., endpoints returning 200 on resources normally gated by redirects. Correlate with middleware log entries.
- >Unusual replayed or crafted requests where
Referer
,Origin
, orUser-Agent
patterns are anomalous relative to normal traffic. Use IDS/NDR to surface traffic patterns. - >WAF telemetry: Many WAFs added managed rules to detect and block known exploit patterns; check WAF logs for blocked requests containing the header or signature matching exploit templates.
If you use log aggregation / SIEM (e.g., Datadog, Splunk, Elastic), search for x-middleware-subrequest
across all ingress logs and correlate with status
changes and unusual IPs.
## Mitigation & hardening (what to do now)
Immediate steps (0–24 hours):
- >Patch: Upgrade Next.js to the patched versions. Vendor advisories list the minimum safe versions. If you run any affected version, schedule an emergency upgrade.
- >Edge block / WAF: If you can’t patch immediately, block requests that contain the internal-only header at your edge (CDN / WAF). Cloudflare and others released managed WAF rules to do this; enable them. Remember that blocking headers must be done carefully to avoid false positives with legitimate internal traffic.
- >Alerting: Create SIEM alerts for public requests that include
x-middleware-subrequest
or other unusual internal markers.
Short-term (days):
- >Harden middleware code:
- >Canonicalize
req.nextUrl
and use strict path normalization before checking prefixes. Don’t rely on naivestartsWith('/admin')
checks without normalization. - >Validate headers: treat internal headers as untrusted coming from the network — refuse requests with those headers unless the source is a known internal IP or comes through a secure internal channel.
- >Add route-level checks: replicate authorization checks in the server/route handler as a defense-in-depth if middleware is bypassed.
- >Canonicalize
Longer-term (weeks):
- >Audit all middleware usage: identify places where middleware is used for auth, rate-limiting, or security policy enforcement. For each, ensure there’s a fallback check in the route or API handler.
- >Add automated tests: include security regression tests that assert that middleware gates can't be bypassed by crafted requests.
- >Review deployment model: prefer deployment modes where middleware semantics are well-defined and supported by your hosting provider; follow Next.js guidance for middleware hosting.
## Example defensive patterns (code snippets)
Quote:Defensive examples below are for guidance. They demonstrate robust header handling, canonicalization, and duplicating checks in server handlers. Adapt to your environment.
### Refuse internal-only headers from the public
terminal// defensive-middleware.ts import { NextResponse } from "next/server"; function isInternalSource(req: Request) { const ip = req.headers.get('x-forwarded-for') || ''; // Replace with your internal CIDR checks or trust proxy logic return ip.startsWith('10.') || ip.startsWith('192.168.'); } export function middleware(req: Request) { // Refuse requests that contain internal-only headers unless the source is internal if (req.headers.get('x-middleware-subrequest') && !isInternalSource(req)) { return new NextResponse('Forbidden', { status: 403 }); } // canonicalize path const url = new URL(req.url); const pathname = url.pathname.replace(/\/+$, ''); // trim trailing slashes // robust auth check example if (pathname.startsWith('/admin')) { const cookie = req.headers.get('cookie') || ''; if (!cookie.includes('session=')) return NextResponse.redirect('/login'); } return NextResponse.next(); }
### Duplicate the check in the server handler (defense-in-depth)
terminal// app/api/admin/route.ts (or pages/api/admin.js) export async function GET(req: Request) { const cookie = req.headers.get('cookie') || ''; if (!cookie.includes('session=')) { return new Response('Unauthorized', { status: 401 }); } // serve protected data... }
## Appendix A — SIEM / Detection Queries
Quote:Example queries for Splunk, Elastic/Kibana, and Datadog. Adjust fields to your logs schema.
### Splunk (SPL)
terminal# Find public requests that include internal-only headers index=web_logs sourcetype=access_combined | where like(_raw, "%x-middleware-subrequest%") OR like(_raw, "%x-nextjs-internal%") | lookup local=true internal_ip_list ip AS clientip OUTPUT ip AS internal_ip /* list your internal IPs */ | where isnull(internal_ip) | stats count by clientip, useragent, referer, _time | sort -count
terminal# Detect gated endpoints returning 200 without session cookie index=web_logs sourcetype=access_combined status=200 path="/admin*" | where NOT like(_raw, "%session=%") AND NOT like(_raw, "%authToken%") | stats count by clientip, useragent, _time
### Elastic / Kibana (KQL)
terminal// Requests containing internal headers coming from public IPs http.request.headers: "*x-middleware-subrequest*" and NOT client.ip : (10.0.0.0/8 or 192.168.0.0/16 or 172.16.0.0/12)
terminal// Unexpected 200 on protected routes http.response.status_code : 200 and url.path : "/admin*" and NOT http.request.cookies : "*session*"
### Datadog (Log Query)
terminal@http.headers:*x-middleware-subrequest* AND !@network.client.ip:[10.0.0.0/8 OR 192.168.0.0/16]
## Appendix B — Nuclei/Scanner Detection Template (defensive)
terminalid: nextjs-middleware-internal-header-detection info: name: Next.js middleware internal header exposure (detection) author: SovietGhost severity: medium requests: - method: GET path: - "{{BaseURL}}/" headers: X-Check: "sovietghost" matchers: - type: word words: - "x-middleware-subrequest" condition: and part: header extractors: - type: regex regex: - "x-middleware-subrequest"
Quote:Purpose: scan internal observability logs or test endpoints to highlight when the internal marker appears in the request stream from public networks.
## Appendix C — Cloudflare / WAF Rule Examples (edge mitigation)
Cloudflare WAF expression (example)
terminal(http.request.headers["x-middleware-subrequest"] ne "") and not (ip.geoip.country in {"UK" "US"} and ip.src in {<YOUR_INTERNAL_EDGE_IPS>})
Alternative (simpler): Create a rule that blocks any request containing x-middleware-subrequest
unless it originates from a defined internal IP list or comes via a trusted backend header.
## Appendix D — Test Harness & CI Checks
### Local test script (Node) — simulate a request with the internal header (run only on staging)
terminal// test/middleware-header-test.js import fetch from 'node-fetch'; async function test() { const res = await fetch('https://staging.sovietghost.uk/admin', { method: 'GET', headers: { 'X-Middleware-Subrequest': '1', 'User-Agent': 'soviet-qa' } }); console.log('status', res.status); const body = await res.text(); console.log('body', body.slice(0, 200)); } test().catch(console.error);
### CI unit test (Jest + Supertest) — ensure middleware blocks the header
terminal// __tests__/middleware.spec.ts import request from 'supertest'; import app from '../server/app'; // however you export your test server test('blocks requests with internal header from external ip', async () => { const res = await request(app) .get('/admin') .set('X-Middleware-Subrequest', '1') .set('X-Forwarded-For', '8.8.8.8'); // simulate public IP expect([401, 403, 302]).toContain(res.status); // expect it to not 200 });
## Appendix E — Incident Response: One-Page Postmortem Template
Title: Next.js Middleware Bypass — Incident Report
Date & Time (UTC): 2025-09-01 12:00
Scope: Services using Next.js middleware on staging
and prod
(list hosts)
Summary: Brief one-paragraph description of what happened, when discovered, and immediate impact.
Root Cause: Summary (framework trusted internal header; external requests could set it).
Impact: Affected endpoints, data exposure risk, number of affected requests/sessions.
Timeline:
- >T0 — discovery
- >T+1h — WAF rule applied
- >T+6h — vendor patch applied on staging
- >T+24h — production patch complete
Mitigation / Remediation: Steps taken (patch versions, WAF rules, session rotation).
Detection: Logs, SIEM searches, IDS signatures used.
Follow-up Actions: CI tests, code changes, long-term mitigations, reporting to customers/regulators if necessary.
Author / Contact: Name, role, secure contact.
## Appendix F — Post-mortem / Lessons Learned Checklist
- > Inventory where middleware is responsible for auth/ACLs.
- > Add route-level enforcement for critical endpoints.
- > Harden header handling and canonicalize URL logic.
- > Add SIEM detection for internal header anomalies.
- > Add CI tests that assert middleware behavior against crafted inputs.
- > Run dependency updates monthly and subscribe to vendor CVE feeds.
- > Create an emergency patch playbook & staging rollback instructions.
- > Provide a customer-facing communication template in case of data exposure.
## Appendix G — Quick Playbook (for engineers on-call)
- >If alerted, identify affected services (grep for middleware, auth checks).
- >Enable WAF rule blocking
x-middleware-subrequest
from public IPs. - >Patch Next.js to vendor-specified safe release.
- >Revoke/rotate sessions or tokens if suspicious access is confirmed.
- >Run SIEM queries to scope exposure (use the Splunk/Elastic queries above).
- >Assemble IR team, capture forensic logs, and prepare the one-page incident report.
## Closing thoughts
Middleware is an elegant place to centralize cross-cutting concerns — but treat it as part of your security perimeter, not the whole fence. The 2025 Next.js middleware bypass taught the community a simple lesson: trust nothing that comes from the network. Headers, paths, cookies — all are user-controlled. Use defense-in-depth, canonicalization, robust validation, and monitoring to survive when frameworks fail.