


























@@ -39,6 +39,8 @@ type ExecDockerRawFn = (
3939opts?: { allowFailure?: boolean; input?: Buffer | string; signal?: AbortSignal },
4040) => Promise<import("../agents/sandbox/docker.js").ExecDockerRawResult>;
414142+const DEFAULT_SANDBOX_BROWSER_DOCKER_PROBE_TIMEOUT_MS = 5000;
43+4244type CodeSafetySummaryCache = Map<string, Promise<unknown>>;
4345let skillsModulePromise: Promise<typeof import("../agents/skills.js")> | undefined;
4446let configModulePromise: Promise<typeof import("../config/config.js")> | undefined;
@@ -274,13 +276,63 @@ function normalizeDockerLabelValue(raw: string | undefined): string | null {
274276return trimmed;
275277}
276278277-async function listSandboxBrowserContainers(
278-execDockerRawFn: ExecDockerRawFn,
279-): Promise<string[] | null> {
279+class DockerProbeTimeoutError extends Error {
280+constructor(timeoutMs: number) {
281+super(`Docker probe timed out after ${timeoutMs}ms`);
282+this.name = "DockerProbeTimeoutError";
283+}
284+}
285+286+function normalizeDockerProbeTimeoutMs(timeoutMs: number | undefined): number {
287+if (Number.isFinite(timeoutMs) && timeoutMs !== undefined) {
288+return Math.max(250, Math.floor(timeoutMs));
289+}
290+return DEFAULT_SANDBOX_BROWSER_DOCKER_PROBE_TIMEOUT_MS;
291+}
292+293+async function withDockerProbeTimeout<T>(
294+timeoutMs: number,
295+run: (signal: AbortSignal) => Promise<T>,
296+): Promise<T> {
297+const controller = new AbortController();
298+let timeout: NodeJS.Timeout | undefined;
299+let timedOut = false;
300+const timeoutPromise = new Promise<never>((_, reject) => {
301+timeout = setTimeout(() => {
302+timedOut = true;
303+controller.abort();
304+reject(new DockerProbeTimeoutError(timeoutMs));
305+}, timeoutMs);
306+});
307+try {
308+return await Promise.race([run(controller.signal), timeoutPromise]);
309+} catch (err) {
310+if (timedOut || controller.signal.aborted) {
311+throw new DockerProbeTimeoutError(timeoutMs);
312+}
313+throw err;
314+} finally {
315+if (timeout) {
316+clearTimeout(timeout);
317+}
318+}
319+}
320+321+function isDockerProbeTimeoutError(error: unknown): boolean {
322+return error instanceof DockerProbeTimeoutError;
323+}
324+325+async function listSandboxBrowserContainers(params: {
326+execDockerRawFn: ExecDockerRawFn;
327+timeoutMs: number;
328+onTimeout?: () => void;
329+}): Promise<string[] | null> {
280330try {
281-const result = await execDockerRawFn(
282-["ps", "-a", "--filter", "label=openclaw.sandboxBrowser=1", "--format", "{{.Names}}"],
283-{ allowFailure: true },
331+const result = await withDockerProbeTimeout(params.timeoutMs, (signal) =>
332+params.execDockerRawFn(
333+["ps", "-a", "--filter", "label=openclaw.sandboxBrowser=1", "--format", "{{.Names}}"],
334+{ allowFailure: true, signal },
335+),
284336);
285337if (result.code !== 0) {
286338return null;
@@ -290,24 +342,31 @@ async function listSandboxBrowserContainers(
290342.split(/\r?\n/)
291343.map((entry) => entry.trim())
292344.filter(Boolean);
293-} catch {
345+} catch (err) {
346+if (isDockerProbeTimeoutError(err)) {
347+params.onTimeout?.();
348+}
294349return null;
295350}
296351}
297352298353async function readSandboxBrowserHashLabels(params: {
299354containerName: string;
300355execDockerRawFn: ExecDockerRawFn;
356+timeoutMs: number;
357+onTimeout?: () => void;
301358}): Promise<{ configHash: string | null; epoch: string | null } | null> {
302359try {
303-const result = await params.execDockerRawFn(
304-[
305-"inspect",
306-"-f",
307-'{{ index .Config.Labels "openclaw.configHash" }}\t{{ index .Config.Labels "openclaw.browserConfigEpoch" }}',
308-params.containerName,
309-],
310-{ allowFailure: true },
360+const result = await withDockerProbeTimeout(params.timeoutMs, (signal) =>
361+params.execDockerRawFn(
362+[
363+"inspect",
364+"-f",
365+'{{ index .Config.Labels "openclaw.configHash" }}\t{{ index .Config.Labels "openclaw.browserConfigEpoch" }}',
366+params.containerName,
367+],
368+{ allowFailure: true, signal },
369+),
311370);
312371if (result.code !== 0) {
313372return null;
@@ -317,7 +376,10 @@ async function readSandboxBrowserHashLabels(params: {
317376configHash: normalizeDockerLabelValue(hashRaw),
318377epoch: normalizeDockerLabelValue(epochRaw),
319378};
320-} catch {
379+} catch (err) {
380+if (isDockerProbeTimeoutError(err)) {
381+params.onTimeout?.();
382+}
321383return null;
322384}
323385}
@@ -349,11 +411,16 @@ function isLoopbackPublishHost(host: string): boolean {
349411async function readSandboxBrowserPortMappings(params: {
350412containerName: string;
351413execDockerRawFn: ExecDockerRawFn;
414+timeoutMs: number;
415+onTimeout?: () => void;
352416}): Promise<string[] | null> {
353417try {
354-const result = await params.execDockerRawFn(["port", params.containerName], {
355-allowFailure: true,
356-});
418+const result = await withDockerProbeTimeout(params.timeoutMs, (signal) =>
419+params.execDockerRawFn(["port", params.containerName], {
420+allowFailure: true,
421+ signal,
422+}),
423+);
357424if (result.code !== 0) {
358425return null;
359426}
@@ -362,21 +429,37 @@ async function readSandboxBrowserPortMappings(params: {
362429.split(/\r?\n/)
363430.map((entry) => entry.trim())
364431.filter(Boolean);
365-} catch {
432+} catch (err) {
433+if (isDockerProbeTimeoutError(err)) {
434+params.onTimeout?.();
435+}
366436return null;
367437}
368438}
369439370440export async function collectSandboxBrowserHashLabelFindings(params?: {
371441execDockerRawFn?: ExecDockerRawFn;
442+timeoutMs?: number;
372443}): Promise<SecurityAuditFinding[]> {
373444const findings: SecurityAuditFinding[] = [];
445+const timeoutMs = normalizeDockerProbeTimeoutMs(params?.timeoutMs);
446+let timedOut = false;
447+const markTimedOut = () => {
448+timedOut = true;
449+};
374450const [execFn, browserHashEpoch] = await Promise.all([
375451params?.execDockerRawFn ? Promise.resolve(params.execDockerRawFn) : loadExecDockerRaw(),
376452loadSandboxBrowserSecurityHashEpoch(),
377453]);
378-const containers = await listSandboxBrowserContainers(execFn);
454+const containers = await listSandboxBrowserContainers({
455+execDockerRawFn: execFn,
456+ timeoutMs,
457+onTimeout: markTimedOut,
458+});
379459if (!containers || containers.length === 0) {
460+if (timedOut) {
461+findings.push(buildSandboxBrowserDockerProbeTimeoutFinding(timeoutMs));
462+}
380463return findings;
381464}
382465@@ -385,7 +468,15 @@ export async function collectSandboxBrowserHashLabelFindings(params?: {
385468const nonLoopbackPublished: string[] = [];
386469387470for (const containerName of containers) {
388-const labels = await readSandboxBrowserHashLabels({ containerName, execDockerRawFn: execFn });
471+const labels = await readSandboxBrowserHashLabels({
472+ containerName,
473+execDockerRawFn: execFn,
474+ timeoutMs,
475+onTimeout: markTimedOut,
476+});
477+if (timedOut) {
478+break;
479+}
389480if (!labels) {
390481continue;
391482}
@@ -398,7 +489,12 @@ export async function collectSandboxBrowserHashLabelFindings(params?: {
398489const portMappings = await readSandboxBrowserPortMappings({
399490 containerName,
400491execDockerRawFn: execFn,
492+ timeoutMs,
493+onTimeout: markTimedOut,
401494});
495+if (timedOut) {
496+break;
497+}
402498if (!portMappings?.length) {
403499continue;
404500}
@@ -449,9 +545,26 @@ export async function collectSandboxBrowserHashLabelFindings(params?: {
449545});
450546}
451547548+if (timedOut) {
549+findings.push(buildSandboxBrowserDockerProbeTimeoutFinding(timeoutMs));
550+}
551+452552return findings;
453553}
454554555+function buildSandboxBrowserDockerProbeTimeoutFinding(timeoutMs: number): SecurityAuditFinding {
556+return {
557+checkId: "sandbox.browser_container.docker_probe_timeout",
558+severity: "warn",
559+title: "Sandbox browser Docker audit probe timed out",
560+detail:
561+`Docker did not answer within ${timeoutMs}ms while checking sandbox browser containers. ` +
562+"OpenClaw skipped any remaining sandbox browser container drift checks for this status run.",
563+remediation:
564+"Retry after Docker is responsive, or recreate sandbox browser containers if drift is suspected.",
565+};
566+}
567+455568export async function collectIncludeFilePermFindings(params: {
456569configSnapshot: ConfigFileSnapshot;
457570env?: NodeJS.ProcessEnv;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。