From c9ec548e6f8bce9efe602e3fa2c5320eecfda0a9 Mon Sep 17 00:00:00 2001 From: Masato Hoshino Date: Thu, 9 Jul 2026 20:04:39 +0900 Subject: [PATCH] fix(browser): preserve strict CDP discovery policy on target-list lookup (#102328) * fix(browser): preserve strict CDP discovery policy on target-list lookup findPageByTargetIdViaTargetList fetched the CDP /json/list endpoint without passing an ssrfPolicy argument, so the caller's discovery policy was dropped at the fetch layer while the sibling tryTerminateExecutionViaCdp scoped it. Build the scoped control policy with scopeCdpPolicyToConfiguredEndpoint and pass it to fetchJson, matching the sibling lookup path. Co-Authored-By: Claude Fable 5 * test(browser): tighten target-list CDP policy test comment Co-Authored-By: Claude Fable 5 * test(browser): consolidate target-list SSRF coverage --------- Co-authored-by: Claude Fable 5 Co-authored-by: Peter Steinberger --- ...ge-for-targetid.extension-fallback.test.ts | 23 +++++++++++++++++++ extensions/browser/src/browser/pw-session.ts | 3 ++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/extensions/browser/src/browser/pw-session.get-page-for-targetid.extension-fallback.test.ts b/extensions/browser/src/browser/pw-session.get-page-for-targetid.extension-fallback.test.ts index 82f5c3060971..31d8b86a2834 100644 --- a/extensions/browser/src/browser/pw-session.get-page-for-targetid.extension-fallback.test.ts +++ b/extensions/browser/src/browser/pw-session.get-page-for-targetid.extension-fallback.test.ts @@ -1,4 +1,5 @@ // Browser tests cover pw session.get page for targetid.extension fallback plugin behavior. +import type { SsrFPolicy } from "openclaw/plugin-sdk/ssrf-runtime"; import { chromium } from "playwright-core"; import { afterEach, describe, expect, it, vi } from "vitest"; import * as chromeModule from "./chrome.js"; @@ -9,6 +10,19 @@ import { setCdpConnectRetryDelayMsForTests, } from "./pw-session.js"; +const fetchWithSsrFGuardSpy = vi.hoisted(() => vi.fn()); + +vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetchWithSsrFGuard: (...args: Parameters) => { + fetchWithSsrFGuardSpy(...args); + return actual.fetchWithSsrFGuard(...args); + }, + }; +}); + const connectOverCdpSpy = vi.spyOn(chromium, "connectOverCDP"); const getChromeWebSocketUrlSpy = vi.spyOn(chromeModule, "getChromeWebSocketUrl"); @@ -84,6 +98,7 @@ function makeBrowser(pages: MockPageSpec[]): BrowserMockBundle { afterEach(async () => { connectOverCdpSpy.mockReset(); getChromeWebSocketUrlSpy.mockReset(); + fetchWithSsrFGuardSpy.mockClear(); setCdpConnectRetryDelayMsForTests(); await closePlaywrightBrowserConnection().catch(() => {}); }); @@ -196,9 +211,17 @@ describe("pw-session getPageForTargetId", () => { const resolved = await getPageForTargetId({ cdpUrl: "http://127.0.0.1:19993", targetId: "TARGET_B", + ssrfPolicy: { + dangerouslyAllowPrivateNetwork: false, + allowRfc2544BenchmarkRange: true, + }, }); expect(resolved).toBe(pageB); expect(newCDPSession).toHaveBeenCalled(); + expect(fetchWithSsrFGuardSpy).toHaveBeenCalledTimes(1); + const policy = fetchWithSsrFGuardSpy.mock.calls[0]?.[0]?.policy as SsrFPolicy | undefined; + expect(policy?.allowRfc2544BenchmarkRange).toBe(true); + expect(policy?.hostnameAllowlist).toEqual(["127.0.0.1"]); } finally { fetchSpy.mockRestore(); } diff --git a/extensions/browser/src/browser/pw-session.ts b/extensions/browser/src/browser/pw-session.ts index b490bf168e48..d7273d36f101 100644 --- a/extensions/browser/src/browser/pw-session.ts +++ b/extensions/browser/src/browser/pw-session.ts @@ -1201,13 +1201,14 @@ async function findPageByTargetIdViaTargetList( ): Promise { const cdpHttpBase = normalizeCdpHttpBaseForJsonEndpoints(cdpUrl); await assertCdpEndpointAllowed(cdpUrl, ssrfPolicy); + const cdpControlPolicy = scopeCdpPolicyToConfiguredEndpoint(cdpUrl, ssrfPolicy); const targets = await fetchJson< Array<{ id: string; url: string; title?: string; }> - >(appendCdpPath(cdpHttpBase, "/json/list"), 2000); + >(appendCdpPath(cdpHttpBase, "/json/list"), 2000, undefined, cdpControlPolicy); return matchPageByTargetList(pages, targets, targetId); }