From 3720ecaf528d21b35aa8142f1348abe69aa845bc Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 4 Jun 2026 07:42:57 -0400 Subject: [PATCH] docs: document browser config paths --- extensions/browser/src/browser/config.ts | 17 +++++++++++++++++ .../src/browser/local-dispatch.runtime.ts | 7 +++++++ .../browser/src/browser/navigation-guard.ts | 15 +++++++++++++++ extensions/browser/src/browser/output-atomic.ts | 7 +++++++ .../browser/src/browser/output-directories.ts | 7 +++++++ extensions/browser/src/browser/output-files.ts | 7 +++++++ extensions/browser/src/browser/paths.ts | 12 ++++++++++++ .../browser/src/browser/profile-capabilities.ts | 10 ++++++++++ .../browser/src/browser/profiles-service.ts | 10 ++++++++++ extensions/browser/src/browser/profiles.ts | 14 ++++++++++++++ extensions/browser/src/browser/proxy-files.ts | 8 ++++++++ 11 files changed, 114 insertions(+) diff --git a/extensions/browser/src/browser/config.ts b/extensions/browser/src/browser/config.ts index 396e6d25f7dc..95e9e01816f6 100644 --- a/extensions/browser/src/browser/config.ts +++ b/extensions/browser/src/browser/config.ts @@ -1,3 +1,9 @@ +/** + * Browser config resolution. + * + * Normalizes raw browser config into resolved runtime defaults, profile + * records, SSRF policy, timeouts, headless mode, and managed Chrome settings. + */ import os from "node:os"; import path from "node:path"; import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime"; @@ -54,6 +60,7 @@ type BrowserSsrFPolicyCompat = NonNullable & { allowPrivateNetwork?: boolean; }; +/** Browser config after defaults, derived ports, and profile defaults are applied. */ export type ResolvedBrowserConfig = { enabled: boolean; evaluateEnabled: boolean; @@ -81,6 +88,7 @@ export type ResolvedBrowserConfig = { extraArgs: string[]; }; +/** Normalized tab-cleanup settings for session-owned browser tabs. */ export type ResolvedBrowserTabCleanupConfig = { enabled: boolean; idleMinutes: number; @@ -88,6 +96,7 @@ export type ResolvedBrowserTabCleanupConfig = { sweepMinutes: number; }; +/** Runtime browser profile settings resolved from global and profile config. */ export type ResolvedBrowserProfile = { name: string; cdpPort: number; @@ -107,8 +116,10 @@ export type ResolvedBrowserProfile = { const DEFAULT_BROWSER_CDP_PORT_RANGE_START = 18800; const MAX_BROWSER_STARTUP_TIMEOUT_MS = 120_000; +/** Environment variable that overrides managed Chrome headless mode. */ export const OPENCLAW_BROWSER_HEADLESS_ENV = "OPENCLAW_BROWSER_HEADLESS"; +/** Source that determined managed Chrome headless mode. */ export type ManagedBrowserHeadlessSource = | "request" | "env" @@ -122,6 +133,7 @@ type ManagedBrowserHeadlessMode = { source: ManagedBrowserHeadlessSource; }; +/** Inputs used to resolve managed Chrome headless mode. */ export type ManagedBrowserHeadlessOptions = { headlessOverride?: boolean; env?: NodeJS.ProcessEnv; @@ -333,6 +345,7 @@ function ensureDefaultUserBrowserProfile( return result; } +/** Resolve raw browser config into runtime browser defaults. */ export function resolveBrowserConfig( cfg: BrowserConfig | undefined, rootConfig?: OpenClawConfig, @@ -457,6 +470,7 @@ export function resolveBrowserConfig( }; } +/** Resolve one configured browser profile by name. */ export function resolveProfile( resolved: ResolvedBrowserConfig, profileName: string, @@ -544,6 +558,7 @@ export function resolveProfile( }; } +/** Resolve effective headless mode for a managed browser profile. */ export function resolveManagedBrowserHeadlessMode( resolved: ResolvedBrowserConfig, profile: ResolvedBrowserProfile, @@ -576,6 +591,7 @@ export function resolveManagedBrowserHeadlessMode( return { headless: resolved.headless, source: "default" }; } +/** Return a Linux display error for headed managed Chrome when no display exists. */ export function getManagedBrowserMissingDisplayError( resolved: ResolvedBrowserConfig, profile: ResolvedBrowserProfile, @@ -610,6 +626,7 @@ export function getManagedBrowserMissingDisplayError( ); } +/** Return whether local browser control should start for a resolved config. */ export function shouldStartLocalBrowserServer(_resolved: unknown) { return true; } diff --git a/extensions/browser/src/browser/local-dispatch.runtime.ts b/extensions/browser/src/browser/local-dispatch.runtime.ts index 62e028dee738..329149f59e1e 100644 --- a/extensions/browser/src/browser/local-dispatch.runtime.ts +++ b/extensions/browser/src/browser/local-dispatch.runtime.ts @@ -1,3 +1,9 @@ +/** + * Local browser control dispatch bridge. + * + * Starts the browser control service when needed and dispatches requests + * through the in-process route dispatcher for local Browser tool calls. + */ import { createBrowserControlContext, startBrowserControlServiceFromConfig, @@ -8,6 +14,7 @@ import { type BrowserDispatchResponse, } from "./routes/dispatcher.js"; +/** Dispatch one browser-control request through the local in-process router. */ export async function dispatchBrowserControlRequest( req: BrowserDispatchRequest, ): Promise { diff --git a/extensions/browser/src/browser/navigation-guard.ts b/extensions/browser/src/browser/navigation-guard.ts index 6d5749f89b57..bedfcff21baf 100644 --- a/extensions/browser/src/browser/navigation-guard.ts +++ b/extensions/browser/src/browser/navigation-guard.ts @@ -1,3 +1,9 @@ +/** + * Browser navigation SSRF guard. + * + * Validates page navigation URLs and redirect chains before or after browser + * navigation while accounting for browser proxy routing. + */ import { isIP } from "node:net"; import { isPrivateNetworkAllowedByPolicy, @@ -19,6 +25,7 @@ function normalizeNavigationUrl(url: string): string { return url.trim(); } +/** Raised when a browser navigation URL fails syntax or policy validation. */ export class InvalidBrowserNavigationUrlError extends Error { constructor(message: string) { super(message); @@ -26,18 +33,22 @@ export class InvalidBrowserNavigationUrlError extends Error { } } +/** Policy inputs applied to browser page navigation checks. */ export type BrowserNavigationPolicyOptions = { ssrfPolicy?: SsrFPolicy; browserProxyMode?: BrowserNavigationProxyMode; }; +/** Describes whether the browser itself is routing page traffic through a proxy. */ export type BrowserNavigationProxyMode = "direct" | "explicit-browser-proxy"; +/** Minimal request shape used to walk browser redirect chains. */ export type BrowserNavigationRequestLike = { url(): string; redirectedFrom(): BrowserNavigationRequestLike | null; }; +/** Build a navigation-policy object while omitting default direct proxy mode. */ export function withBrowserNavigationPolicy( ssrfPolicy?: SsrFPolicy, opts?: { browserProxyMode?: BrowserNavigationProxyMode }, @@ -50,10 +61,12 @@ export function withBrowserNavigationPolicy( }; } +/** Return true when strict policy requires redirect-chain inspection. */ export function requiresInspectableBrowserNavigationRedirects(ssrfPolicy?: SsrFPolicy): boolean { return ssrfPolicy?.dangerouslyAllowPrivateNetwork === false; } +/** Return true when a URL needs redirect inspection under strict policy. */ export function requiresInspectableBrowserNavigationRedirectsForUrl( url: string, ssrfPolicy?: SsrFPolicy, @@ -87,6 +100,7 @@ function isExplicitlyAllowedBrowserHostname(hostname: string, ssrfPolicy?: SsrFP : false; } +/** Assert that a requested browser navigation URL is policy-allowed. */ export async function assertBrowserNavigationAllowed( opts: { url: string; @@ -178,6 +192,7 @@ export async function assertBrowserNavigationResultAllowed( } } +/** Assert that every URL in a browser redirect chain is policy-allowed. */ export async function assertBrowserNavigationRedirectChainAllowed( opts: { request?: BrowserNavigationRequestLike | null; diff --git a/extensions/browser/src/browser/output-atomic.ts b/extensions/browser/src/browser/output-atomic.ts index 74997be547fe..05c8a61575e6 100644 --- a/extensions/browser/src/browser/output-atomic.ts +++ b/extensions/browser/src/browser/output-atomic.ts @@ -1,6 +1,13 @@ +/** + * Atomic output write helper. + * + * Ensures browser-generated files are written through a sibling temp path under + * an allowed output root before becoming visible at the target path. + */ import { writeExternalFileWithinRoot } from "../sdk-security-runtime.js"; import { ensureOutputDirectory } from "./output-directories.js"; +/** Write a file inside an output root via a caller-provided temp writer. */ export async function writeViaSiblingTempPath(params: { rootDir: string; targetPath: string; diff --git a/extensions/browser/src/browser/output-directories.ts b/extensions/browser/src/browser/output-directories.ts index 19bdc57c17c2..fb51c421444c 100644 --- a/extensions/browser/src/browser/output-directories.ts +++ b/extensions/browser/src/browser/output-directories.ts @@ -1,3 +1,9 @@ +/** + * Browser output directory helper. + * + * Creates absolute output directories while handling macOS system symlink + * aliases such as /tmp and /var safely. + */ import fs from "node:fs/promises"; import path from "node:path"; import { ensureAbsoluteDirectory } from "../sdk-security-runtime.js"; @@ -22,6 +28,7 @@ async function resolveSystemDirectoryAlias(dirPath: string): Promise { return dirPath; } +/** Ensure an absolute browser output directory exists and is safe to use. */ export async function ensureOutputDirectory(dirPath: string): Promise { const result = await ensureAbsoluteDirectory( await resolveSystemDirectoryAlias(path.resolve(dirPath)), diff --git a/extensions/browser/src/browser/output-files.ts b/extensions/browser/src/browser/output-files.ts index 9b5436de0e1b..f751b67ec24a 100644 --- a/extensions/browser/src/browser/output-files.ts +++ b/extensions/browser/src/browser/output-files.ts @@ -1,7 +1,14 @@ +/** + * Browser output file writer. + * + * Validates caller-provided output paths against a root before writing + * screenshots, PDFs, downloads, or traces to disk. + */ import path from "node:path"; import { writeExternalFileWithinRoot } from "../sdk-security-runtime.js"; import { ensureOutputDirectory } from "./output-directories.js"; +/** Write a browser output file within a caller-selected output root. */ export async function writeExternalFileWithinOutputRoot(params: { rootDir?: string; path: string; diff --git a/extensions/browser/src/browser/paths.ts b/extensions/browser/src/browser/paths.ts index 994e99cc84ac..e9e69b97e485 100644 --- a/extensions/browser/src/browser/paths.ts +++ b/extensions/browser/src/browser/paths.ts @@ -1,3 +1,9 @@ +/** + * Browser filesystem path helpers. + * + * Defines browser output roots and resolves upload/media references while + * enforcing root-scoped path access for Browser tool file inputs. + */ import fs from "node:fs/promises"; import path from "node:path"; import { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; @@ -35,9 +41,13 @@ function canUseNodeFs(): boolean { const DEFAULT_BROWSER_TMP_DIR = canUseNodeFs() ? resolvePreferredOpenClawTmpDir() : DEFAULT_FALLBACK_BROWSER_TMP_DIR; +/** Default root directory for browser trace files. */ export const DEFAULT_TRACE_DIR = DEFAULT_BROWSER_TMP_DIR; +/** Default root directory for browser downloads. */ export const DEFAULT_DOWNLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "downloads"); +/** Default root directory for browser upload inputs. */ export const DEFAULT_UPLOAD_DIR = path.join(DEFAULT_BROWSER_TMP_DIR, "uploads"); +/** Default root directory for managed inbound media references. */ export const DEFAULT_INBOUND_MEDIA_DIR = path.join(CONFIG_DIR, "media", "inbound"); type ExistingPathsResult = Awaited>; @@ -190,6 +200,7 @@ async function resolveDirectInboundMediaPath(params: { return inboundPathsResult; } +/** Resolve upload paths and managed media references into existing file paths. */ export async function resolveExistingUploadPaths({ requestedPaths, uploadDir = DEFAULT_UPLOAD_DIR, @@ -234,6 +245,7 @@ export async function resolveExistingUploadPaths({ return { ok: true, paths }; } +/** Strictly resolve upload paths under the upload root only. */ export async function resolveStrictExistingUploadPaths({ requestedPaths, uploadDir = DEFAULT_UPLOAD_DIR, diff --git a/extensions/browser/src/browser/profile-capabilities.ts b/extensions/browser/src/browser/profile-capabilities.ts index faab1bee0bf2..aac784a0ea8e 100644 --- a/extensions/browser/src/browser/profile-capabilities.ts +++ b/extensions/browser/src/browser/profile-capabilities.ts @@ -1,3 +1,9 @@ +/** + * Browser profile capability resolution. + * + * Derives transport and driver capability flags used by routes and the Browser + * tool to choose CDP, Playwright, or Chrome MCP behavior. + */ import type { ResolvedBrowserProfile } from "./config.js"; type BrowserProfileMode = "local-managed" | "local-existing-session" | "remote-cdp"; @@ -14,6 +20,7 @@ type BrowserProfileCapabilities = { supportsManagedTabLimit: boolean; }; +/** Return feature capabilities for a resolved browser profile. */ export function getBrowserProfileCapabilities( profile: ResolvedBrowserProfile, ): BrowserProfileCapabilities { @@ -55,6 +62,7 @@ export function getBrowserProfileCapabilities( }; } +/** Resolve the default snapshot format for a profile and available drivers. */ export function resolveDefaultSnapshotFormat(params: { profile: ResolvedBrowserProfile; hasPlaywright: boolean; @@ -76,6 +84,7 @@ export function resolveDefaultSnapshotFormat(params: { return params.hasPlaywright ? "ai" : "aria"; } +/** Return true when screenshots should use Playwright for the profile. */ export function shouldUsePlaywrightForScreenshot(params: { profile: ResolvedBrowserProfile; wsUrl?: string; @@ -85,6 +94,7 @@ export function shouldUsePlaywrightForScreenshot(params: { return !params.wsUrl || Boolean(params.ref) || Boolean(params.element); } +/** Return true when ARIA snapshots should use Playwright for the profile. */ export function shouldUsePlaywrightForAriaSnapshot(params: { profile: ResolvedBrowserProfile; wsUrl?: string; diff --git a/extensions/browser/src/browser/profiles-service.ts b/extensions/browser/src/browser/profiles-service.ts index f98dfbc5c6eb..8535aca79d98 100644 --- a/extensions/browser/src/browser/profiles-service.ts +++ b/extensions/browser/src/browser/profiles-service.ts @@ -1,3 +1,9 @@ +/** + * Browser profile service. + * + * Implements profile listing, creation, and deletion using browser config + * mutation helpers and route context runtime state. + */ import fs from "node:fs"; import path from "node:path"; import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; @@ -18,6 +24,7 @@ import { isValidProfileName } from "./profiles.js"; import type { BrowserRouteContext, ProfileStatus } from "./server-context.js"; import { movePathToTrash } from "./trash.js"; +/** Input accepted when creating a browser profile. */ export type CreateProfileParams = { name: string; color?: string; @@ -26,6 +33,7 @@ export type CreateProfileParams = { driver?: "openclaw" | "existing-session"; }; +/** Result returned after creating a browser profile. */ export type CreateProfileResult = { ok: true; profile: string; @@ -37,6 +45,7 @@ export type CreateProfileResult = { isRemote: boolean; }; +/** Result returned after deleting a browser profile. */ export type DeleteProfileResult = { ok: true; profile: string; @@ -45,6 +54,7 @@ export type DeleteProfileResult = { const HEX_COLOR_RE = /^#[0-9A-Fa-f]{6}$/; +/** Create a profile service bound to one browser route context. */ export function createBrowserProfilesService(ctx: BrowserRouteContext) { const listProfiles = async (): Promise => { return await ctx.listProfiles(); diff --git a/extensions/browser/src/browser/profiles.ts b/extensions/browser/src/browser/profiles.ts index f111d30a0c32..9807049da094 100644 --- a/extensions/browser/src/browser/profiles.ts +++ b/extensions/browser/src/browser/profiles.ts @@ -1,3 +1,9 @@ +/** + * Browser profile allocation helpers. + * + * Validates profile names and allocates CDP ports/colors for newly persisted + * browser profiles. + */ import { parseBrowserHttpUrl } from "openclaw/plugin-sdk/browser-config"; /** @@ -14,12 +20,15 @@ import { parseBrowserHttpUrl } from "openclaw/plugin-sdk/browser-config"; * 18792-18799 - Reserved for future one-off services (canvas at 18793) */ +/** Default first CDP port for browser profiles. */ export const CDP_PORT_RANGE_START = 18800; +/** Default last CDP port for browser profiles. */ export const CDP_PORT_RANGE_END = 18899; const MAX_TCP_PORT = 65_535; const PROFILE_NAME_REGEX = /^[a-z0-9][a-z0-9-]*$/; +/** Return true when a profile name matches the supported config key format. */ export function isValidProfileName(name: string): boolean { if (!name || name.length > 64) { return false; @@ -27,6 +36,7 @@ export function isValidProfileName(name: string): boolean { return PROFILE_NAME_REGEX.test(name); } +/** Allocate the first unused CDP port in the configured range. */ export function allocateCdpPort( usedPorts: Set, range?: { start: number; end: number }, @@ -51,6 +61,7 @@ function isValidTcpPort(port: number): boolean { return Number.isSafeInteger(port) && port > 0 && port <= MAX_TCP_PORT; } +/** Extract currently used CDP ports from profile config. */ export function getUsedPorts( profiles: Record | undefined, ): Set { @@ -76,6 +87,7 @@ export function getUsedPorts( return used; } +/** Default browser profile color palette. */ export const PROFILE_COLORS = [ "#FF4500", // Orange-red (openclaw default) "#0066CC", // Blue @@ -89,6 +101,7 @@ export const PROFILE_COLORS = [ "#339966", // Teal ]; +/** Allocate the first unused profile color, cycling when all are used. */ export function allocateColor(usedColors: Set): string { // Find first unused color from palette for (const color of PROFILE_COLORS) { @@ -101,6 +114,7 @@ export function allocateColor(usedColors: Set): string { return PROFILE_COLORS[index] ?? PROFILE_COLORS[0]; } +/** Extract currently used profile colors from profile config. */ export function getUsedColors( profiles: Record | undefined, ): Set { diff --git a/extensions/browser/src/browser/proxy-files.ts b/extensions/browser/src/browser/proxy-files.ts index 459fa4de3de7..101170d9edd3 100644 --- a/extensions/browser/src/browser/proxy-files.ts +++ b/extensions/browser/src/browser/proxy-files.ts @@ -1,3 +1,9 @@ +/** + * Browser proxy file helpers. + * + * Persists files returned by node-hosted browser proxy calls and rewrites + * proxied result paths to local saved media paths. + */ import { saveMediaBuffer } from "../media/store.js"; type BrowserProxyFile = { @@ -6,6 +12,7 @@ type BrowserProxyFile = { mimeType?: string; }; +/** Persist proxy-returned files and return a remote-path to local-path map. */ export async function persistBrowserProxyFiles(files: BrowserProxyFile[] | undefined) { if (!files || files.length === 0) { return new Map(); @@ -19,6 +26,7 @@ export async function persistBrowserProxyFiles(files: BrowserProxyFile[] | undef return mapping; } +/** Rewrite result.path when it points at a persisted proxy file. */ export function applyBrowserProxyPaths(result: unknown, mapping: Map) { if (!result || typeof result !== "object") { return;