diff --git a/packages/cli/src/update.ts b/packages/cli/src/update.ts index aa4e66bb..23fff223 100644 --- a/packages/cli/src/update.ts +++ b/packages/cli/src/update.ts @@ -646,6 +646,15 @@ const MIGRATIONS: Migration[] = [ // Existing workspaces can invoke `wrnexus production` without generated-file changes. }, }, + { + version: "0.2.65", + id: "private-production-app-listeners", + description: + "Restricts workspace-managed production app servers to loopback so external clients must use the gateway.", + apply() { + // The production gateway applies the private bind address automatically. + }, + }, ]; /** Release tooling uses this to require an explicit migration entry per version. */ diff --git a/packages/dev-server/src/gateway.ts b/packages/dev-server/src/gateway.ts index a00a75e0..1f577906 100644 --- a/packages/dev-server/src/gateway.ts +++ b/packages/dev-server/src/gateway.ts @@ -337,6 +337,9 @@ export async function startGateway(opts: GatewayOptions): Promise({ port: resolvePort(opts.port), - hostname: opts.hostname ?? "0.0.0.0", + hostname: resolveProductionHostname(opts.hostname), development: false, maxRequestBodySize: opts.maxBodyBytes ?? 10 * 1024 * 1024, fetch: handlers.fetch, diff --git a/packages/dev-server/test/gateway.test.ts b/packages/dev-server/test/gateway.test.ts index d7343f89..22f721fd 100644 --- a/packages/dev-server/test/gateway.test.ts +++ b/packages/dev-server/test/gateway.test.ts @@ -6,6 +6,14 @@ import { gatewayProxyHeaders, gatewayRestartDelay, } from "../src/gateway.ts"; +import { resolveProductionHostname } from "../src/prod.ts"; + +test("gateway-managed production apps bind to loopback", () => { + expect(resolveProductionHostname(undefined, "127.0.0.1")).toBe("127.0.0.1"); + expect(resolveProductionHostname("0.0.0.0", "127.0.0.1")).toBe("127.0.0.1"); + expect(resolveProductionHostname("10.0.0.5", "")).toBe("10.0.0.5"); + expect(resolveProductionHostname(undefined, "")).toBe("0.0.0.0"); +}); test("gateway uses platform-safe hostname defaults", () => { expect(defaultGatewayHostname("development")).toBe("127.0.0.1");