The Internal Header Anyone Could Send — JavaScript Bug Hunt
Modelled on Next.js CVE-2025-29927 (March 2025). Next.js marked its own internal subrequests with an x-middleware-subrequest header so middleware would not…
- Language: JavaScript
- Layer: Backend
- Difficulty: Medium
- Concepts: Security, Auth, Networking
- Modelled on: Next.js · CVE-2025-29927
- Visible tests: an anonymous /admin request is refused; the internal header cannot skip middleware from outside
- Reward: 50 XP for a complete fix
Briefing
Modelled on Next.js CVE-2025-29927 (March 2025). Next.js marked its own internal subrequests with an x-middleware-subrequest header so middleware would not recurse into itself. The framework trusted that header on incoming requests too: a client that sent it with the right value had middleware skipped entirely — and with it any authentication or authorisation check the app ran in middleware. Patched releases stopped honouring the header from outside, and the advice for unpatched deployments was to strip it at the edge.
This reconstruction's pipeline.js has the same recursion guard, read straight from the request headers.
Fix handle so only the server's own subrequests can skip middleware.
Bug report
BUG-MWSKIP · Priority: Critical (auth bypass) · Reported by: security
handle(req, middleware, route) — req = { path, headers, internal? }:
- req.internal === true marks a subrequest the server itself created; only then is the "x-middleware-subrequest" header honoured (a colon-separated list of middleware names already run; if middleware.name is in it, middleware is skipped)
- on any other request the header is deleted from req.headers before middleware or the route sees it, and middleware ALWAYS runs
- a non-null result from middleware.run(req) is returned as the response; otherwise route(req) is
Observed: GET /admin with "x-middleware-subrequest: middleware" and no cookie returns the admin page.
Logs
[edge] GET /admin cookie=<none> x-middleware-subrequest=middleware -> 200The code as shipped
src/server/pipeline.js (editable)
var HEADER = "x-middleware-subrequest";
// Runs the middleware, then the route. A subrequest lists the middleware it
// has already been through, so middleware that fetches its own app does not
// recurse forever.
exports.handle = function (req, middleware, route) {
var seen = (req.headers[HEADER] || "").split(":");
if (seen.indexOf(middleware.name) === -1) {
var early = middleware.run(req);
if (early) return early;
}
return route(req);
};
Read-only context: src/server/auth-middleware.js.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.