From 327f9158fa0b4f1b77a663c57ac0efd69b69c85d Mon Sep 17 00:00:00 2001 From: Aaron Aronchick Date: Thu, 5 Mar 2026 00:45:19 +0000 Subject: [PATCH] test: add tailscale/bind compatibility tests Cover the runtime validation that rejects tailscale serve/funnel when bind is not loopback, and verify the happy path (serve + loopback). Co-Authored-By: Claude Opus 4.6 --- src/gateway/server-runtime-config.test.ts | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/gateway/server-runtime-config.test.ts b/src/gateway/server-runtime-config.test.ts index 34cc4632670..86c62f9698b 100644 --- a/src/gateway/server-runtime-config.test.ts +++ b/src/gateway/server-runtime-config.test.ts @@ -232,6 +232,58 @@ describe("resolveGatewayRuntimeConfig", () => { }); }); + describe("tailscale/bind compatibility", () => { + it("rejects tailscale serve with non-loopback bind", async () => { + await expect( + resolveGatewayRuntimeConfig({ + cfg: { + gateway: { + bind: "lan" as const, + auth: TOKEN_AUTH, + tailscale: { mode: "serve" as const }, + controlUi: { allowedOrigins: ["https://control.example.com"] }, + }, + }, + port: 18789, + }), + ).rejects.toThrow("tailscale serve/funnel requires gateway bind=loopback (127.0.0.1)"); + }); + + it("rejects tailscale funnel with non-loopback bind", async () => { + await expect( + resolveGatewayRuntimeConfig({ + cfg: { + gateway: { + bind: "lan" as const, + auth: { + mode: "password" as const, + password: "test-password", + }, + tailscale: { mode: "funnel" as const }, + controlUi: { allowedOrigins: ["https://control.example.com"] }, + }, + }, + port: 18789, + }), + ).rejects.toThrow("tailscale serve/funnel requires gateway bind=loopback (127.0.0.1)"); + }); + + it("allows tailscale serve with loopback bind", async () => { + const result = await resolveGatewayRuntimeConfig({ + cfg: { + gateway: { + bind: "loopback" as const, + auth: TOKEN_AUTH, + tailscale: { mode: "serve" as const }, + }, + }, + port: 18789, + }); + expect(result.tailscaleMode).toBe("serve"); + expect(result.bindHost).toBe("127.0.0.1"); + }); + }); + describe("HTTP security headers", () => { it("resolves strict transport security header from config", async () => { const result = await resolveGatewayRuntimeConfig({