mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 00:31:24 +00:00
refactor: route browser control via gateway/node
This commit is contained in:
@@ -356,82 +356,41 @@ function collectGatewayConfigFindings(
|
||||
return findings;
|
||||
}
|
||||
|
||||
function isLoopbackClientHost(hostname: string): boolean {
|
||||
const h = hostname.trim().toLowerCase();
|
||||
return h === "localhost" || h === "127.0.0.1" || h === "::1";
|
||||
}
|
||||
|
||||
function collectBrowserControlFindings(cfg: ClawdbotConfig): SecurityAuditFinding[] {
|
||||
const findings: SecurityAuditFinding[] = [];
|
||||
|
||||
let resolved: ReturnType<typeof resolveBrowserConfig>;
|
||||
try {
|
||||
resolved = resolveBrowserConfig(cfg.browser);
|
||||
resolved = resolveBrowserConfig(cfg.browser, cfg);
|
||||
} catch (err) {
|
||||
findings.push({
|
||||
checkId: "browser.control_invalid_config",
|
||||
severity: "warn",
|
||||
title: "Browser control config looks invalid",
|
||||
detail: String(err),
|
||||
remediation: `Fix browser.controlUrl/browser.cdpUrl in ${resolveConfigPath()} and re-run "${formatCliCommand("clawdbot security audit --deep")}".`,
|
||||
remediation: `Fix browser.cdpUrl in ${resolveConfigPath()} and re-run "${formatCliCommand("clawdbot security audit --deep")}".`,
|
||||
});
|
||||
return findings;
|
||||
}
|
||||
|
||||
if (!resolved.enabled) return findings;
|
||||
|
||||
const url = new URL(resolved.controlUrl);
|
||||
const isLoopback = isLoopbackClientHost(url.hostname);
|
||||
const envToken = process.env.CLAWDBOT_BROWSER_CONTROL_TOKEN?.trim();
|
||||
const controlToken = (envToken || resolved.controlToken)?.trim() || null;
|
||||
|
||||
if (!isLoopback) {
|
||||
if (!controlToken) {
|
||||
findings.push({
|
||||
checkId: "browser.control_remote_no_token",
|
||||
severity: "critical",
|
||||
title: "Remote browser control is missing an auth token",
|
||||
detail: `browser.controlUrl is non-loopback (${resolved.controlUrl}) but no browser.controlToken (or CLAWDBOT_BROWSER_CONTROL_TOKEN) is configured.`,
|
||||
remediation:
|
||||
"Set browser.controlToken (or export CLAWDBOT_BROWSER_CONTROL_TOKEN) and prefer serving over Tailscale Serve or HTTPS reverse proxy.",
|
||||
});
|
||||
for (const name of Object.keys(resolved.profiles)) {
|
||||
const profile = resolveProfile(resolved, name);
|
||||
if (!profile || profile.cdpIsLoopback) continue;
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(profile.cdpUrl);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (url.protocol === "http:") {
|
||||
findings.push({
|
||||
checkId: "browser.control_remote_http",
|
||||
checkId: "browser.remote_cdp_http",
|
||||
severity: "warn",
|
||||
title: "Remote browser control uses HTTP",
|
||||
detail: `browser.controlUrl=${resolved.controlUrl} is http; this is OK only if it's tailnet-only (Tailscale) or behind another encrypted tunnel.`,
|
||||
remediation: `Prefer HTTPS termination (Tailscale Serve) and keep the endpoint tailnet-only.`,
|
||||
});
|
||||
}
|
||||
|
||||
if (controlToken && controlToken.length < 24) {
|
||||
findings.push({
|
||||
checkId: "browser.control_token_too_short",
|
||||
severity: "warn",
|
||||
title: "Browser control token looks short",
|
||||
detail: `browser control token is ${controlToken.length} chars; prefer a long random token.`,
|
||||
});
|
||||
}
|
||||
|
||||
const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
|
||||
const gatewayAuth = resolveGatewayAuth({ authConfig: cfg.gateway?.auth, tailscaleMode });
|
||||
const gatewayToken =
|
||||
gatewayAuth.mode === "token" &&
|
||||
typeof gatewayAuth.token === "string" &&
|
||||
gatewayAuth.token.trim()
|
||||
? gatewayAuth.token.trim()
|
||||
: null;
|
||||
|
||||
if (controlToken && gatewayToken && controlToken === gatewayToken) {
|
||||
findings.push({
|
||||
checkId: "browser.control_token_reuse_gateway_token",
|
||||
severity: "warn",
|
||||
title: "Browser control token reuses the Gateway token",
|
||||
detail: `browser.controlToken matches gateway.auth token; compromise of browser control expands blast radius to the Gateway API.`,
|
||||
remediation: `Use a separate browser.controlToken dedicated to browser control.`,
|
||||
title: "Remote CDP uses HTTP",
|
||||
detail: `browser profile "${name}" uses http CDP (${profile.cdpUrl}); this is OK only if it's tailnet-only or behind an encrypted tunnel.`,
|
||||
remediation: `Prefer HTTPS/TLS or a tailnet-only endpoint for remote CDP.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user