diff --git a/extensions/browser/src/browser/profiles-service.test.ts b/extensions/browser/src/browser/profiles-service.test.ts index 8668d1f7a32..0cd88916988 100644 --- a/extensions/browser/src/browser/profiles-service.test.ts +++ b/extensions/browser/src/browser/profiles-service.test.ts @@ -141,6 +141,30 @@ describe("BrowserProfilesService", () => { ); }); + it("rejects private-network cdpUrl when strict SSRF mode is enabled", async () => { + const resolved = resolveBrowserConfig({ + ssrfPolicy: { dangerouslyAllowPrivateNetwork: false }, + }); + const { ctx } = createCtx(resolved); + + vi.mocked(loadConfig).mockReturnValue({ + browser: { + ssrfPolicy: { dangerouslyAllowPrivateNetwork: false }, + profiles: {}, + }, + }); + + const service = createBrowserProfilesService(ctx); + + await expect( + service.createProfile({ + name: "remote", + cdpUrl: "http://10.0.0.42:9222", + }), + ).rejects.toThrow(/private\/internal\/special-use ip address/i); + expect(writeConfigFile).not.toHaveBeenCalled(); + }); + it("creates existing-session profiles as attach-only local entries", async () => { const resolved = resolveBrowserConfig({}); const { ctx, state } = createCtx(resolved); diff --git a/extensions/browser/src/browser/profiles-service.ts b/extensions/browser/src/browser/profiles-service.ts index ea1f3b674c6..74919d22087 100644 --- a/extensions/browser/src/browser/profiles-service.ts +++ b/extensions/browser/src/browser/profiles-service.ts @@ -4,6 +4,7 @@ import type { BrowserProfileConfig, OpenClawConfig } from "../config/config.js"; import { loadConfig, writeConfigFile } from "../config/config.js"; import { deriveDefaultBrowserCdpPortRange } from "../config/port-defaults.js"; import { resolveUserPath } from "../utils.js"; +import { assertCdpEndpointAllowed } from "./cdp.helpers.js"; import { resolveOpenClawUserDataDir } from "./chrome.js"; import { parseHttpUrl, resolveProfile } from "./config.js"; import { @@ -124,6 +125,7 @@ export function createBrowserProfilesService(ctx: BrowserRouteContext) { let parsed: ReturnType; try { parsed = parseHttpUrl(rawCdpUrl, "browser.profiles.cdpUrl"); + await assertCdpEndpointAllowed(parsed.normalized, state.resolved.ssrfPolicy); } catch (err) { throw new BrowserValidationError(String(err)); }