From bc356cc8c2beaa747c71dd86cceab8f804699665 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 4 Apr 2026 11:47:11 +0100 Subject: [PATCH] fix: harden direct CDP websocket validation (#60469) (thanks @eleqtrizit) --- CHANGELOG.md | 1 + extensions/browser/src/browser/cdp.test.ts | 13 +++++++++++++ extensions/browser/src/browser/cdp.ts | 1 + 3 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 530803518fc..1d7cc1d23d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -177,6 +177,7 @@ Docs: https://docs.openclaw.ai - Agents/logging: keep orphaned-user transcript repair warnings focused on interactive runs, and downgrade background-trigger repairs (`heartbeat`, `cron`, `memory`, `overflow`) to debug logs to reduce false-alarm gateway noise. - Gateway/node pairing: require `operator.pairing` for node approvals end-to-end, while still requiring `operator.write` or `operator.admin` when the pending node commands need those higher scopes. (#60461) Thanks @eleqtrizit. - Providers/OpenRouter: gate Anthropic prompt-cache `cache_control` markers to native/default OpenRouter routes and preserve them for native OpenRouter hosts behind custom provider ids. Thanks @vincentkoc. +- Browser/CDP: validate both initial and discovered CDP websocket endpoints before connect so strict SSRF policy blocks cross-host pivots and direct websocket targets. (#60469) Thanks @eleqtrizit. ## 2026.4.1 diff --git a/extensions/browser/src/browser/cdp.test.ts b/extensions/browser/src/browser/cdp.test.ts index 6af5f52481b..d70e5586b35 100644 --- a/extensions/browser/src/browser/cdp.test.ts +++ b/extensions/browser/src/browser/cdp.test.ts @@ -257,6 +257,19 @@ describe("cdp", () => { ).rejects.toBeInstanceOf(SsrFBlockedError); }); + it("blocks direct websocket cdp urls outside strict SSRF policy", async () => { + await expect( + createTargetViaCdp({ + cdpUrl: "ws://169.254.169.254:9222/devtools/browser/PIVOT", + url: "https://example.com", + ssrfPolicy: { + dangerouslyAllowPrivateNetwork: false, + allowedHostnames: ["127.0.0.1"], + }, + }), + ).rejects.toBeInstanceOf(SsrFBlockedError); + }); + it("evaluates javascript via CDP", async () => { const wsPort = await startWsServerWithMessages((msg, socket) => { if (msg.method === "Runtime.enable") { diff --git a/extensions/browser/src/browser/cdp.ts b/extensions/browser/src/browser/cdp.ts index e03c544c22e..6cee47ccf95 100644 --- a/extensions/browser/src/browser/cdp.ts +++ b/extensions/browser/src/browser/cdp.ts @@ -183,6 +183,7 @@ export async function createTargetViaCdp(opts: { let wsUrl: string; if (isWebSocketUrl(opts.cdpUrl)) { // Direct WebSocket URL — skip /json/version discovery. + await assertCdpEndpointAllowed(opts.cdpUrl, opts.ssrfPolicy); wsUrl = opts.cdpUrl; } else { // Standard HTTP(S) CDP endpoint — discover WebSocket URL via /json/version.