The Header That Skips the Check: Bypassing Next.js Middleware

Web & API Security Level 3/4 ~5 min October 13, 2026

The challenge

Mereton's internal console puts its login wall in Next.js middleware: every request to /admin is supposed to pass through it first. A researcher noticed the framework trusts a header it uses to talk to itself, and that nothing stops you from sending that header from the outside. The request to the admin dashboard is loaded in the editor. Make the middleware stand aside and read the admin API key the dashboard prints once you are in.

What you'll learn

  • Explain why a value a framework uses to trust itself must never be accepted from a client
  • Reproduce the x-middleware-subrequest bypass by sending the header with a repeated value
  • Read a redirect response for the clue that names the middleware being skipped
  • Connect this to the X-Forwarded-For rule: trust what your edge writes, not what arrives
  • Recognise authentication enforced only in middleware as a single point that can be stepped around

Skills tested

HTTP header manipulationAccess control analysisReading framework behaviour

Prerequisites

  • The shape of an HTTP request
  • What middleware does in a web framework

How it works

A web framework often makes requests to its own internals, and marks them with a header so it does not loop forever. Next.js used x-middleware-subrequest for exactly that. The problem was that the same header decided whether the middleware ran: the framework counted how many times the current middleware's name appeared in it, and once the count reached its recursion limit of five, it assumed a loop and skipped the middleware.

Nothing stripped that header at the edge, so a browser could send it too. Set it to the middleware's own module path, repeated five times, and the authentication layer simply did not run. The request reached the protected page as if it had been approved.

The general rule is that a token meaning this request is internal must be unforgeable from outside, which in practice means the boundary has to remove any inbound copy and write its own. A header, by itself, is never that.

Common mistakes

  • Sending a single middleware. Older Next.js releases skipped on one copy, but this version counts them and only stands aside at its recursion limit of five.
  • Editing the path or the Host. The endpoint and host are correct. Only the trust header is the way in.
  • Reading the login redirect as a dead end. Its comment names the middleware module, which is the value you need.
  • Answering with the redirect target. /login?next=/admin is the wall, not the prize.

How to defend against it

Treat the header as hostile input.

  • Strip x-middleware-subrequest at the CDN or reverse proxy so it can never arrive from a client. The framework's patched releases do this internally; upgrade.
  • Do not make authentication depend on a single middleware pass. Re-check authorisation in the route handler or data layer, where a skipped middleware cannot help.
  • Apply the same discipline to every trust header (X-Forwarded-For, X-Real-IP, internal auth headers): the only value you may trust is the one your outermost proxy wrote.

Full solution

Pro and Max members unlock the complete step-by-step walkthrough.

Go Pro

Community stats

161 completions
83% success rate
M2F14M3 First blood

Related Daily Hacks

Contributors

Credited contributors

These hackers reported issues, improved content, and helped harden this page.

36,000+ Hackers Real labs Free
Start Hacking Free or solve today's hack, no account needed