refactor(gateway): dedupe origin seeding and plugin route auth matching

This commit is contained in:
Peter Steinberger
2026-03-02 00:42:15 +00:00
parent b81e1b902d
commit cef5fae0a2
12 changed files with 411 additions and 293 deletions

View File

@@ -0,0 +1,91 @@
import type { OpenClawConfig } from "./config.js";
import { DEFAULT_GATEWAY_PORT } from "./paths.js";
export type GatewayNonLoopbackBindMode = "lan" | "tailnet" | "custom";
export function isGatewayNonLoopbackBindMode(bind: unknown): bind is GatewayNonLoopbackBindMode {
return bind === "lan" || bind === "tailnet" || bind === "custom";
}
export function hasConfiguredControlUiAllowedOrigins(params: {
allowedOrigins: unknown;
dangerouslyAllowHostHeaderOriginFallback: unknown;
}): boolean {
if (params.dangerouslyAllowHostHeaderOriginFallback === true) {
return true;
}
return (
Array.isArray(params.allowedOrigins) &&
params.allowedOrigins.some((origin) => typeof origin === "string" && origin.trim().length > 0)
);
}
export function resolveGatewayPortWithDefault(
port: unknown,
fallback = DEFAULT_GATEWAY_PORT,
): number {
return typeof port === "number" && port > 0 ? port : fallback;
}
export function buildDefaultControlUiAllowedOrigins(params: {
port: number;
bind: unknown;
customBindHost?: string;
}): string[] {
const origins = new Set<string>([
`http://localhost:${params.port}`,
`http://127.0.0.1:${params.port}`,
]);
const customBindHost = params.customBindHost?.trim();
if (params.bind === "custom" && customBindHost) {
origins.add(`http://${customBindHost}:${params.port}`);
}
return [...origins];
}
export function ensureControlUiAllowedOriginsForNonLoopbackBind(
config: OpenClawConfig,
opts?: { defaultPort?: number; requireControlUiEnabled?: boolean },
): {
config: OpenClawConfig;
seededOrigins: string[] | null;
bind: GatewayNonLoopbackBindMode | null;
} {
const bind = config.gateway?.bind;
if (!isGatewayNonLoopbackBindMode(bind)) {
return { config, seededOrigins: null, bind: null };
}
if (opts?.requireControlUiEnabled && config.gateway?.controlUi?.enabled === false) {
return { config, seededOrigins: null, bind };
}
if (
hasConfiguredControlUiAllowedOrigins({
allowedOrigins: config.gateway?.controlUi?.allowedOrigins,
dangerouslyAllowHostHeaderOriginFallback:
config.gateway?.controlUi?.dangerouslyAllowHostHeaderOriginFallback,
})
) {
return { config, seededOrigins: null, bind };
}
const port = resolveGatewayPortWithDefault(config.gateway?.port, opts?.defaultPort);
const seededOrigins = buildDefaultControlUiAllowedOrigins({
port,
bind,
customBindHost: config.gateway?.customBindHost,
});
return {
config: {
...config,
gateway: {
...config.gateway,
controlUi: {
...config.gateway?.controlUi,
allowedOrigins: seededOrigins,
},
},
},
seededOrigins,
bind,
};
}

View File

@@ -1,3 +1,9 @@
import {
buildDefaultControlUiAllowedOrigins,
hasConfiguredControlUiAllowedOrigins,
isGatewayNonLoopbackBindMode,
resolveGatewayPortWithDefault,
} from "./gateway-control-ui-origins.js";
import {
ensureAgentEntry,
ensureRecord,
@@ -31,34 +37,30 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_3: LegacyConfigMigration[] = [
return;
}
const bind = gateway.bind;
if (bind !== "lan" && bind !== "tailnet" && bind !== "custom") {
if (!isGatewayNonLoopbackBindMode(bind)) {
return;
}
const controlUi = getRecord(gateway.controlUi) ?? {};
const existingOrigins = controlUi.allowedOrigins;
const hasConfiguredOrigins =
Array.isArray(existingOrigins) &&
existingOrigins.some((origin) => typeof origin === "string" && origin.trim().length > 0);
if (hasConfiguredOrigins) {
return; // already configured
}
if (controlUi.dangerouslyAllowHostHeaderOriginFallback === true) {
return; // already opted into fallback
}
const port =
typeof gateway.port === "number" && gateway.port > 0 ? gateway.port : DEFAULT_GATEWAY_PORT;
const origins = new Set<string>([`http://localhost:${port}`, `http://127.0.0.1:${port}`]);
if (
bind === "custom" &&
typeof gateway.customBindHost === "string" &&
gateway.customBindHost.trim()
hasConfiguredControlUiAllowedOrigins({
allowedOrigins: controlUi.allowedOrigins,
dangerouslyAllowHostHeaderOriginFallback:
controlUi.dangerouslyAllowHostHeaderOriginFallback,
})
) {
origins.add(`http://${gateway.customBindHost.trim()}:${port}`);
return;
}
gateway.controlUi = { ...controlUi, allowedOrigins: [...origins] };
const port = resolveGatewayPortWithDefault(gateway.port, DEFAULT_GATEWAY_PORT);
const origins = buildDefaultControlUiAllowedOrigins({
port,
bind,
customBindHost:
typeof gateway.customBindHost === "string" ? gateway.customBindHost : undefined,
});
gateway.controlUi = { ...controlUi, allowedOrigins: origins };
raw.gateway = gateway;
changes.push(
`Seeded gateway.controlUi.allowedOrigins ${JSON.stringify([...origins])} for bind=${String(bind)}. ` +
`Seeded gateway.controlUi.allowedOrigins ${JSON.stringify(origins)} for bind=${String(bind)}. ` +
"Required since v2026.2.26. Add other machine origins to gateway.controlUi.allowedOrigins if needed.",
);
},