mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document browser config paths
This commit is contained in:
@@ -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<BrowserConfig["ssrfPolicy"]> & {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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<BrowserDispatchResponse> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string> {
|
||||
return dirPath;
|
||||
}
|
||||
|
||||
/** Ensure an absolute browser output directory exists and is safe to use. */
|
||||
export async function ensureOutputDirectory(dirPath: string): Promise<void> {
|
||||
const result = await ensureAbsoluteDirectory(
|
||||
await resolveSystemDirectoryAlias(path.resolve(dirPath)),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<ReturnType<typeof resolveExistingPathsWithinRoot>>;
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<ProfileStatus[]> => {
|
||||
return await ctx.listProfiles();
|
||||
|
||||
@@ -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<number>,
|
||||
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<string, { cdpPort?: number; cdpUrl?: string }> | undefined,
|
||||
): Set<number> {
|
||||
@@ -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>): string {
|
||||
// Find first unused color from palette
|
||||
for (const color of PROFILE_COLORS) {
|
||||
@@ -101,6 +114,7 @@ export function allocateColor(usedColors: Set<string>): string {
|
||||
return PROFILE_COLORS[index] ?? PROFILE_COLORS[0];
|
||||
}
|
||||
|
||||
/** Extract currently used profile colors from profile config. */
|
||||
export function getUsedColors(
|
||||
profiles: Record<string, { color: string }> | undefined,
|
||||
): Set<string> {
|
||||
|
||||
@@ -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<string, string>();
|
||||
@@ -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<string, string>) {
|
||||
if (!result || typeof result !== "object") {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user