From f01de62d37609a651faa62aad589f1e4b2da524a Mon Sep 17 00:00:00 2001 From: 17hz <0x149527@gmail.com> Date: Fri, 6 Mar 2026 09:23:36 +0800 Subject: [PATCH 1/2] fix: stop stripping credential headers from rewrite proxy requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite destinations are typically the user's own backend services, not untrusted third-party origins. Stripping cookie, authorization, x-api-key, and proxy-authorization headers breaks authentication for proxied API requests. Next.js does not strip these headers for rewrites either — it forwards them as-is to the destination. --- packages/vinext/src/config/config-matchers.ts | 6 ------ packages/vinext/src/server/app-dev-server.ts | 6 ------ 2 files changed, 12 deletions(-) diff --git a/packages/vinext/src/config/config-matchers.ts b/packages/vinext/src/config/config-matchers.ts index 079a424f..2d7feccc 100644 --- a/packages/vinext/src/config/config-matchers.ts +++ b/packages/vinext/src/config/config-matchers.ts @@ -658,12 +658,6 @@ export async function proxyExternalRequest( headers.set("host", targetUrl.host); // Remove headers that should not be forwarded to external services headers.delete("connection"); - // Strip credentials and internal headers to prevent leaking auth tokens, - // session cookies, and middleware internals to third-party origins. - headers.delete("cookie"); - headers.delete("authorization"); - headers.delete("x-api-key"); - headers.delete("proxy-authorization"); const keysToDelete: string[] = []; for (const key of headers.keys()) { if (key.startsWith("x-middleware-")) { diff --git a/packages/vinext/src/server/app-dev-server.ts b/packages/vinext/src/server/app-dev-server.ts index 4b7bb013..32cbdc12 100644 --- a/packages/vinext/src/server/app-dev-server.ts +++ b/packages/vinext/src/server/app-dev-server.ts @@ -1238,12 +1238,6 @@ async function __proxyExternalRequest(request, externalUrl) { const headers = new Headers(request.headers); headers.set("host", targetUrl.host); headers.delete("connection"); - // Strip credentials and internal headers to prevent leaking auth tokens, - // session cookies, and middleware internals to third-party origins. - headers.delete("cookie"); - headers.delete("authorization"); - headers.delete("x-api-key"); - headers.delete("proxy-authorization"); for (const key of [...headers.keys()]) { if (key.startsWith("x-middleware-")) headers.delete(key); } From ae52c07697773f3d3931ddd92432f3fd420fee06 Mon Sep 17 00:00:00 2001 From: 17hz <0x149527@gmail.com> Date: Fri, 6 Mar 2026 09:55:12 +0800 Subject: [PATCH 2/2] test: update proxy tests to assert credential headers are forwarded Update tests to match new behavior where credential headers (cookie, authorization, x-api-key, proxy-authorization) are forwarded through the rewrite proxy, matching Next.js behavior. x-middleware-* headers are still stripped. --- tests/app-router.test.ts | 12 ++++++------ tests/shims.test.ts | 13 +++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/app-router.test.ts b/tests/app-router.test.ts index 3fca677e..b62b96f3 100644 --- a/tests/app-router.test.ts +++ b/tests/app-router.test.ts @@ -2910,7 +2910,7 @@ describe("App Router external rewrite proxy credential stripping", () => { await new Promise((resolve) => mockServer?.close(() => resolve())); }); - it("strips credential headers from proxied requests to external rewrite targets", async () => { + it("forwards credential headers through proxied requests to external rewrite targets", async () => { mockResponseMode = "plain"; capturedHeaders = null; @@ -2926,11 +2926,11 @@ describe("App Router external rewrite proxy credential stripping", () => { }); expect(capturedHeaders).not.toBeNull(); - // Credential headers must be stripped - expect(capturedHeaders!["cookie"]).toBeUndefined(); - expect(capturedHeaders!["authorization"]).toBeUndefined(); - expect(capturedHeaders!["x-api-key"]).toBeUndefined(); - expect(capturedHeaders!["proxy-authorization"]).toBeUndefined(); + // Credential headers must be forwarded (matching Next.js behavior) + expect(capturedHeaders!["cookie"]).toBe("session=secret123"); + expect(capturedHeaders!["authorization"]).toBe("Bearer tok_secret"); + expect(capturedHeaders!["x-api-key"]).toBe("sk_live_secret"); + expect(capturedHeaders!["proxy-authorization"]).toBe("Basic cHJveHk="); // Internal middleware headers must be stripped expect(capturedHeaders!["x-middleware-next"]).toBeUndefined(); // Non-sensitive headers must be preserved diff --git a/tests/shims.test.ts b/tests/shims.test.ts index eb6368e5..bc417fbd 100644 --- a/tests/shims.test.ts +++ b/tests/shims.test.ts @@ -3813,7 +3813,7 @@ describe("proxyExternalRequest", () => { } }); - it("strips credentials and x-middleware-* headers from proxied requests", async () => { + it("forwards credential headers and strips x-middleware-* headers from proxied requests", async () => { const { proxyExternalRequest } = await import( "../packages/vinext/src/config/config-matchers.js" ); @@ -3842,11 +3842,12 @@ describe("proxyExternalRequest", () => { try { await proxyExternalRequest(request, "https://api.example.com/data"); expect(capturedHeaders).toBeDefined(); - // Sensitive headers must be stripped - expect(capturedHeaders!.get("cookie")).toBeNull(); - expect(capturedHeaders!.get("authorization")).toBeNull(); - expect(capturedHeaders!.get("x-api-key")).toBeNull(); - expect(capturedHeaders!.get("proxy-authorization")).toBeNull(); + // Credential headers must be forwarded (matching Next.js behavior) + expect(capturedHeaders!.get("cookie")).toBe("session=secret123"); + expect(capturedHeaders!.get("authorization")).toBe("Bearer tok_secret"); + expect(capturedHeaders!.get("x-api-key")).toBe("sk_live_secret"); + expect(capturedHeaders!.get("proxy-authorization")).toBe("Basic cHJveHk="); + // Internal middleware headers must be stripped expect(capturedHeaders!.get("x-middleware-rewrite")).toBeNull(); expect(capturedHeaders!.get("x-middleware-next")).toBeNull(); // Non-sensitive headers must be preserved