From 1eb8cfd273d6cf3e6a91799a29a3d2e7ce9a8f00 Mon Sep 17 00:00:00 2001 From: brunorezio Date: Sat, 25 Jul 2026 22:27:31 +0100 Subject: [PATCH] compare the Host header against the origin authority, not as a suffix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The no-Origin branch asked whether the configured origin ends with the client-supplied Host, so `Host: dev` matched https://rezio.pastilhas.dev — as did `pastilhas.dev` and `o.pastilhas.dev`. Match the URL authority exactly instead. Officer always runs behind an HTTPS reverse proxy, so the forwarded Host is expected to equal PUBLIC_URL's authority. Co-Authored-By: Claude Opus 5 --- src/servers/_middlewares/origin-validation.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/servers/_middlewares/origin-validation.ts b/src/servers/_middlewares/origin-validation.ts index 65fcfcb7..9640ef6b 100644 --- a/src/servers/_middlewares/origin-validation.ts +++ b/src/servers/_middlewares/origin-validation.ts @@ -16,6 +16,11 @@ const PUBLIC_ORIGIN = (() => { const WEB_ORIGINS: string[] = PUBLIC_ORIGIN ? [PUBLIC_ORIGIN] : []; +// Host authorities (`example.com`, or `example.com:8080` off the default port) for the same origins. +// Officer always sits behind an HTTPS reverse proxy, so the proxy's `Host` header is expected to +// match PUBLIC_URL's authority exactly. +const WEB_HOSTS: string[] = WEB_ORIGINS.map((o) => new URL(o).host); + const CHROME_EXTENSIONS: string[] = [ // 'chrome-extension://' ]; @@ -41,7 +46,7 @@ export function isOriginAllowed(origin: string | undefined, host?: string): bool } if (host) { - return WEB_ORIGINS.some((o) => o.endsWith(host)); + return WEB_HOSTS.includes(host); } return false;