diff --git a/src/cline-sdk/cline-mcp-runtime-service.ts b/src/cline-sdk/cline-mcp-runtime-service.ts index 8607be8fe..b88a3e81c 100644 --- a/src/cline-sdk/cline-mcp-runtime-service.ts +++ b/src/cline-sdk/cline-mcp-runtime-service.ts @@ -15,7 +15,7 @@ import type { import { z } from "zod"; import type { RuntimeClineMcpServer } from "../core/api-contract"; -import { buildKanbanRuntimeUrl } from "../core/runtime-endpoint"; +import { getKanbanOauthRedirectOrigin } from "../core/runtime-endpoint"; import { lockedFileSystem } from "../fs/locked-file-system"; import { createClineMcpSettingsService, resolveMcpSettingsPath } from "./cline-mcp-settings-service"; import { @@ -440,7 +440,7 @@ class RuntimeMcpServerClient implements SdkMcpServerClient { serverName: this.server.name, redirectUrl: parseOauthSettings(this.oauthSettingsPath).servers[this.server.name]?.redirectUrl ?? - buildKanbanRuntimeUrl(OAUTH_CALLBACK_PATH), + getKanbanOauthRedirectOrigin() + OAUTH_CALLBACK_PATH, }); } @@ -546,7 +546,7 @@ class RuntimeMcpServerClient implements SdkMcpServerClient { } function buildMcpOauthCallbackUrl(requestId: string): string { - const callbackUrl = new URL(buildKanbanRuntimeUrl(OAUTH_CALLBACK_PATH)); + const callbackUrl = new URL(getKanbanOauthRedirectOrigin() + OAUTH_CALLBACK_PATH); callbackUrl.searchParams.set(OAUTH_CALLBACK_REQUEST_ID_PARAM, requestId); return callbackUrl.toString(); } diff --git a/src/core/runtime-endpoint.ts b/src/core/runtime-endpoint.ts index dff687b91..46d92078d 100644 --- a/src/core/runtime-endpoint.ts +++ b/src/core/runtime-endpoint.ts @@ -106,6 +106,12 @@ export function getKanbanRuntimeOrigin(): string { return `${scheme}://${getKanbanRuntimeHost()}:${getKanbanRuntimePort()}`; } +export function getKanbanOauthRedirectOrigin(): string { + const scheme = isKanbanRuntimeHttps() ? "https" : "http"; + const host = runtimeHost === "0.0.0.0" ? "127.0.0.1" : runtimeHost; + return `${scheme}://${host}:${getKanbanRuntimePort()}`; +} + export function getKanbanRuntimeWsOrigin(): string { const scheme = isKanbanRuntimeHttps() ? "wss" : "ws"; return `${scheme}://${getKanbanRuntimeHost()}:${getKanbanRuntimePort()}`; diff --git a/src/server/middleware.ts b/src/server/middleware.ts index acbb134ad..9c676824e 100644 --- a/src/server/middleware.ts +++ b/src/server/middleware.ts @@ -70,6 +70,10 @@ export function getAllowedHostHeaders(): ReadonlySet { if (isKanbanRemoteHost()) { addHostPort(boundHost); + if (boundHost === "0.0.0.0") { + addHostPort("localhost"); + addHostPort("127.0.0.1"); + } return allowed; } @@ -109,18 +113,22 @@ function rejectSocket(socket: Duplex): { end: boolean } { } export function handleHttpRequest(req: IncomingMessage, res: ServerResponse): { end: boolean } { - const hostDecision = evaluateHost({ - hostHeader: req.headers.host, - allowedHosts: getAllowedHostHeaders(), - }); - if (hostDecision.kind === "reject") { - return rejectRequest(res, "Host not allowed."); + const boundHost = getKanbanRuntimeHost().toLowerCase(); + if (boundHost !== "0.0.0.0") { + const hostDecision = evaluateHost({ + hostHeader: req.headers.host, + allowedHosts: getAllowedHostHeaders(), + }); + if (hostDecision.kind === "reject") { + return rejectRequest(res, "Host not allowed."); + } } + const allowedOrigin = boundHost === "0.0.0.0" && req.headers.origin ? req.headers.origin : getKanbanRuntimeOrigin(); const corsDecision = evaluateCors({ method: req.method, originHeader: req.headers.origin, - allowedOrigin: getKanbanRuntimeOrigin(), + allowedOrigin, }); switch (corsDecision.kind) { @@ -146,18 +154,23 @@ export function handleHttpRequest(req: IncomingMessage, res: ServerResponse): { } export function handleSocketUpgrade(request: IncomingMessage, socket: Duplex): { end: boolean } { - const hostDecision = evaluateHost({ - hostHeader: request.headers.host, - allowedHosts: getAllowedHostHeaders(), - }); - if (hostDecision.kind === "reject") { - return rejectSocket(socket); + const boundHost = getKanbanRuntimeHost().toLowerCase(); + if (boundHost !== "0.0.0.0") { + const hostDecision = evaluateHost({ + hostHeader: request.headers.host, + allowedHosts: getAllowedHostHeaders(), + }); + if (hostDecision.kind === "reject") { + return rejectSocket(socket); + } } + const allowedOrigin = + boundHost === "0.0.0.0" && request.headers.origin ? request.headers.origin : getKanbanRuntimeOrigin(); const corsDecision = evaluateCors({ method: request.method, originHeader: request.headers.origin, - allowedOrigin: getKanbanRuntimeOrigin(), + allowedOrigin, }); if (corsDecision.kind === "reject") { return rejectSocket(socket);