diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 831e9381..c2085a0a 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -127,10 +127,18 @@ function parsePort(input: string): number { } function resolveHost(input: string | undefined): string { - if (input && input.trim() === "0.0.0.0") { + const trimmed = input?.trim() + if (!trimmed) return DEFAULT_HOST + + if (trimmed === "0.0.0.0") { return "0.0.0.0" } - return DEFAULT_HOST + + if (trimmed === "localhost") { + return DEFAULT_HOST + } + + return trimmed } async function main() { @@ -149,11 +157,13 @@ async function main() { const eventBus = new EventBus(eventLogger) + const isLoopbackHost = (host: string) => host === "127.0.0.1" || host === "::1" || host.startsWith("127.") + const serverMeta: ServerMeta = { httpBaseUrl: `http://${options.host}:${options.port}`, eventsUrl: `/api/events`, host: options.host, - listeningMode: options.host === "0.0.0.0" ? "all" : "local", + listeningMode: isLoopbackHost(options.host) ? "local" : "all", port: options.port, hostLabel: options.host, workspaceRoot: options.rootDir, diff --git a/packages/server/src/server/http-server.ts b/packages/server/src/server/http-server.ts index a92f01e3..65ef3472 100644 --- a/packages/server/src/server/http-server.ts +++ b/packages/server/src/server/http-server.ts @@ -93,6 +93,7 @@ export function createHttpServer(deps: HttpServerDeps) { }) const allowedDevOrigins = new Set(["http://localhost:3000", "http://127.0.0.1:3000"]) + const isLoopbackHost = (host: string) => host === "127.0.0.1" || host === "::1" || host.startsWith("127.") app.register(cors, { origin: (origin, cb) => { @@ -113,10 +114,17 @@ export function createHttpServer(deps: HttpServerDeps) { return } - if (allowedDevOrigins.has(origin)) { - cb(null, true) - return - } + if (allowedDevOrigins.has(origin)) { + cb(null, true) + return + } + + // When we bind to a non-loopback host (e.g., 0.0.0.0 or LAN IP), allow cross-origin UI access. + if (deps.host === "0.0.0.0" || !isLoopbackHost(deps.host)) { + cb(null, true) + return + } + cb(null, false) }, @@ -275,13 +283,13 @@ export function createHttpServer(deps: HttpServerDeps) { } } - const displayHost = deps.host === "0.0.0.0" ? "127.0.0.1" : deps.host === "127.0.0.1" ? "localhost" : deps.host + const displayHost = deps.host === "127.0.0.1" ? "localhost" : deps.host const serverUrl = `http://${displayHost}:${actualPort}` deps.serverMeta.httpBaseUrl = serverUrl deps.serverMeta.host = deps.host deps.serverMeta.port = actualPort - deps.serverMeta.listeningMode = deps.host === "0.0.0.0" ? "all" : "local" + deps.serverMeta.listeningMode = deps.host === "0.0.0.0" || !isLoopbackHost(deps.host) ? "all" : "local" deps.logger.info({ port: actualPort, host: deps.host }, "HTTP server listening") console.log(`CodeNomad Server is ready at ${serverUrl}`) diff --git a/packages/server/src/server/routes/meta.ts b/packages/server/src/server/routes/meta.ts index d7161985..40782181 100644 --- a/packages/server/src/server/routes/meta.ts +++ b/packages/server/src/server/routes/meta.ts @@ -17,7 +17,7 @@ function buildMetaResponse(meta: ServerMeta): ServerMeta { return { ...meta, port, - listeningMode: meta.host === "0.0.0.0" ? "all" : "local", + listeningMode: meta.host === "0.0.0.0" || !isLoopbackHost(meta.host) ? "all" : "local", addresses, } } @@ -35,6 +35,10 @@ function resolvePort(meta: ServerMeta): number { } } +function isLoopbackHost(host: string): boolean { + return host === "127.0.0.1" || host === "::1" || host.startsWith("127.") +} + function resolveAddresses(port: number, host: string): NetworkAddress[] { const interfaces = os.networkInterfaces() const seen = new Set()