From dfa3605bee235dcafbe4aeb67c1d07b945231a66 Mon Sep 17 00:00:00 2001 From: Joe Harouni Date: Sun, 15 Feb 2026 22:59:50 -0600 Subject: [PATCH] fix(browser): rewrite 0.0.0.0 and [::] wildcard addresses in CDP WebSocket URLs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Containerized browsers (e.g. browserless in Docker) report `ws://0.0.0.0:` in their `/json/version` response. `normalizeCdpWsUrl` rewrites loopback WS hosts to the external CDP host:port, but `0.0.0.0` and `[::]` were not treated as addresses needing rewriting, causing OpenClaw to try connecting to `ws://0.0.0.0:3000` literally — which always fails. Fixes #17752 Co-Authored-By: Claude Opus 4.6 --- src/browser/cdp.test.ts | 16 ++++++++++++++++ src/browser/cdp.ts | 6 +++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/browser/cdp.test.ts b/src/browser/cdp.test.ts index 9976869b9dd..44dbdd70e30 100644 --- a/src/browser/cdp.test.ts +++ b/src/browser/cdp.test.ts @@ -320,6 +320,22 @@ describe("cdp", () => { expect(normalized).toBe("wss://user:pass@example.com/devtools/browser/ABC?token=abc"); }); + it("rewrites 0.0.0.0 wildcard bind address to remote CDP host", () => { + const normalized = normalizeCdpWsUrl( + "ws://0.0.0.0:3000/devtools/browser/ABC", + "http://192.168.1.202:18850?token=secret", + ); + expect(normalized).toBe("ws://192.168.1.202:18850/devtools/browser/ABC?token=secret"); + }); + + it("rewrites :: wildcard bind address to remote CDP host", () => { + const normalized = normalizeCdpWsUrl( + "ws://[::]:3000/devtools/browser/ABC", + "http://192.168.1.202:18850", + ); + expect(normalized).toBe("ws://192.168.1.202:18850/devtools/browser/ABC"); + }); + it("upgrades ws to wss when CDP uses https", () => { const normalized = normalizeCdpWsUrl( "ws://production-sfo.browserless.io", diff --git a/src/browser/cdp.ts b/src/browser/cdp.ts index 1f078bb4a04..d8b9994089b 100644 --- a/src/browser/cdp.ts +++ b/src/browser/cdp.ts @@ -19,7 +19,11 @@ export { export function normalizeCdpWsUrl(wsUrl: string, cdpUrl: string): string { const ws = new URL(wsUrl); const cdp = new URL(cdpUrl); - if (isLoopbackHost(ws.hostname) && !isLoopbackHost(cdp.hostname)) { + // Treat 0.0.0.0 and :: as wildcard bind addresses that need rewriting. + // Containerized browsers (e.g. browserless) report ws://0.0.0.0: + // in /json/version — these must be rewritten to the external cdpUrl host:port. + const isWildcardBind = ws.hostname === "0.0.0.0" || ws.hostname === "[::]"; + if ((isLoopbackHost(ws.hostname) || isWildcardBind) && !isLoopbackHost(cdp.hostname)) { ws.hostname = cdp.hostname; const cdpPort = cdp.port || (cdp.protocol === "https:" ? "443" : "80"); if (cdpPort) {