























@@ -1,4 +1,13 @@
11import { afterEach, describe, expect, it, vi } from "vitest";
2+import { resolveCdpReachabilityPolicy } from "./cdp-reachability-policy.js";
3+import {
4+PROFILE_HTTP_REACHABILITY_TIMEOUT_MS,
5+PROFILE_WS_REACHABILITY_MAX_TIMEOUT_MS,
6+PROFILE_WS_REACHABILITY_MIN_TIMEOUT_MS,
7+resolveCdpReachabilityTimeouts,
8+} from "./cdp-timeouts.js";
9+import type { ResolvedBrowserProfile } from "./config.js";
10+import { assertBrowserNavigationAllowed } from "./navigation-guard.js";
211312const fetchWithSsrFGuardMock = vi.hoisted(() => vi.fn());
413@@ -138,3 +147,119 @@ describe("cdp helpers", () => {
138147expect(release).toHaveBeenCalledTimes(1);
139148});
140149});
150+151+function createProfile(overrides: Partial<ResolvedBrowserProfile>): ResolvedBrowserProfile {
152+return {
153+name: "remote",
154+cdpPort: 9223,
155+cdpUrl: "http://172.29.128.1:9223",
156+cdpHost: "172.29.128.1",
157+cdpIsLoopback: false,
158+color: "#123456",
159+driver: "openclaw",
160+attachOnly: false,
161+ ...overrides,
162+};
163+}
164+165+describe("resolveCdpReachabilityTimeouts", () => {
166+it("uses loopback defaults when timeout is omitted", () => {
167+expect(
168+resolveCdpReachabilityTimeouts({
169+profileIsLoopback: true,
170+timeoutMs: undefined,
171+remoteHttpTimeoutMs: 1500,
172+remoteHandshakeTimeoutMs: 3000,
173+}),
174+).toEqual({
175+httpTimeoutMs: PROFILE_HTTP_REACHABILITY_TIMEOUT_MS,
176+wsTimeoutMs: PROFILE_HTTP_REACHABILITY_TIMEOUT_MS * 2,
177+});
178+});
179+180+it("clamps loopback websocket timeout range", () => {
181+const low = resolveCdpReachabilityTimeouts({
182+profileIsLoopback: true,
183+timeoutMs: 1,
184+remoteHttpTimeoutMs: 1500,
185+remoteHandshakeTimeoutMs: 3000,
186+});
187+const high = resolveCdpReachabilityTimeouts({
188+profileIsLoopback: true,
189+timeoutMs: 5000,
190+remoteHttpTimeoutMs: 1500,
191+remoteHandshakeTimeoutMs: 3000,
192+});
193+194+expect(low.wsTimeoutMs).toBe(PROFILE_WS_REACHABILITY_MIN_TIMEOUT_MS);
195+expect(high.wsTimeoutMs).toBe(PROFILE_WS_REACHABILITY_MAX_TIMEOUT_MS);
196+});
197+198+it("enforces remote minimums even when caller passes lower timeout", () => {
199+expect(
200+resolveCdpReachabilityTimeouts({
201+profileIsLoopback: false,
202+timeoutMs: 200,
203+remoteHttpTimeoutMs: 1500,
204+remoteHandshakeTimeoutMs: 3000,
205+}),
206+).toEqual({
207+httpTimeoutMs: 1500,
208+wsTimeoutMs: 3000,
209+});
210+});
211+212+it("uses remote defaults when timeout is omitted", () => {
213+expect(
214+resolveCdpReachabilityTimeouts({
215+profileIsLoopback: false,
216+timeoutMs: undefined,
217+remoteHttpTimeoutMs: 1750,
218+remoteHandshakeTimeoutMs: 3250,
219+}),
220+).toEqual({
221+httpTimeoutMs: 1750,
222+wsTimeoutMs: 3250,
223+});
224+});
225+});
226+227+describe("CDP reachability policy", () => {
228+it("allows the selected remote profile CDP host without widening browser navigation policy", async () => {
229+const browserPolicy = {};
230+const profile = createProfile({});
231+232+expect(resolveCdpReachabilityPolicy(profile, browserPolicy)).toEqual({
233+allowedHostnames: ["172.29.128.1"],
234+});
235+expect(browserPolicy).toEqual({});
236+await expect(
237+assertBrowserNavigationAllowed({
238+url: "http://172.29.128.1/",
239+ssrfPolicy: browserPolicy,
240+}),
241+).rejects.toThrow(/private\/internal\/special-use ip address/i);
242+});
243+244+it("merges the selected remote profile CDP host with existing CDP policy hostnames", () => {
245+const profile = createProfile({});
246+247+expect(
248+resolveCdpReachabilityPolicy(profile, {
249+allowedHostnames: ["metadata.internal"],
250+}),
251+).toEqual({
252+allowedHostnames: ["metadata.internal", "172.29.128.1"],
253+});
254+});
255+256+it("keeps local managed loopback CDP control outside browser SSRF policy", () => {
257+const profile = createProfile({
258+cdpUrl: "http://127.0.0.1:18800",
259+cdpHost: "127.0.0.1",
260+cdpIsLoopback: true,
261+});
262+263+expect(resolveCdpReachabilityPolicy(profile, {})).toBeUndefined();
264+});
265+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。