0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-04-07 23:41:43 -05:00

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 <contact@florian-lefebvre.dev>

---------

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>
This commit is contained in:
Matthew Phillips 2025-03-18 09:02:32 -04:00 committed by GitHub
parent 65f7e09901
commit 9cac9f3142
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 35 additions and 5 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Prevent bad value in x-forwarded-host from crashing request

View file

@ -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)) {

View file

@ -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', () => {