Skip to content

Regression: bind=lan breaks browser tool self-connection due to #20803 security check #22047

@usedhonda

Description

@usedhonda

Summary

bind: "lan" configuration causes the browser tool (and any Gateway self-connection) to fail with a SECURITY ERROR after the plaintext ws:// block introduced in #20803.

Affected versions

Confirmed broken after commit 9edec67a1 (fix(security): block plaintext WebSocket connections to non-loopback addresses).

Root cause

Two commits are in conflict:

When bind: "lan" is set, localUrl becomes ws://<LAN_IP>:<port>. This passes the #11448 check but is then rejected by the #20803 security guard (isSecureWebSocketUrl returns false for non-loopback IPs).

Relevant code in src/gateway/call.ts:

const preferLan = bindMode === "lan";
const lanIPv4 = preferLan ? pickPrimaryLanIPv4() : undefined;
const localUrl =
  preferTailnet && tailnetIPv4
    ? `${scheme}://${tailnetIPv4}:${localPort}`
    : preferLan && lanIPv4
      ? `${scheme}://${lanIPv4}:${localPort}`   // <-- produces ws://192.168.x.x
      : `${scheme}://127.0.0.1:${localPort}`;

Impact

  • Any agent running on the same host as the Gateway with bind: "lan" cannot connect to its own Gateway via the browser tool or any callGateway code path.
  • Affected real-world use case: Chi agent on macmini trying to open NotebookLM via the browser tool.

Steps to reproduce

  1. Set gateway.bind: "lan" in openclaw.json
  2. Run any tool that calls buildGatewayConnectionDetails() (e.g. browser tool opening a URL)
  3. Observe: SECURITY ERROR: Gateway URL "ws://192.168.x.x:18789" uses plaintext ws:// to a non-loopback address.

Expected behavior

A self-connection from an agent running on the same host should always use ws://127.0.0.1:<port>, regardless of bind mode. The bind setting controls which interface the server listens on — it should not force clients on the same host to use the LAN IP.

Proposed fix

In buildGatewayConnectionDetails, decouple the server bind address from the client connection URL. For self-connections (same host), always prefer loopback:

// localUrl should always use loopback for self-connections.
// bind=lan/tailnet affects the *server* listen interface, not the *client* connect URL.
const localUrl = `${scheme}://127.0.0.1:${localPort}`;

If the LAN IP is needed for display purposes (QR code, onboarding hints, etc.), it can be exposed separately without affecting the actual connection URL.

Workaround (temporary)

None available without modifying config in an unsupported way (mode: "remote" + remote.url: "ws://127.0.0.1:<port>" may work but has untested side effects).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions