fix: classify env gateway URL overrides with callGateway semantics in resolveGatewayTarget
When OPENCLAW_GATEWAY_URL/CLAWDBOT_GATEWAY_URL is set to a valid remote URL that doesn't match gateway.remote.url (or has a non-root path like /ws), validateGatewayUrlOverrideForAgentTools throws and the old code silently fell through to config-based resolution, returning undefined (local). But callGateway/buildGatewayConnectionDetails still uses the env URL verbatim, so the actual call goes remote while resolveGatewayTarget returned local — causing gateway-tool to forward live deliveryContext into remote config.apply / config.patch / update.run writes, which can misroute or leak post-restart wake messages across hosts. Fix: when validateGatewayUrlOverrideForAgentTools throws for an env URL override, fall back to hostname-based loopback detection instead of silently treating the target as local. Only truly malformed URLs (that new URL() cannot parse) fall through to config-based resolution. Adds tests for: - env-only remote URL not matching gateway.remote.url → 'remote' - env URL with no configured remote URL → 'remote' - env URL with /ws path → 'remote' - loopback env URL with /ws path → 'local'
This commit is contained in:
parent
3c914b8f38
commit
d629945255
@ -109,6 +109,39 @@ describe("resolveGatewayTarget – env URL override classification", () => {
|
||||
expect(resolveGatewayTarget()).toBe("remote");
|
||||
});
|
||||
|
||||
it("classifies env-only remote URL (not matching gateway.remote.url) as 'remote'", () => {
|
||||
// callGateway uses the env URL as-is even when validateGatewayUrlOverrideForAgentTools
|
||||
// rejects it (different host than configured gateway.remote.url). Must not leak
|
||||
// deliveryContext into a remote call by falling back to 'local'.
|
||||
process.env.OPENCLAW_GATEWAY_URL = "wss://other-host.example.com";
|
||||
setConfig({
|
||||
gateway: { mode: "remote", remote: { url: "wss://remote.example.com" } },
|
||||
});
|
||||
expect(resolveGatewayTarget()).toBe("remote");
|
||||
});
|
||||
|
||||
it("classifies env-only remote URL with no configured gateway.remote.url as 'remote'", () => {
|
||||
// callGateway picks up the env URL even when gateway.remote.url is absent.
|
||||
process.env.OPENCLAW_GATEWAY_URL = "wss://remote.example.com";
|
||||
setConfig({});
|
||||
expect(resolveGatewayTarget()).toBe("remote");
|
||||
});
|
||||
|
||||
it("classifies env URL with /ws path (rejected by allowlist) as 'remote'", () => {
|
||||
// URLs with non-root paths are rejected by validateGatewayUrlOverrideForAgentTools but
|
||||
// callGateway/buildGatewayConnectionDetails still use them verbatim. Classify correctly.
|
||||
process.env.OPENCLAW_GATEWAY_URL = "wss://remote.example.com/ws";
|
||||
setConfig({});
|
||||
expect(resolveGatewayTarget()).toBe("remote");
|
||||
});
|
||||
|
||||
it("classifies loopback env URL with /ws path (rejected by allowlist) as 'local'", () => {
|
||||
// Even with a non-root path, loopback targets remain local.
|
||||
process.env.OPENCLAW_GATEWAY_URL = "ws://127.0.0.1:18789/ws";
|
||||
setConfig({});
|
||||
expect(resolveGatewayTarget()).toBe("local");
|
||||
});
|
||||
|
||||
it("OPENCLAW_GATEWAY_URL takes precedence over env CLAWDBOT_GATEWAY_URL", () => {
|
||||
process.env.OPENCLAW_GATEWAY_URL = "ws://127.0.0.1:18789";
|
||||
process.env.CLAWDBOT_GATEWAY_URL = "wss://remote.example.com";
|
||||
|
||||
@ -144,7 +144,19 @@ export function resolveGatewayTarget(opts?: GatewayCallOptions): GatewayOverride
|
||||
urlOverride: envUrlOverride,
|
||||
}).target;
|
||||
} catch {
|
||||
// Malformed or security-rejected env URL; fall through to config-based resolution.
|
||||
// URL rejected by the agent-tools allowlist (e.g. non-loopback URL not matching
|
||||
// gateway.remote.url, or URL with a non-root path like /ws). callGateway /
|
||||
// buildGatewayConnectionDetails will still use this env URL as-is, so we must
|
||||
// classify based on the actual target host — not silently fall back to local.
|
||||
try {
|
||||
const parsed = new URL(envUrlOverride.trim());
|
||||
// Normalize IPv6 brackets: "[::1]" → "::1"
|
||||
const host = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
||||
const isLoopback = host === "127.0.0.1" || host === "localhost" || host === "::1";
|
||||
return isLoopback ? "local" : "remote";
|
||||
} catch {
|
||||
// Truly malformed URL; callGateway will also fail. Fall through to config-based resolution.
|
||||
}
|
||||
}
|
||||
}
|
||||
// No env override. When mode=remote with a configured remote URL → truly remote.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user