diff --git a/server/src/__tests__/runtime-api.test.ts b/server/src/__tests__/runtime-api.test.ts index df628f66..5e46bbb0 100644 --- a/server/src/__tests__/runtime-api.test.ts +++ b/server/src/__tests__/runtime-api.test.ts @@ -17,6 +17,17 @@ describe("runtime API discovery", () => { ).toBe("https://paperclip.example.com"); }); + it("prefers the loopback bind host over allowed hostnames for the primary runtime URL", () => { + expect( + choosePrimaryRuntimeApiUrl({ + authPublicBaseUrl: null, + allowedHostnames: ["192.168.1.50"], + bindHost: "127.0.0.1", + port: 3100, + }), + ).toBe("http://127.0.0.1:3100"); + }); + it("builds ordered callback candidates from explicit, allowed, bind, and interface hosts", () => { expect( buildRuntimeApiCandidateUrls({ diff --git a/server/src/__tests__/server-startup-feedback-export.test.ts b/server/src/__tests__/server-startup-feedback-export.test.ts index f51c09f3..410f550f 100644 --- a/server/src/__tests__/server-startup-feedback-export.test.ts +++ b/server/src/__tests__/server-startup-feedback-export.test.ts @@ -347,6 +347,21 @@ describe("startServer PAPERCLIP_API_URL handling", () => { expect(process.env.PAPERCLIP_API_URL).toBe("http://127.0.0.1:3210"); }); + it("keeps loopback as the runtime API URL when allowed hostnames are present", async () => { + loadConfigMock.mockReturnValueOnce(buildTestConfig({ + allowedHostnames: ["192.168.1.50"], + })); + + const started = await startServer(); + + expect(started.apiUrl).toBe("http://127.0.0.1:3210"); + expect(process.env.PAPERCLIP_RUNTIME_API_URL).toBe("http://127.0.0.1:3210"); + expect(process.env.PAPERCLIP_API_URL).toBe("http://127.0.0.1:3210"); + expect(JSON.parse(process.env.PAPERCLIP_RUNTIME_API_CANDIDATES_JSON ?? "[]")).toEqual( + expect.arrayContaining(["http://127.0.0.1:3210", "http://192.168.1.50:3210"]), + ); + }); + it("rewrites explicit-port auth public URLs when detect-port selects a new port", async () => { loadConfigMock.mockReturnValueOnce(buildTestConfig({ port: 3100, diff --git a/server/src/runtime-api.ts b/server/src/runtime-api.ts index 96c8ab75..bf4caf36 100644 --- a/server/src/runtime-api.ts +++ b/server/src/runtime-api.ts @@ -61,6 +61,11 @@ export function choosePrimaryRuntimeApiUrl(input: { } } + const bindHost = normalizeHost(input.bindHost); + if (bindHost && !isWildcardHost(bindHost) && isLoopbackHost(bindHost)) { + return formatOrigin("http:", bindHost, input.port); + } + const allowedHostname = input.allowedHostnames .map((value) => value.trim()) .find(Boolean); @@ -68,7 +73,6 @@ export function choosePrimaryRuntimeApiUrl(input: { return formatOrigin("http:", allowedHostname, input.port); } - const bindHost = normalizeHost(input.bindHost); if (bindHost && !isWildcardHost(bindHost)) { return formatOrigin("http:", bindHost, input.port); }