fix: isolate browser proxy routing

Co-authored-by: Sanjays2402 <Sanjays2402@users.noreply.github.com>
This commit is contained in:
Peter Steinberger
2026-04-25 03:48:48 +01:00
parent 9e5d09c962
commit a98a0b94d1
13 changed files with 253 additions and 48 deletions

View File

@@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";
import {
hasChromeProxyControlArg,
hasExplicitChromeProxyRoutingArg,
omitChromeProxyEnv,
resolveBrowserNavigationProxyMode,
} from "./browser-proxy-mode.js";
describe("browser proxy mode", () => {
it("detects Chrome proxy-routing args separately from direct proxy controls", () => {
expect(hasChromeProxyControlArg(["--no-proxy-server"])).toBe(true);
expect(hasExplicitChromeProxyRoutingArg(["--no-proxy-server"])).toBe(false);
expect(hasExplicitChromeProxyRoutingArg(["--proxy-server=http://127.0.0.1:7890"])).toBe(true);
expect(hasExplicitChromeProxyRoutingArg(["--proxy-pac-url", "http://proxy.test/pac"])).toBe(
true,
);
});
it("removes proxy env before launching managed Chrome", () => {
const env = omitChromeProxyEnv({
HTTP_PROXY: "http://proxy.test:8080",
HTTPS_PROXY: "http://proxy.test:8443",
ALL_PROXY: "socks5://proxy.test:1080",
NO_PROXY: "localhost",
PATH: "/usr/bin",
http_proxy: "http://lower.test:8080",
no_proxy: "127.0.0.1",
});
expect(env).toEqual({ PATH: "/usr/bin" });
});
it("marks only managed local Chrome with explicit proxy routing as proxy-routed", () => {
const resolved = { extraArgs: ["--proxy-server=http://127.0.0.1:7890"] };
expect(
resolveBrowserNavigationProxyMode({
resolved,
profile: { driver: "openclaw", cdpIsLoopback: true },
}),
).toBe("explicit-browser-proxy");
expect(
resolveBrowserNavigationProxyMode({
resolved,
profile: { driver: "existing-session", cdpIsLoopback: true },
}),
).toBe("direct");
expect(
resolveBrowserNavigationProxyMode({
resolved,
profile: { driver: "openclaw", cdpIsLoopback: false },
}),
).toBe("direct");
});
});

View File

@@ -0,0 +1,55 @@
import type { ResolvedBrowserConfig, ResolvedBrowserProfile } from "./config.js";
import type { BrowserNavigationProxyMode } from "./navigation-guard.js";
const PROXY_ROUTING_CHROME_ARGS = new Set([
"--proxy-auto-detect",
"--proxy-pac-url",
"--proxy-server",
]);
const PROXY_CONTROL_CHROME_ARGS = new Set(["--no-proxy-server", ...PROXY_ROUTING_CHROME_ARGS]);
export const CHROME_PROXY_ENV_KEYS = [
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
"NO_PROXY",
"http_proxy",
"https_proxy",
"all_proxy",
"no_proxy",
] as const;
function chromeArgName(arg: string): string {
return arg.trim().split("=", 1)[0]?.toLowerCase() ?? "";
}
export function hasChromeProxyControlArg(args: readonly string[]): boolean {
return args.some((arg) => PROXY_CONTROL_CHROME_ARGS.has(chromeArgName(arg)));
}
export function hasExplicitChromeProxyRoutingArg(args: readonly string[]): boolean {
return args.some((arg) => PROXY_ROUTING_CHROME_ARGS.has(chromeArgName(arg)));
}
export function omitChromeProxyEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
const next: NodeJS.ProcessEnv = { ...env };
for (const key of CHROME_PROXY_ENV_KEYS) {
delete next[key];
}
return next;
}
export function resolveBrowserNavigationProxyMode(params: {
resolved: Pick<ResolvedBrowserConfig, "extraArgs">;
profile: Pick<ResolvedBrowserProfile, "cdpIsLoopback" | "driver">;
}): BrowserNavigationProxyMode {
if (
params.profile.driver === "openclaw" &&
params.profile.cdpIsLoopback &&
hasExplicitChromeProxyRoutingArg(params.resolved.extraArgs)
) {
return "explicit-browser-proxy";
}
return "direct";
}

