-
-
Notifications
You must be signed in to change notification settings - Fork 69.4k
Cron announce delivery fails on loopback: isSecureWebSocketUrl rejects http:// protocol #38882
Description
Summary
Cron announce delivery fails on bind: "loopback" setups because the internal delivery path constructs http://127.0.0.1:<port> but isSecureWebSocketUrl() only accepts ws:// and wss:// protocols. The http:// URL falls through all protocol checks and throws SECURITY ERROR: Gateway URL uses plaintext ws:// to a non-loopback address.
Environment
- OpenClaw version: 2026.3.2
- Setup: bare-metal VM (Ubuntu 24.04), systemd user service
- Config:
gateway.bind: "loopback",gateway.auth.mode: "token",OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1
Steps to reproduce
- Configure gateway with
bind: "loopback"andauth.mode: "token" - Create a cron job with
delivery.mode: "announce"anddelivery.channel: "telegram" - Wait for cron to fire (or force-run with
openclaw cron run <id> --url ws://127.0.0.1:18789 --token <token>) - Agent turn completes (
lastStatus: ok) but delivery fails (lastDelivered: false)
Expected behavior
Cron announce delivery should succeed — the gateway and subagent are co-located on loopback.
Actual behavior
Log shows:
Subagent completion direct announce failed: SECURITY ERROR: Gateway URL "http://127.0.0.1:18789"
uses plaintext ws:// to a non-loopback address.
The announce delivery path internally constructs http://127.0.0.1:18789 (HTTP protocol), but isSecureWebSocketUrl() in net-Bf8Z-b6p.js (and 6 other chunk files where it's duplicated) only checks for ws: and wss: protocols:
// net-Bf8Z-b6p.js line 249
function isSecureWebSocketUrl(url, opts) {
// ...
if (parsed.protocol === "wss:") return true;
if (parsed.protocol !== "ws:") return false; // ← http: hits this, returns false
if (isLoopbackHost(parsed.hostname)) return true;
// ...
}When parsed.protocol is "http:", the second check (!== "ws:") returns false, so the loopback check on line 258 is never reached.
Root cause
The cron announce delivery uses the HTTP chatCompletions endpoint (http://127.0.0.1:18789) rather than the WebSocket endpoint (ws://127.0.0.1:18789). The security check doesn't account for http:// URLs even though they carry the same loopback safety properties.
Workaround
Patch isSecureWebSocketUrl() in all 7 dist files to accept http: and https: protocols:
if (parsed.protocol === "wss:" || parsed.protocol === "https:") return true;
if (parsed.protocol !== "ws:" && parsed.protocol !== "http:") return false;Files to patch: net-Bf8Z-b6p.js, chrome-BJ1olan3.js, chrome-CdadObm0.js, daemon-cli.js, pi-embedded-CtM2Mrrj.js, pi-embedded-DgYXShcG.js, ws-j0__CWxN.js
Clear compile cache after: rm -rf /var/tmp/openclaw-compile-cache/*
Patch is overwritten on next npm update.
Related issues
- [Bug]: Internal tool→gateway RPC calls incorrectly blocked by ws:// security check #22104 — Internal tool→gateway RPC blocked by ws:// check (fixed for
bind: "lan", notbind: "loopback") - Gateway: add gateway.internalUrl config for same-host sub-agent connections #22779 — Feature request for
gateway.internalUrl(open)