diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f8b1707e7..1934899a0de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -247,6 +247,7 @@ Docs: https://docs.openclaw.ai - Discord/native slash command auth: honor `commands.allowFrom.discord` (and `commands.allowFrom["*"]`) in guild slash-command pre-dispatch authorization so allowlisted senders are no longer incorrectly rejected as unauthorized. (#38794) Thanks @jskoiz and @thewilloftheshadow. - Outbound/message target normalization: ignore empty legacy `to`/`channelId` fields when explicit `target` is provided so valid target-based sends no longer fail legacy-param validation; includes regression coverage. (#38944) Thanks @Narcooo. - Models/auth token prompts: guard cancelled manual token prompts so `Symbol(clack:cancel)` values cannot be persisted into auth profiles; adds regression coverage for cancelled `models auth paste-token`. (#38951) Thanks @MumuTW. +- Gateway/loopback announce URLs: treat `http://` and `https://` aliases with the same loopback/private-network policy as websocket URLs so loopback cron announce delivery no longer fails secure URL validation. (#39064) Thanks @Narcooo. ## 2026.3.2 diff --git a/src/gateway/net.test.ts b/src/gateway/net.test.ts index 1faf727a856..f5ee5db9a8e 100644 --- a/src/gateway/net.test.ts +++ b/src/gateway/net.test.ts @@ -439,8 +439,10 @@ describe("isSecureWebSocketUrl", () => { // invalid URLs { input: "not-a-url", expected: false }, { input: "", expected: false }, - { input: "http://127.0.0.1:18789", expected: false }, - { input: "https://127.0.0.1:18789", expected: false }, + { input: "http://127.0.0.1:18789", expected: true }, + { input: "https://127.0.0.1:18789", expected: true }, + { input: "https://remote.example.com:18789", expected: true }, + { input: "http://remote.example.com:18789", expected: false }, ] as const; for (const testCase of cases) { @@ -451,6 +453,7 @@ describe("isSecureWebSocketUrl", () => { it("allows private ws:// only when opt-in is enabled", () => { const allowedWhenOptedIn = [ "ws://10.0.0.5:18789", + "http://10.0.0.5:18789", "ws://172.16.0.1:18789", "ws://192.168.1.100:18789", "ws://100.64.0.1:18789", diff --git a/src/gateway/net.ts b/src/gateway/net.ts index d57915fdcc0..db8779606a5 100644 --- a/src/gateway/net.ts +++ b/src/gateway/net.ts @@ -421,11 +421,17 @@ export function isSecureWebSocketUrl( return false; } - if (parsed.protocol === "wss:") { + // Node's ws client accepts http(s) URLs and normalizes them to ws(s). + // Treat those aliases the same way here so loopback cron announce delivery + // and TLS-backed https endpoints follow the same security policy. + const protocol = + parsed.protocol === "https:" ? "wss:" : parsed.protocol === "http:" ? "ws:" : parsed.protocol; + + if (protocol === "wss:") { return true; } - if (parsed.protocol !== "ws:") { + if (protocol !== "ws:") { return false; }