View File

@@ -224,6 +224,16 @@ describe("chrome.ts internal", () => {
});
expect(args).toContain("--proxy-server=http://localhost:3128");
expect(args).toContain("--mute-audio");
expect(args).not.toContain("--no-proxy-server");
});
it("launches managed Chrome direct by default", () => {
const args = buildOpenClawChromeLaunchArgs({
resolved: baseResolved(),
profile: baseProfile,
userDataDir: "/tmp/foo",
});
expect(args).toContain("--no-proxy-server");
});
});
@@ -354,6 +364,9 @@ describe("chrome.ts internal", () => {
spawnCalls += 1;
return makeFakeProc();
});
vi.stubEnv("HTTP_PROXY", "http://proxy.test:8080");
vi.stubEnv("HTTPS_PROXY", "http://proxy.test:8443");
vi.stubEnv("NO_PROXY", "localhost");
// Set up a real HTTP server impersonating Chrome's /json/version.
await withMockChromeCdpServer({
@@ -364,6 +377,10 @@ describe("chrome.ts internal", () => {
const running = await launchOpenClawChrome(makeResolved(), profile);
expect(running.pid).toBe(4242);
expect(spawnCalls).toBeGreaterThanOrEqual(1);
const spawnOptions = spawnMock.mock.calls[0]?.[2] as { env?: NodeJS.ProcessEnv };
expect(spawnOptions.env?.HTTP_PROXY).toBeUndefined();
expect(spawnOptions.env?.HTTPS_PROXY).toBeUndefined();
expect(spawnOptions.env?.NO_PROXY).toBeUndefined();
// Cleanup.
running.proc.kill?.("SIGTERM");
},

View File

@@ -8,6 +8,7 @@ import type { SsrFPolicy } from "../infra/net/ssrf.js";
import { ensurePortAvailable } from "../infra/ports.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { CONFIG_DIR } from "../utils.js";
import { hasChromeProxyControlArg, omitChromeProxyEnv } from "./browser-proxy-mode.js";
import {
CHROME_BOOTSTRAP_EXIT_POLL_MS,
CHROME_BOOTSTRAP_EXIT_TIMEOUT_MS,
@@ -132,6 +133,9 @@ export function buildOpenClawChromeLaunchArgs(params: {
if (process.platform === "linux") {
args.push("--disable-dev-shm-usage");
}
if (!hasChromeProxyControlArg(resolved.extraArgs)) {
args.push("--no-proxy-server");
}
if (resolved.extraArgs.length > 0) {
args.push(...resolved.extraArgs);
}
@@ -293,7 +297,7 @@ export async function launchOpenClawChrome(
// the tuple overload resolution varies across @types/node versions.
const preparedSpawn = prepareOomScoreAdjustedSpawn(exe.path, args, {
env: {
...process.env,
...omitChromeProxyEnv(process.env),
// Reduce accidental sharing with the user's env.
HOME: os.homedir(),
},

View File

@@ -207,7 +207,7 @@ describe("browser navigation guard", () => {
).resolves.toBeUndefined();
});
it("blocks strict policy navigation when env proxy is configured", async () => {
it("allows public navigation when only Gateway env proxy is configured", async () => {
vi.stubEnv("HTTP_PROXY", "http://127.0.0.1:7890");
const lookupFn = createLookupFn("93.184.216.34");
await expect(
@@ -215,16 +215,29 @@ describe("browser navigation guard", () => {
url: "https://example.com",
lookupFn,
}),
).rejects.toBeInstanceOf(InvalidBrowserNavigationUrlError);
).resolves.toBeUndefined();
expect(lookupFn).toHaveBeenCalledWith("example.com", { all: true });
});
it("allows env proxy navigation when private-network mode is explicitly enabled", async () => {
vi.stubEnv("HTTP_PROXY", "http://127.0.0.1:7890");
it("blocks explicit browser proxy routing in strict SSRF mode", async () => {
const lookupFn = createLookupFn("93.184.216.34");
await expect(
assertBrowserNavigationAllowed({
url: "https://example.com",
lookupFn,
browserProxyMode: "explicit-browser-proxy",
}),
).rejects.toBeInstanceOf(InvalidBrowserNavigationUrlError);
expect(lookupFn).not.toHaveBeenCalled();
});
it("allows explicit browser proxy routing when private-network mode is enabled", async () => {
const lookupFn = createLookupFn("93.184.216.34");
await expect(
assertBrowserNavigationAllowed({
url: "https://example.com",
lookupFn,
browserProxyMode: "explicit-browser-proxy",
ssrfPolicy: { dangerouslyAllowPrivateNetwork: true },
}),
).resolves.toBeUndefined();

View File

@@ -3,7 +3,6 @@ import {
matchesHostnameAllowlist,
normalizeHostname,
} from "openclaw/plugin-sdk/browser-security-runtime";
import { hasProxyEnvConfigured } from "../infra/net/proxy-env.js";
import {
isPrivateNetworkAllowedByPolicy,
resolvePinnedHostnameWithPolicy,
@@ -32,8 +31,11 @@ export class InvalidBrowserNavigationUrlError extends Error {
export type BrowserNavigationPolicyOptions = {
ssrfPolicy?: SsrFPolicy;
browserProxyMode?: BrowserNavigationProxyMode;
};
export type BrowserNavigationProxyMode = "direct" | "explicit-browser-proxy";
export type BrowserNavigationRequestLike = {
url(): string;
redirectedFrom(): BrowserNavigationRequestLike | null;
@@ -41,8 +43,14 @@ export type BrowserNavigationRequestLike = {
export function withBrowserNavigationPolicy(
ssrfPolicy?: SsrFPolicy,
opts?: { browserProxyMode?: BrowserNavigationProxyMode },
): BrowserNavigationPolicyOptions {
return ssrfPolicy ? { ssrfPolicy } : {};
return {
...(ssrfPolicy ? { ssrfPolicy } : {}),
...(opts?.browserProxyMode && opts.browserProxyMode !== "direct"
? { browserProxyMode: opts.browserProxyMode }
: {}),
};
}
export function requiresInspectableBrowserNavigationRedirects(ssrfPolicy?: SsrFPolicy): boolean {
@@ -109,13 +117,15 @@ export async function assertBrowserNavigationAllowed(
);
}
// Browser network stacks may apply env proxy routing at connect-time, which
// can bypass strict destination-binding intent from pre-navigation DNS checks.
// In strict mode, fail closed unless private-network navigation is explicitly
// enabled by policy.
if (hasProxyEnvConfigured() && !isPrivateNetworkAllowedByPolicy(opts.ssrfPolicy)) {
// Browser proxy routing hides the final connect target from this process.
// Only block when the browser profile is known to be proxy-routed; Gateway
// provider proxy env alone is not proof of browser page proxy behavior.
if (
opts.browserProxyMode === "explicit-browser-proxy" &&
!isPrivateNetworkAllowedByPolicy(opts.ssrfPolicy)
) {
throw new InvalidBrowserNavigationUrlError(
"Navigation blocked: strict browser SSRF policy cannot be enforced while env proxy variables are set",
"Navigation blocked: strict browser SSRF policy cannot be enforced while this browser profile is proxy-routed",
);
}
@@ -188,6 +198,7 @@ export async function assertBrowserNavigationRedirectChainAllowed(
url,
lookupFn: opts.lookupFn,
ssrfPolicy: opts.ssrfPolicy,
browserProxyMode: opts.browserProxyMode,
});
}
}

View File

@@ -27,6 +27,7 @@ import {
assertBrowserNavigationAllowed,
assertBrowserNavigationRedirectChainAllowed,
assertBrowserNavigationResultAllowed,
type BrowserNavigationPolicyOptions,
InvalidBrowserNavigationUrlError,
withBrowserNavigationPolicy,
} from "./navigation-guard.js";
@@ -747,14 +748,17 @@ async function closeBlockedNavigationTarget(opts: {
await opts.page.close().catch(() => {});
}
export async function assertPageNavigationCompletedSafely(opts: {
cdpUrl: string;
page: Page;
response: Response | null;
ssrfPolicy?: SsrFPolicy;
targetId?: string;
}): Promise<void> {
const navigationPolicy = withBrowserNavigationPolicy(opts.ssrfPolicy);
export async function assertPageNavigationCompletedSafely(
opts: {
cdpUrl: string;
page: Page;
response: Response | null;
targetId?: string;
} & BrowserNavigationPolicyOptions,
): Promise<void> {
const navigationPolicy = withBrowserNavigationPolicy(opts.ssrfPolicy, {
browserProxyMode: opts.browserProxyMode,
});
try {
await assertBrowserNavigationRedirectChainAllowed({
request: opts.response?.request(),
@@ -776,15 +780,18 @@ export async function assertPageNavigationCompletedSafely(opts: {
}
}
export async function gotoPageWithNavigationGuard(opts: {
cdpUrl: string;
page: Page;
url: string;
timeoutMs: number;
ssrfPolicy?: SsrFPolicy;
targetId?: string;
}): Promise<Response | null> {
const navigationPolicy = withBrowserNavigationPolicy(opts.ssrfPolicy);
export async function gotoPageWithNavigationGuard(
opts: {
cdpUrl: string;
page: Page;
url: string;
timeoutMs: number;
targetId?: string;
} & BrowserNavigationPolicyOptions,
): Promise<Response | null> {
const navigationPolicy = withBrowserNavigationPolicy(opts.ssrfPolicy, {
browserProxyMode: opts.browserProxyMode,
});
let blockedError: unknown = null;
const handler = async (route: Route, request: Request) => {
@@ -1102,11 +1109,12 @@ export async function listPagesViaPlaywright(opts: {
* Used for remote profiles where HTTP-based /json/new is ephemeral.
* Returns the new page's targetId and metadata.
*/
export async function createPageViaPlaywright(opts: {
cdpUrl: string;
url: string;
ssrfPolicy?: SsrFPolicy;
}): Promise<{
export async function createPageViaPlaywright(
opts: {
cdpUrl: string;
url: string;
} & BrowserNavigationPolicyOptions,
): Promise<{
targetId: string;
title: string;
url: string;
@@ -1125,7 +1133,9 @@ export async function createPageViaPlaywright(opts: {
// Navigate to the URL
const targetUrl = opts.url.trim() || "about:blank";
if (targetUrl !== "about:blank") {
const navigationPolicy = withBrowserNavigationPolicy(opts.ssrfPolicy);
const navigationPolicy = withBrowserNavigationPolicy(opts.ssrfPolicy, {
browserProxyMode: opts.browserProxyMode,
});
await assertBrowserNavigationAllowed({
url: targetUrl,
...navigationPolicy,
@@ -1138,6 +1148,7 @@ export async function createPageViaPlaywright(opts: {
url: targetUrl,
timeoutMs: 30_000,
ssrfPolicy: opts.ssrfPolicy,
browserProxyMode: opts.browserProxyMode,
targetId: createdTargetId ?? undefined,
});
} catch (err) {
@@ -1150,6 +1161,7 @@ export async function createPageViaPlaywright(opts: {
page,
response,
ssrfPolicy: opts.ssrfPolicy,
browserProxyMode: opts.browserProxyMode,
targetId: createdTargetId ?? undefined,
});
}

View File

@@ -2,7 +2,11 @@ import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
import type { Page } from "playwright-core";
import type { SsrFPolicy } from "../infra/net/ssrf.js";
import { type AriaSnapshotNode, formatAriaSnapshot, type RawAXNode } from "./cdp.js";
import { assertBrowserNavigationAllowed, withBrowserNavigationPolicy } from "./navigation-guard.js";
import {
assertBrowserNavigationAllowed,
type BrowserNavigationPolicyOptions,
withBrowserNavigationPolicy,
} from "./navigation-guard.js";
import {
buildRoleSnapshotFromAiSnapshot,
buildRoleSnapshotFromAriaSnapshot,
@@ -241,6 +245,7 @@ export async function navigateViaPlaywright(opts: {
url: string;
timeoutMs?: number;
ssrfPolicy?: SsrFPolicy;
browserProxyMode?: BrowserNavigationPolicyOptions["browserProxyMode"];
}): Promise<{ url: string }> {
const isRetryableNavigateError = (err: unknown): boolean => {
const msg =
@@ -261,7 +266,9 @@ export async function navigateViaPlaywright(opts: {
}
await assertBrowserNavigationAllowed({
url,
...withBrowserNavigationPolicy(opts.ssrfPolicy),
...withBrowserNavigationPolicy(opts.ssrfPolicy, {
browserProxyMode: opts.browserProxyMode,
}),
});
const timeout = Math.max(1000, Math.min(120_000, opts.timeoutMs ?? 20_000));
let page = await getPageForTargetId(opts);
@@ -273,6 +280,7 @@ export async function navigateViaPlaywright(opts: {
url,
timeoutMs: timeout,
ssrfPolicy: opts.ssrfPolicy,
browserProxyMode: opts.browserProxyMode,
targetId: opts.targetId,
});
let response;
@@ -298,6 +306,7 @@ export async function navigateViaPlaywright(opts: {
page,
response,
ssrfPolicy: opts.ssrfPolicy,
browserProxyMode: opts.browserProxyMode,
targetId: opts.targetId,
});
const finalUrl = page.url();

View File

@@ -1,5 +1,6 @@
import path from "node:path";
import { ensureMediaDir, saveMediaBuffer } from "../../media/store.js";
import { resolveBrowserNavigationProxyMode } from "../browser-proxy-mode.js";
import { captureScreenshot, snapshotAria } from "../cdp.js";
import {
evaluateChromeMcpScript,
@@ -23,7 +24,7 @@ import {
DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE,
normalizeBrowserScreenshot,
} from "../screenshot.js";
import type { BrowserRouteContext } from "../server-context.js";
import type { BrowserRouteContext, ProfileContext } from "../server-context.js";
import {
getPwAiModule,
handleRouteError,
@@ -45,6 +46,15 @@ import { asyncBrowserRoute, jsonError, toBoolean, toNumber, toStringOrEmpty } fr
const CHROME_MCP_OVERLAY_ATTR = "data-openclaw-mcp-overlay";
function browserNavigationPolicyForProfile(ctx: BrowserRouteContext, profileCtx: ProfileContext) {
return withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy, {
browserProxyMode: resolveBrowserNavigationProxyMode({
resolved: ctx.state().resolved,
profile: profileCtx.profile,
}),
});
}
async function collectChromeMcpSnapshotUrls(params: {
profileName: string;
userDataDir?: string;
@@ -251,7 +261,7 @@ export function registerBrowserAgentSnapshotRoutes(
targetId,
run: async ({ profileCtx, tab, cdpUrl }) => {
if (getBrowserProfileCapabilities(profileCtx.profile).usesChromeMcp) {
const ssrfPolicyOpts = withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy);
const ssrfPolicyOpts = browserNavigationPolicyForProfile(ctx, profileCtx);
await assertBrowserNavigationAllowed({ url, ...ssrfPolicyOpts });
const result = await navigateChromeMcpPage({
profileName: profileCtx.profile.name,
@@ -270,7 +280,7 @@ export function registerBrowserAgentSnapshotRoutes(
cdpUrl,
targetId: tab.targetId,
url,
...withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy),
...browserNavigationPolicyForProfile(ctx, profileCtx),
});
const currentTargetId = await resolveTargetIdAfterNavigate({
oldTargetId: tab.targetId,
@@ -346,7 +356,7 @@ export function registerBrowserAgentSnapshotRoutes(
targetId,
run: async ({ profileCtx, tab, cdpUrl }) => {
if (getBrowserProfileCapabilities(profileCtx.profile).usesChromeMcp) {
const ssrfPolicyOpts = withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy);
const ssrfPolicyOpts = browserNavigationPolicyForProfile(ctx, profileCtx);
if (element) {
return jsonError(res, 400, EXISTING_SESSION_LIMITS.snapshot.screenshotElement);
}
@@ -508,7 +518,7 @@ export function registerBrowserAgentSnapshotRoutes(
return jsonError(res, 400, "labels/mode=efficient require format=ai");
}
if (getBrowserProfileCapabilities(profileCtx.profile).usesChromeMcp) {
const ssrfPolicyOpts = withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy);
const ssrfPolicyOpts = browserNavigationPolicyForProfile(ctx, profileCtx);
if (plan.selectorValue || plan.frameSelectorValue) {
return jsonError(res, 400, EXISTING_SESSION_LIMITS.snapshot.snapshotSelector);
}

View File

@@ -1,3 +1,4 @@
import { resolveBrowserNavigationProxyMode } from "../browser-proxy-mode.js";
import {
BrowserProfileUnavailableError,
BrowserTabNotFoundError,
@@ -32,6 +33,15 @@ function resolveTabsProfileContext(
return profileCtx;
}
function browserNavigationPolicyForProfile(ctx: BrowserRouteContext, profileCtx: ProfileContext) {
return withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy, {
browserProxyMode: resolveBrowserNavigationProxyMode({
resolved: ctx.state().resolved,
profile: profileCtx.profile,
}),
});
}
function handleTabsRouteError(
ctx: BrowserRouteContext,
res: BrowserResponse,
@@ -188,7 +198,7 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
run: async (profileCtx) => {
await assertBrowserNavigationAllowed({
url,
...withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy),
...browserNavigationPolicyForProfile(ctx, profileCtx),
});
await profileCtx.ensureBrowserAvailable();
const tab = await profileCtx.openTab(url, { label });
@@ -223,7 +233,7 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
if (!tab) {
throw new BrowserTabNotFoundError({ input: id });
}
const ssrfPolicyOpts = withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy);
const ssrfPolicyOpts = browserNavigationPolicyForProfile(ctx, profileCtx);
if (ssrfPolicyOpts.ssrfPolicy) {
await assertBrowserNavigationResultAllowed({
url: tab.url,
@@ -331,7 +341,7 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
if (!target) {
throw new BrowserTabNotFoundError();
}
const ssrfPolicyOpts = withBrowserNavigationPolicy(ctx.state().resolved.ssrfPolicy);
const ssrfPolicyOpts = browserNavigationPolicyForProfile(ctx, profileCtx);
if (ssrfPolicyOpts.ssrfPolicy) {
await assertBrowserNavigationResultAllowed({
url: target.url,

View File

@@ -1,3 +1,4 @@
import { resolveBrowserNavigationProxyMode } from "./browser-proxy-mode.js";
import { resolveCdpControlPolicy } from "./cdp-reachability-policy.js";
import { CDP_JSON_NEW_TIMEOUT_MS } from "./cdp-timeouts.js";
import {
@@ -132,6 +133,13 @@ export function createProfileTabOps({
const cdpHttpBase = normalizeCdpHttpBaseForJsonEndpoints(profile.cdpUrl);
const capabilities = getBrowserProfileCapabilities(profile);
const getCdpControlPolicy = () => resolveCdpControlPolicy(profile, state().resolved.ssrfPolicy);
const getNavigationPolicy = () =>
withBrowserNavigationPolicy(state().resolved.ssrfPolicy, {
browserProxyMode: resolveBrowserNavigationProxyMode({
resolved: state().resolved,
profile,
}),
});
const readTabs = async (): Promise<BrowserTab[]> => {
if (capabilities.usesChromeMcp) {
@@ -218,7 +226,7 @@ export function createProfileTabOps({
};
const openTab = async (url: string, opts?: { label?: string }): Promise<BrowserTab> => {
const ssrfPolicyOpts = withBrowserNavigationPolicy(state().resolved.ssrfPolicy);
const ssrfPolicyOpts = getNavigationPolicy();
if (capabilities.usesChromeMcp) {
await assertBrowserNavigationAllowed({ url, ...ssrfPolicyOpts });
@@ -261,6 +269,7 @@ export function createProfileTabOps({
);
}
await assertBrowserNavigationAllowed({ url, ...ssrfPolicyOpts });
const createdViaCdp = await createTargetViaCdp({
cdpUrl: profile.cdpUrl,
url,
@@ -293,7 +302,6 @@ export function createProfileTabOps({
const encoded = encodeURIComponent(url);
const endpointUrl = new URL(appendCdpPath(cdpHttpBase, "/json/new"));
await assertBrowserNavigationAllowed({ url, ...ssrfPolicyOpts });
const endpoint = endpointUrl.search
? (() => {
endpointUrl.searchParams.set("url", url);