From 9cac9f314277def0ee584e45d4937bac0235738a Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Tue, 18 Mar 2025 09:02:32 -0400 Subject: [PATCH] Handle bad values in x-forwarded-host (#13428) * Handle bad values in x-forwarded-host If a bad value is provide by this header, we simply ignore it and fallback to the host provided by the host header (if there is one). * Add changeset * Update packages/astro/src/core/app/node.ts Co-authored-by: Florian Lefebvre --------- Co-authored-by: Emanuele Stoppa Co-authored-by: Florian Lefebvre --- .changeset/true-moose-report.md | 5 +++++ packages/astro/src/core/app/node.ts | 24 +++++++++++++++++----- packages/astro/test/units/app/node.test.js | 11 ++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 .changeset/true-moose-report.md diff --git a/.changeset/true-moose-report.md b/.changeset/true-moose-report.md new file mode 100644 index 0000000000..29c35526b5 --- /dev/null +++ b/.changeset/true-moose-report.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Prevent bad value in x-forwarded-host from crashing request diff --git a/packages/astro/src/core/app/node.ts b/packages/astro/src/core/app/node.ts index 79e649ed52..9f271c18ec 100644 --- a/packages/astro/src/core/app/node.ts +++ b/packages/astro/src/core/app/node.ts @@ -75,19 +75,27 @@ export class NodeApp extends App { // We need to handle it here and parse the header correctly. // @example "https, http,http" => "http" const forwardedProtocol = getFirstForwardedValue(req.headers['x-forwarded-proto']); - const protocol = forwardedProtocol ?? (isEncrypted ? 'https' : 'http'); + const providedProtocol = (isEncrypted ? 'https' : 'http'); + const protocol = forwardedProtocol ?? providedProtocol; // @example "example.com,www2.example.com" => "example.com" const forwardedHostname = getFirstForwardedValue(req.headers['x-forwarded-host']); - const hostname = forwardedHostname ?? req.headers.host ?? req.headers[':authority']; + const providedHostname = req.headers.host ?? req.headers[':authority']; + const hostname = forwardedHostname ?? providedHostname; // @example "443,8080,80" => "443" const port = getFirstForwardedValue(req.headers['x-forwarded-port']); - const portInHostname = typeof hostname === 'string' && /:\d+$/.test(hostname); - const hostnamePort = portInHostname ? hostname : `${hostname}${port ? `:${port}` : ''}`; + let url: URL; + try { + const hostnamePort = getHostnamePort(hostname, port); + url = new URL(`${protocol}://${hostnamePort}${req.url}`); + } catch { + // Fallback to the provided hostname and port + const hostnamePort = getHostnamePort(providedHostname, port); + url = new URL(`${providedProtocol}://${hostnamePort}`); + } - const url = `${protocol}://${hostnamePort}${req.url}`; const options: RequestInit = { method: req.method || 'GET', headers: makeRequestHeaders(req), @@ -161,6 +169,12 @@ export class NodeApp extends App { } } +function getHostnamePort(hostname: string | string[] | undefined, port?: string): string { + const portInHostname = typeof hostname === 'string' && /:\d+$/.test(hostname); + const hostnamePort = portInHostname ? hostname : `${hostname}${port ? `:${port}` : ''}`; + return hostnamePort; +} + function makeRequestHeaders(req: NodeRequest): Headers { const headers = new Headers(); for (const [name, value] of Object.entries(req.headers)) { diff --git a/packages/astro/test/units/app/node.test.js b/packages/astro/test/units/app/node.test.js index d11a0a8aa0..05c11f2463 100644 --- a/packages/astro/test/units/app/node.test.js +++ b/packages/astro/test/units/app/node.test.js @@ -86,6 +86,17 @@ describe('NodeApp', () => { }); assert.equal(result.url, 'https://example.com/'); }); + + it('bad values are ignored and fallback to host header', () => { + const result = NodeApp.createRequest({ + ...mockNodeRequest, + headers: { + host: 'example.com', + 'x-forwarded-host': ':123' + }, + }); + assert.equal(result.url, 'https://example.com/'); + }); }); describe('x-forwarded-proto', () => {