From 575df64c6cffab670a3ecf4c3e6d3c3125f99444 Mon Sep 17 00:00:00 2001 From: Harry Phan Date: Fri, 31 Jul 2026 13:29:04 +0700 Subject: [PATCH] fix(server): allow Railway's healthcheck Host, unblocking every deploy since the pairing PR The Host-header validation added in the security-hardening PR (d5e5fd9) only allowed localhost/127.0.0.1/[::1] and the deployment's own public domain. Railway's own healthcheck probe sends requests with a fixed, unrelated Host - healthcheck.railway.app - which that check had no reason to know about, so every healthcheck since that PR shipped got a 421 and Railway rolled the deploy back to the last one that could pass it. Confirmed via `railway logs`: the currently-active deployment is still from July 30 13:51 UTC (the Move Studio PR, #4) - both the security PR (#5) and the changelog PR (#6) built and pushed successfully but never went live, because their new server code rejected the one request that decides whether a deploy survives. Verified locally with RAILWAY_SERVICE_ID set (forces the cloud code path): Host: healthcheck.railway.app -> 200; Host: evil.com -> still 421. --- apps/server/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index ff39f43..b999678 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -65,6 +65,12 @@ function isAllowedHost(hostHeader: string | undefined): boolean { const allowedHostnames = new Set(['localhost', '127.0.0.1', '[::1]']); if (isCloud) { + // Railway's own internal healthcheck probe - sent with this fixed Host + // regardless of the service's actual public domain. Without it, Railway + // can never see this deployment as healthy and rolls back to the last + // one that could - this rule shipped once and immediately took down every + // deploy after it. + allowedHostnames.add('healthcheck.railway.app'); for (const domain of [process.env.RAILWAY_PUBLIC_DOMAIN, process.env.RAILWAY_STATIC_URL]) { if (domain) allowedHostnames.add(domain.replace(/^https?:\/\//, '')); }