fix(browser): require auth on control HTTP and auto-bootstrap token

This commit is contained in:
Peter Steinberger
2026-02-13 02:01:57 +01:00
parent 85409e401b
commit 9230a2ae14
11 changed files with 634 additions and 5 deletions

View File

@@ -287,6 +287,52 @@ describe("security audit", () => {
);
});
it("flags browser control without auth when browser is enabled", async () => {
const cfg: OpenClawConfig = {
gateway: {
controlUi: { enabled: false },
auth: {},
},
browser: {
enabled: true,
},
};
const res = await runSecurityAudit({
config: cfg,
env: {},
includeFilesystem: false,
includeChannelSecurity: false,
});
expect(res.findings).toEqual(
expect.arrayContaining([
expect.objectContaining({ checkId: "browser.control_no_auth", severity: "critical" }),
]),
);
});
it("does not flag browser control auth when gateway token is configured", async () => {
const cfg: OpenClawConfig = {
gateway: {
controlUi: { enabled: false },
auth: { token: "very-long-browser-token-0123456789" },
},
browser: {
enabled: true,
},
};
const res = await runSecurityAudit({
config: cfg,
env: {},
includeFilesystem: false,
includeChannelSecurity: false,
});
expect(res.findings.some((f) => f.checkId === "browser.control_no_auth")).toBe(false);
});
it("warns when remote CDP uses HTTP", async () => {
const cfg: OpenClawConfig = {
browser: {

View File

@@ -2,6 +2,7 @@ import type { ChannelId } from "../channels/plugins/types.js";
import type { OpenClawConfig } from "../config/config.js";
import type { ExecFn } from "./windows-acl.js";
import { resolveBrowserConfig, resolveProfile } from "../browser/config.js";
import { resolveBrowserControlAuth } from "../browser/control-auth.js";
import { resolveChannelDefaultAccountId } from "../channels/plugins/helpers.js";
import { listChannelPlugins } from "../channels/plugins/index.js";
import { formatCliCommand } from "../cli/command-format.js";
@@ -364,7 +365,10 @@ function collectGatewayConfigFindings(
return findings;
}
function collectBrowserControlFindings(cfg: OpenClawConfig): SecurityAuditFinding[] {
function collectBrowserControlFindings(
cfg: OpenClawConfig,
env: NodeJS.ProcessEnv,
): SecurityAuditFinding[] {
const findings: SecurityAuditFinding[] = [];
let resolved: ReturnType<typeof resolveBrowserConfig>;
@@ -385,6 +389,20 @@ function collectBrowserControlFindings(cfg: OpenClawConfig): SecurityAuditFindin
return findings;
}
const browserAuth = resolveBrowserControlAuth(cfg, env);
if (!browserAuth.token && !browserAuth.password) {
findings.push({
checkId: "browser.control_no_auth",
severity: "critical",
title: "Browser control has no auth",
detail:
"Browser control HTTP routes are enabled but no gateway.auth token/password is configured. " +
"Any local process (or SSRF to loopback) can call browser control endpoints.",
remediation:
"Set gateway.auth.token (recommended) or gateway.auth.password so browser control HTTP routes require authentication. Restarting the gateway will auto-generate gateway.auth.token when browser control is enabled.",
});
}
for (const name of Object.keys(resolved.profiles)) {
const profile = resolveProfile(resolved, name);
if (!profile || profile.cdpIsLoopback) {
@@ -924,7 +942,7 @@ export async function runSecurityAudit(opts: SecurityAuditOptions): Promise<Secu
findings.push(...collectSyncedFolderFindings({ stateDir, configPath }));
findings.push(...collectGatewayConfigFindings(cfg, env));
findings.push(...collectBrowserControlFindings(cfg));
findings.push(...collectBrowserControlFindings(cfg, env));
findings.push(...collectLoggingFindings(cfg));
findings.push(...collectElevatedFindings(cfg));
findings.push(...collectHooksHardeningFindings(cfg));