From b1cf502a75eec2603561b60ab1115aa7b75d3a43 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 04:16:19 +0000 Subject: [PATCH 1/2] test(server): Tailscale Serve targets the interface a specific --host binds Failing reproduction for #34733. AI-assisted; verified with the tests and tools listed above. --- apps/server/src/startupAccess.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/server/src/startupAccess.test.ts b/apps/server/src/startupAccess.test.ts index fd56b4a4c86f3..a48bb5263f8ec 100644 --- a/apps/server/src/startupAccess.test.ts +++ b/apps/server/src/startupAccess.test.ts @@ -1,9 +1,11 @@ import type * as NodeOS from "node:os"; import { describe, expect, it } from "@effect/vitest"; +import * as NetAddress from "effect/net/NetAddress"; import { acceptsPrivateNetworkConnection, + loopbackUrlHostForAddress, resolveHeadlessConnectionString, } from "./startupAccess.ts"; @@ -55,6 +57,18 @@ it("prefers a LAN address over a tailnet one for a private-network pairing URL", ).toBe("http://192.168.1.42:3773"); }); +it("targets the bound interface when the listener binds a specific address", () => { + const hostFor = (ip: string) => + loopbackUrlHostForAddress(NetAddress.inetAddressFromIpStringUnsafe(ip, 3773)); + + expect(hostFor("0.0.0.0")).toBe("127.0.0.1"); + expect(hostFor("::")).toBe("127.0.0.1"); + expect(hostFor("::1")).toBe("[::1]"); + expect(hostFor("127.0.0.1")).toBe("127.0.0.1"); + expect(hostFor("127.0.0.2")).toBe("127.0.0.2"); + expect(hostFor("100.101.102.103")).toBe("100.101.102.103"); +}); + describe("acceptsPrivateNetworkConnection", () => { const accepts = (localAddress: string | undefined, remoteAddress: string | undefined) => acceptsPrivateNetworkConnection({ localAddress, remoteAddress }); From a083b0bf7e7d1dbffbcf8623ff19a3191d43838c Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 12 Nov 2026 04:19:45 +0000 Subject: [PATCH 2/2] fix(server): Tailscale Serve forwards to the address a specific --host binds loopbackUrlHostForAddress returned 127.0.0.1 for every listener except IPv6 loopback, so Tailscale Serve and the T3 Connect managed-tunnel origin pointed at a socket the server never opened when --host named one interface. Use the bound address unless the listener is a wildcard. Fixes pingdotgg/t3code#34733 AI-assisted; verified with the tests and tools listed above. --- apps/server/src/startupAccess.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/server/src/startupAccess.ts b/apps/server/src/startupAccess.ts index bcbfedff60893..f393faecebf91 100644 --- a/apps/server/src/startupAccess.ts +++ b/apps/server/src/startupAccess.ts @@ -71,9 +71,9 @@ export const acceptsPrivateNetworkConnection = (socket: { ); export const loopbackUrlHostForAddress = (address: NetAddress.InetAddress): string => - address._tag === "InetAddressV6" && NetAddress.isLoopback(address.address) - ? "[::1]" - : "127.0.0.1"; + NetAddress.isUnspecified(address.address) + ? "127.0.0.1" + : NetAddress.formatUrlHostString(NetAddress.formatIp(address.address)); const normalizeHost = (host: string): string => host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;