[PR #1056] [MERGED] fix: filter internal Next.js headers from inbound requests #1055

Closed
opened 2026-05-06 13:11:45 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/1056
Author: @NathanDrake2406
Created: 5/4/2026
Status: Merged
Merged: 5/5/2026
Merged by: @james-elicx

Base: mainHead: fix/filter-internal-headers-security


📝 Commits (8)

  • 42adff7 fix: filter internal Next.js headers from inbound requests
  • 31e22ad fix: use safe Request rebuild instead of mutating headers directly
  • b8f0700 fix: clone requests when filtering headers
  • 49aa54c fix: clone Requests safely across runtimes
  • 3b9e9b1 fix: avoid type assertions in Request cf access
  • 980440b fix: move header filtering from handler body to entry boundary
  • c469a05 fix: preserve x-vinext-mw-ctx when filtering internal headers in app-rsc-handler
  • 070d555 fix: copy cf in both clone paths, add cloneRequestWithHeaders tests

📊 Changes

7 files changed (+384 additions, -10 deletions)

View changed files

📝 packages/vinext/src/deploy.ts (+13 -1)
📝 packages/vinext/src/index.ts (+14 -2)
📝 packages/vinext/src/server/app-router-entry.ts (+13 -1)
📝 packages/vinext/src/server/app-rsc-handler.ts (+22 -1)
📝 packages/vinext/src/server/prod-server.ts (+14 -5)
📝 packages/vinext/src/server/request-pipeline.ts (+101 -0)
📝 tests/request-pipeline.test.ts (+207 -0)

📄 Description

Summary

  • Adds filterInternalHeaders() to strip internal Next.js headers from inbound requests at all entry points, matching Next.js behavior
  • Prevents attackers from forging x-nextjs-data, x-matched-path, x-now-route-matches, x-next-resume-state-length, and x-middleware-* headers to influence routing or impersonate internal state

Problem

Next.js calls filterInternalHeaders() at the router-server entry point before any handler or middleware sees the request. This strips the INTERNAL_HEADERS list from the request.

vinext was not filtering any of these headers. All four headers reached middleware as user-controlled request headers.

Fix

Added INTERNAL_HEADERS constant and filterInternalHeaders(headers: Headers) to server/request-pipeline.ts, ported directly from Next.js. Wired into every request entry point:

Entry point File
App Router handler server/app-rsc-handler.ts
CF Worker App Router entry server/app-router-entry.ts
Node prod-server (App + Pages) server/prod-server.ts (nodeToWebRequest + Pages path)
Dev server Pages Router middleware path index.ts
Generated Pages Router worker entry deploy.ts

Filtering runs before middleware, config matching, and routing — no subsystem sees forged internal headers. The x-middleware-* headers are stripped from requests but NOT from middleware responses — the middleware protocol (which reads x-middleware-* from Response.headers, not Request.headers) continues to work correctly.

Tests

Added 8 unit tests in tests/request-pipeline.test.ts:

  • Exact header list matches Next.js INTERNAL_HEADERS
  • All internal headers stripped while preserving others
  • Case-insensitive deletion (HTTP spec compliance)
  • No-op on empty/clean headers
  • Forged x-middleware-rewrite + x-middleware-next stripped
  • Subset stripping preserves unrelated headers

All related tests pass (request-pipeline: 78, deploy: 210, app-router: 300, features: 267, pages-router: 200).


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/1056 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 5/4/2026 **Status:** ✅ Merged **Merged:** 5/5/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `fix/filter-internal-headers-security` --- ### 📝 Commits (8) - [`42adff7`](https://github.com/cloudflare/vinext/commit/42adff7ffecb242fc291f5a727d0c511e820d9b2) fix: filter internal Next.js headers from inbound requests - [`31e22ad`](https://github.com/cloudflare/vinext/commit/31e22ad16996878c62af6d25cd0b3180b0037f54) fix: use safe Request rebuild instead of mutating headers directly - [`b8f0700`](https://github.com/cloudflare/vinext/commit/b8f0700cf7535a998bb9981897343e6dce9ee68c) fix: clone requests when filtering headers - [`49aa54c`](https://github.com/cloudflare/vinext/commit/49aa54c76f0a9e3a2e7e6f5091a26707e45ce42f) fix: clone Requests safely across runtimes - [`3b9e9b1`](https://github.com/cloudflare/vinext/commit/3b9e9b1559f990dd4603f907fba5667353787942) fix: avoid type assertions in Request cf access - [`980440b`](https://github.com/cloudflare/vinext/commit/980440b9fc0596577a94b753cc2e5321fa6d6321) fix: move header filtering from handler body to entry boundary - [`c469a05`](https://github.com/cloudflare/vinext/commit/c469a05a9ca46a79019fba84caa4297a4ae58ad6) fix: preserve x-vinext-mw-ctx when filtering internal headers in app-rsc-handler - [`070d555`](https://github.com/cloudflare/vinext/commit/070d555b88ee3aa35405ec80d9dc65954bb9dddc) fix: copy cf in both clone paths, add cloneRequestWithHeaders tests ### 📊 Changes **7 files changed** (+384 additions, -10 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/deploy.ts` (+13 -1) 📝 `packages/vinext/src/index.ts` (+14 -2) 📝 `packages/vinext/src/server/app-router-entry.ts` (+13 -1) 📝 `packages/vinext/src/server/app-rsc-handler.ts` (+22 -1) 📝 `packages/vinext/src/server/prod-server.ts` (+14 -5) 📝 `packages/vinext/src/server/request-pipeline.ts` (+101 -0) 📝 `tests/request-pipeline.test.ts` (+207 -0) </details> ### 📄 Description ## Summary - Adds `filterInternalHeaders()` to strip internal Next.js headers from inbound requests at all entry points, matching Next.js behavior - Prevents attackers from forging `x-nextjs-data`, `x-matched-path`, `x-now-route-matches`, `x-next-resume-state-length`, and `x-middleware-*` headers to influence routing or impersonate internal state ## Problem Next.js calls [`filterInternalHeaders()`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/server-ipc/utils.ts#L55-L63) at the [router-server entry point](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/router-server.ts#L231) before any handler or middleware sees the request. This strips the [`INTERNAL_HEADERS`](https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/server-ipc/utils.ts#L42-L53) list from the request. vinext was not filtering any of these headers. All four headers reached middleware as user-controlled request headers. ## Fix Added `INTERNAL_HEADERS` constant and `filterInternalHeaders(headers: Headers)` to `server/request-pipeline.ts`, ported directly from Next.js. Wired into every request entry point: | Entry point | File | |---|---| | App Router handler | `server/app-rsc-handler.ts` | | CF Worker App Router entry | `server/app-router-entry.ts` | | Node prod-server (App + Pages) | `server/prod-server.ts` (nodeToWebRequest + Pages path) | | Dev server Pages Router middleware path | `index.ts` | | Generated Pages Router worker entry | `deploy.ts` | Filtering runs **before** middleware, config matching, and routing — no subsystem sees forged internal headers. The `x-middleware-*` headers are stripped from **requests** but NOT from middleware **responses** — the middleware protocol (which reads `x-middleware-*` from `Response.headers`, not `Request.headers`) continues to work correctly. ## Tests Added 8 unit tests in `tests/request-pipeline.test.ts`: - Exact header list matches Next.js `INTERNAL_HEADERS` - All internal headers stripped while preserving others - Case-insensitive deletion (HTTP spec compliance) - No-op on empty/clean headers - Forged `x-middleware-rewrite` + `x-middleware-next` stripped - Subset stripping preserves unrelated headers All related tests pass (request-pipeline: 78, deploy: 210, app-router: 300, features: 267, pages-router: 200). --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:11:45 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#1055
No description provided.