From ac52499aca0e9528866c23f6aff07a66d99648af Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 03:46:46 -0400 Subject: [PATCH] fix(browser): validate screenshot timeout --- .../routes/agent.snapshot.timeout.test.ts | 14 ++++++++++++++ .../src/browser/routes/agent.snapshot.ts | 18 ++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/extensions/browser/src/browser/routes/agent.snapshot.timeout.test.ts b/extensions/browser/src/browser/routes/agent.snapshot.timeout.test.ts index 4c0b3284037..f846a23d81b 100644 --- a/extensions/browser/src/browser/routes/agent.snapshot.timeout.test.ts +++ b/extensions/browser/src/browser/routes/agent.snapshot.timeout.test.ts @@ -169,4 +169,18 @@ describe("browser agent snapshot timeout routing", () => { }), ); }); + + it("rejects loose screenshot timeoutMs values before dispatching", async () => { + const handler = getScreenshotHandler(); + const response = createBrowserRouteResponse(); + + await handler?.( + { params: {}, query: {}, body: { type: "png", timeoutMs: "1e3" } }, + response.res, + ); + + expect(response.statusCode).toBe(400); + expect(response.body).toEqual({ error: "timeoutMs must be a positive integer." }); + expect(cdpMocks.captureScreenshot).not.toHaveBeenCalled(); + }); }); diff --git a/extensions/browser/src/browser/routes/agent.snapshot.ts b/extensions/browser/src/browser/routes/agent.snapshot.ts index ec5090710c6..d5f1f8c1dd6 100644 --- a/extensions/browser/src/browser/routes/agent.snapshot.ts +++ b/extensions/browser/src/browser/routes/agent.snapshot.ts @@ -43,8 +43,9 @@ import { shouldUsePlaywrightForScreenshot, } from "./agent.snapshot.plan.js"; import { EXISTING_SESSION_LIMITS } from "./existing-session-limits.js"; +import { readRoutePositiveInteger } from "./route-numeric.js"; import type { BrowserResponse, BrowserRouteRegistrar } from "./types.js"; -import { asyncBrowserRoute, jsonError, toBoolean, toNumber, toStringOrEmpty } from "./utils.js"; +import { asyncBrowserRoute, jsonError, toBoolean, toStringOrEmpty } from "./utils.js"; const CHROME_MCP_OVERLAY_ATTR = "data-openclaw-mcp-overlay"; @@ -368,11 +369,16 @@ export function registerBrowserAgentSnapshotRoutes( const element = toStringOrEmpty(body.element) || undefined; const labels = toBoolean(body.labels) ?? false; const type = body.type === "jpeg" ? "jpeg" : "png"; - const timeoutMsRaw = toNumber(body.timeoutMs); - const timeoutMs = - timeoutMsRaw !== undefined - ? normalizeBrowserTimerDelayMs(timeoutMsRaw) - : DEFAULT_BROWSER_SCREENSHOT_TIMEOUT_MS; + let timeoutMs: number; + try { + const timeoutMsRaw = readRoutePositiveInteger(body.timeoutMs, "timeoutMs"); + timeoutMs = + timeoutMsRaw !== undefined + ? normalizeBrowserTimerDelayMs(timeoutMsRaw) + : DEFAULT_BROWSER_SCREENSHOT_TIMEOUT_MS; + } catch (err) { + return jsonError(res, 400, String(err instanceof Error ? err.message : err)); + } if (fullPage && (ref || element)) { return jsonError(res, 400, "fullPage is not supported for element screenshots");