mirror of
https://github.com/HeyPuter/puter.git
synced 2026-11-03 12:33:07 +00:00
fix(hosting): choose the site and subdomain from Host, not X-Forwarded-Host
With trust proxy on, express derives req.hostname and req.subdomains from X-Forwarded-Host, which any client can send, while host validation reads the raw Host. A request could serve one hosted site's files on the main domain, or reach the API on a hosting host. The header is now dropped before routing. Fixes PUT-2272 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
58456ae87c
commit
2f35dadb28
2 files changed
+64
No files matched your search
@@ -472,6 +472,59 @@ describe('PuterServer host header validation — permissive modes', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('PuterServer behind a trusted proxy', () => {
|
||||
let server: PuterServer;
|
||||
let port: number;
|
||||
|
||||
beforeAll(async () => {
|
||||
port = await allocateEphemeralPort();
|
||||
server = await setupTestServer(
|
||||
{
|
||||
port,
|
||||
domain: 'puter.localhost',
|
||||
origin: `http://puter.localhost:${port}`,
|
||||
api_base_url: `http://api.puter.localhost:${port}`,
|
||||
allow_all_host_values: false,
|
||||
allow_no_host_header: false,
|
||||
custom_domains_enabled: false,
|
||||
static_hosting_domain: 'site.puter.localhost',
|
||||
trust_proxy: 1,
|
||||
} as unknown as IConfig,
|
||||
{ listen: true },
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server?.shutdown();
|
||||
});
|
||||
|
||||
it('serves the main domain whatever X-Forwarded-Host names', async () => {
|
||||
const res = await rawRequest(port, '/healthcheck', {
|
||||
host: `puter.localhost:${port}`,
|
||||
'x-forwarded-host': 'nosuchsite.site.puter.localhost',
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers['x-frame-options']).toBe('SAMEORIGIN');
|
||||
});
|
||||
|
||||
it('keeps a hosted-site host on site hosting', async () => {
|
||||
const res = await rawRequest(port, '/healthcheck', {
|
||||
host: `nosuchsite.site.puter.localhost:${port}`,
|
||||
'x-forwarded-host': 'api.puter.localhost',
|
||||
});
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it('reads subdomains from Host', async () => {
|
||||
const res = await rawRequest(port, '/healthcheck', {
|
||||
host: `api.puter.localhost:${port}`,
|
||||
'x-forwarded-host': 'puter.localhost',
|
||||
});
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers['x-frame-options']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PuterServer host header validation — custom domains with every host accepted', () => {
|
||||
let server: PuterServer;
|
||||
let port: number;
|
||||
|
||||
@@ -413,6 +413,17 @@ export class PuterServer {
|
||||
* `#materializeRoute` as those options ship.
|
||||
*/
|
||||
#installGlobalMiddleware() {
|
||||
// -- Forwarded host ------------------------------------------
|
||||
// With `trust proxy` set, express derives `req.hostname` and
|
||||
// `req.subdomains` from X-Forwarded-Host, which any client can send.
|
||||
// Dropping it keeps them on Host, the header host validation checks,
|
||||
// so a request can't pick a different site or subdomain router than
|
||||
// the host it was sent to.
|
||||
this.#app.use((req, _res, next) => {
|
||||
delete req.headers['x-forwarded-host'];
|
||||
next();
|
||||
});
|
||||
|
||||
// -- Egress metering -----------------------------------------
|
||||
// First, so the byte counter wraps `res.write` before compression
|
||||
// does and therefore counts what actually goes out rather than what
|
||||
|
||||
Reference in new issue
Block a user