From 03b9da8ab827187fd7f792315a2859920477693a Mon Sep 17 00:00:00 2001 From: "xuping.wang" <2232455931@qq.com> Date: Thu, 3 Sep 2026 03:05:02 +0800 Subject: [PATCH 1/2] fix(dev): respect trusted proxy headers --- .../.changes/patch.trust-dev-proxy-headers.md | 1 + .../vite/node-adapter-test.ts | 47 +++++++++++++++++++ .../react-router-dev/vite/node-adapter.ts | 2 +- 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packages/react-router-dev/.changes/patch.trust-dev-proxy-headers.md create mode 100644 packages/react-router-dev/vite/node-adapter-test.ts diff --git a/packages/react-router-dev/.changes/patch.trust-dev-proxy-headers.md b/packages/react-router-dev/.changes/patch.trust-dev-proxy-headers.md new file mode 100644 index 0000000000..0611c08e6a --- /dev/null +++ b/packages/react-router-dev/.changes/patch.trust-dev-proxy-headers.md @@ -0,0 +1 @@ +Respect trusted reverse proxy host and protocol headers in the development server diff --git a/packages/react-router-dev/vite/node-adapter-test.ts b/packages/react-router-dev/vite/node-adapter-test.ts new file mode 100644 index 0000000000..afd292037d --- /dev/null +++ b/packages/react-router-dev/vite/node-adapter-test.ts @@ -0,0 +1,47 @@ +import { + type AddressInfo, + createServer, + request as sendRequest, +} from "node:http"; +import type * as Vite from "vite"; +import { fromNodeRequest } from "./node-adapter"; + +describe("fromNodeRequest", () => { + it("uses forwarded host and protocol headers", async () => { + let requestUrl = await new Promise((resolve, reject) => { + let server = createServer(async (nodeReq, nodeRes) => { + let viteRequest = Object.assign(nodeReq, { + originalUrl: nodeReq.url, + }) as Vite.Connect.IncomingMessage; + + try { + let request = await fromNodeRequest(viteRequest, nodeRes); + resolve(request.url); + nodeRes.end(); + } catch (error) { + reject(error); + } finally { + server.close(); + } + }); + + server.on("error", reject); + server.listen(0, "127.0.0.1", () => { + let { port } = server.address() as AddressInfo; + let request = sendRequest({ + host: "127.0.0.1", + port, + headers: { + host: "internal.example.com", + "x-forwarded-host": "public.example.com", + "x-forwarded-proto": "https", + }, + }); + request.on("error", reject); + request.end(); + }); + }); + + expect(requestUrl).toBe("https://public.example.com/"); + }); +}); diff --git a/packages/react-router-dev/vite/node-adapter.ts b/packages/react-router-dev/vite/node-adapter.ts index 625e12d77c..dd44528869 100644 --- a/packages/react-router-dev/vite/node-adapter.ts +++ b/packages/react-router-dev/vite/node-adapter.ts @@ -19,5 +19,5 @@ export async function fromNodeRequest( ); nodeReq.url = nodeReq.originalUrl; - return createRequest(nodeReq, nodeRes); + return createRequest(nodeReq, nodeRes, { trustProxy: true }); } From b6055f46d645d9070b7ccaf3affc5f84c05bb651 Mon Sep 17 00:00:00 2001 From: "xuping.wang" <2232455931@qq.com> Date: Fri, 4 Sep 2026 00:17:06 +0800 Subject: [PATCH 2/2] fix(dev): preserve validated host for proxy requests --- packages/react-router-dev/vite/node-adapter-test.ts | 6 +++--- packages/react-router-dev/vite/node-adapter.ts | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/react-router-dev/vite/node-adapter-test.ts b/packages/react-router-dev/vite/node-adapter-test.ts index afd292037d..9c215e64d6 100644 --- a/packages/react-router-dev/vite/node-adapter-test.ts +++ b/packages/react-router-dev/vite/node-adapter-test.ts @@ -7,7 +7,7 @@ import type * as Vite from "vite"; import { fromNodeRequest } from "./node-adapter"; describe("fromNodeRequest", () => { - it("uses forwarded host and protocol headers", async () => { + it("uses the forwarded protocol without trusting the forwarded host", async () => { let requestUrl = await new Promise((resolve, reject) => { let server = createServer(async (nodeReq, nodeRes) => { let viteRequest = Object.assign(nodeReq, { @@ -32,8 +32,8 @@ describe("fromNodeRequest", () => { host: "127.0.0.1", port, headers: { - host: "internal.example.com", - "x-forwarded-host": "public.example.com", + host: "public.example.com", + "x-forwarded-host": "evil.example.com", "x-forwarded-proto": "https", }, }); diff --git a/packages/react-router-dev/vite/node-adapter.ts b/packages/react-router-dev/vite/node-adapter.ts index dd44528869..9a2e2b679f 100644 --- a/packages/react-router-dev/vite/node-adapter.ts +++ b/packages/react-router-dev/vite/node-adapter.ts @@ -19,5 +19,10 @@ export async function fromNodeRequest( ); nodeReq.url = nodeReq.originalUrl; - return createRequest(nodeReq, nodeRes, { trustProxy: true }); + return createRequest(nodeReq, nodeRes, { + // Vite validates the Host header against server.allowedHosts. Keep that + // validated host instead of trusting a client-controlled forwarded host. + host: nodeReq.headers.host ?? "localhost", + trustProxy: true, + }); }