fix(browser): authenticate sandbox browser bridge server

This commit is contained in:
Peter Steinberger
2026-02-14 12:54:16 +01:00
parent 3b56a6252b
commit 4711a943e3
6 changed files with 191 additions and 7 deletions

View File

@@ -1,3 +1,11 @@
import type { BrowserBridge } from "../../browser/bridge-server.js";
export const BROWSER_BRIDGES = new Map<string, { bridge: BrowserBridge; containerName: string }>();
export const BROWSER_BRIDGES = new Map<
string,
{
bridge: BrowserBridge;
containerName: string;
authToken?: string;
authPassword?: string;
}
>();

View File

@@ -90,6 +90,7 @@ export async function ensureSandboxBrowser(params: {
agentWorkspaceDir: string;
cfg: SandboxConfig;
evaluateEnabled?: boolean;
bridgeAuth?: { token?: string; password?: string };
}): Promise<SandboxBrowserContext | null> {
if (!params.cfg.browser.enabled) {
return null;
@@ -148,19 +149,29 @@ export async function ensureSandboxBrowser(params: {
? await readDockerPort(containerName, params.cfg.browser.noVncPort)
: null;
const desiredAuthToken = params.bridgeAuth?.token?.trim() || undefined;
const desiredAuthPassword = params.bridgeAuth?.password?.trim() || undefined;
const existing = BROWSER_BRIDGES.get(params.scopeKey);
const existingProfile = existing
? resolveProfile(existing.bridge.state.resolved, DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME)
: null;
const shouldReuse =
existing && existing.containerName === containerName && existingProfile?.cdpPort === mappedCdp;
const authMatches =
!existing ||
(existing.authToken === desiredAuthToken && existing.authPassword === desiredAuthPassword);
if (existing && !shouldReuse) {
await stopBrowserBridgeServer(existing.bridge.server).catch(() => undefined);
BROWSER_BRIDGES.delete(params.scopeKey);
}
if (existing && shouldReuse && !authMatches) {
await stopBrowserBridgeServer(existing.bridge.server).catch(() => undefined);
BROWSER_BRIDGES.delete(params.scopeKey);
}
const bridge = (() => {
if (shouldReuse && existing) {
if (shouldReuse && authMatches && existing) {
return existing.bridge;
}
return null;
@@ -196,15 +207,19 @@ export async function ensureSandboxBrowser(params: {
headless: params.cfg.browser.headless,
evaluateEnabled: params.evaluateEnabled ?? DEFAULT_BROWSER_EVALUATE_ENABLED,
}),
authToken: desiredAuthToken,
authPassword: desiredAuthPassword,
onEnsureAttachTarget,
});
};
const resolvedBridge = await ensureBridge();
if (!shouldReuse) {
if (!shouldReuse || !authMatches) {
BROWSER_BRIDGES.set(params.scopeKey, {
bridge: resolvedBridge,
containerName,
authToken: desiredAuthToken,
authPassword: desiredAuthPassword,
});
}

View File

@@ -2,6 +2,8 @@ import fs from "node:fs/promises";
import type { OpenClawConfig } from "../../config/config.js";
import type { SandboxContext, SandboxWorkspaceInfo } from "./types.js";
import { DEFAULT_BROWSER_EVALUATE_ENABLED } from "../../browser/constants.js";
import { ensureBrowserControlAuth, resolveBrowserControlAuth } from "../../browser/control-auth.js";
import { loadConfig } from "../../config/config.js";
import { defaultRuntime } from "../../runtime.js";
import { resolveUserPath } from "../../utils.js";
import { syncSkillsToWorkspace } from "../skills.js";
@@ -76,12 +78,30 @@ export async function resolveSandboxContext(params: {
const evaluateEnabled =
params.config?.browser?.evaluateEnabled ?? DEFAULT_BROWSER_EVALUATE_ENABLED;
const bridgeAuth = cfg.browser.enabled
? await (async () => {
// Sandbox browser bridge server runs on a loopback TCP port; always wire up
// the same auth that loopback browser clients will send (token/password).
const cfgForAuth = params.config ?? loadConfig();
let browserAuth = resolveBrowserControlAuth(cfgForAuth);
try {
const ensured = await ensureBrowserControlAuth({ cfg: cfgForAuth });
browserAuth = ensured.auth;
} catch (error) {
const message = error instanceof Error ? error.message : JSON.stringify(error);
defaultRuntime.error?.(`Sandbox browser auth ensure failed: ${message}`);
}
return browserAuth;
})()
: undefined;
const browser = await ensureSandboxBrowser({
scopeKey,
workspaceDir,
agentWorkspaceDir,
cfg,
evaluateEnabled,
bridgeAuth,
});
const sandboxContext: SandboxContext = {