From 45a525d374de39aeabe525d0a8a4e1cb07354d10 Mon Sep 17 00:00:00 2001 From: Aaron Aronchick Date: Fri, 6 Mar 2026 06:23:35 +0000 Subject: [PATCH] fix(config): allow custom bind with loopback IP for tailscale compat validator --- src/gateway/server-methods/config.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/gateway/server-methods/config.ts b/src/gateway/server-methods/config.ts index 1623a148509..b4ac8c32d32 100644 --- a/src/gateway/server-methods/config.ts +++ b/src/gateway/server-methods/config.ts @@ -1,4 +1,5 @@ import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../../agents/agent-scope.js"; +import { isLoopbackHost } from "../net.js"; import { listChannelPlugins } from "../../channels/plugins/index.js"; import { CONFIG_PATH, @@ -249,7 +250,7 @@ function loadSchemaWithPlugins(): ConfigSchemaResponse { * which defaults to "loopback"). Rejecting at config-write time prevents the * gateway from entering an unrecoverable crash loop on next restart. */ -function validateTailscaleBindCompat(config: OpenClawConfig): string | null { +export function validateTailscaleBindCompat(config: OpenClawConfig): string | null { const tailscaleMode = config.gateway?.tailscale?.mode; if (tailscaleMode !== "serve" && tailscaleMode !== "funnel") { return null; @@ -258,6 +259,15 @@ function validateTailscaleBindCompat(config: OpenClawConfig): string | null { if (bind === "loopback") { return null; } + // A custom bind with a loopback IP is equivalent to bind=loopback at runtime + // (server-runtime-config.ts uses isLoopbackHost on the resolved IP). Allow it + // at write-time too so we don't reject a valid config. + if (bind === "custom") { + const customBindHost = config.gateway?.customBindHost?.trim(); + if (customBindHost && isLoopbackHost(customBindHost)) { + return null; + } + } return `gateway.tailscale.mode="${tailscaleMode}" requires gateway.bind="loopback", but gateway.bind="${bind}". Change gateway.bind to "loopback" or set gateway.tailscale.mode to "off".`; }