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 <noreply@anthropic.com>

* test(browser): tighten target-list CDP policy test comment

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(browser): consolidate target-list SSRF coverage

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Masato Hoshino
2026-07-09 20:04:39 +09:00
committed by GitHub
parent 0ac8933721
commit c9ec548e6f
2 changed files with 25 additions and 1 deletions

View File

@@ -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<typeof import("openclaw/plugin-sdk/ssrf-runtime")>();
return {
...actual,
fetchWithSsrFGuard: (...args: Parameters<typeof actual.fetchWithSsrFGuard>) => {
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();
}

View File

@@ -1201,13 +1201,14 @@ async function findPageByTargetIdViaTargetList(
): Promise<Page | null> {
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);
}