Skip to content

Cron announce delivery fails on loopback: isSecureWebSocketUrl rejects http:// protocol #38882

@dertbv

Description

@dertbv

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

  1. Configure gateway with bind: "loopback" and auth.mode: "token"
  2. Create a cron job with delivery.mode: "announce" and delivery.channel: "telegram"
  3. Wait for cron to fire (or force-run with openclaw cron run <id> --url ws://127.0.0.1:18789 --token <token>)
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions