Skip to content

Commit 0bc591a

Browse files
committed
fix(browser): reject invalid tab indexes
1 parent 0a14f59 commit 0bc591a

2 files changed

Lines changed: 50 additions & 10 deletions

File tree

extensions/browser/src/browser/routes/tabs.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,26 @@ describe("browser tab routes", () => {
335335
expect(navigationGuardMocks.assertBrowserNavigationResultAllowed).not.toHaveBeenCalled();
336336
});
337337

338+
it("rejects invalid tab action indexes instead of treating them as omitted", async () => {
339+
const profileCtx = createProfileContext();
340+
341+
const closeResponse = await callTabsAction({
342+
body: { action: "close", index: "nope" },
343+
profileCtx,
344+
});
345+
const selectResponse = await callTabsAction({
346+
body: { action: "select", index: "1e0" },
347+
profileCtx,
348+
});
349+
350+
expect(closeResponse.statusCode).toBe(400);
351+
expect(closeResponse.body).toEqual({ error: "index must be a non-negative integer" });
352+
expect(selectResponse.statusCode).toBe(400);
353+
expect(selectResponse.body).toEqual({ error: "index must be a non-negative integer" });
354+
expect(profileCtx.closeTab).not.toHaveBeenCalled();
355+
expect(profileCtx.focusTab).not.toHaveBeenCalled();
356+
});
357+
338358
it("labels tabs by friendly target handles", async () => {
339359
const profileCtx = createProfileContext();
340360

extensions/browser/src/browser/routes/tabs.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
12
import { resolveBrowserNavigationProxyMode } from "../browser-proxy-mode.js";
23
import {
34
BrowserProfileUnavailableError,
@@ -12,13 +13,7 @@ import {
1213
import type { BrowserRouteContext, ProfileContext } from "../server-context.js";
1314
import { resolveTargetIdFromTabs } from "../target-id.js";
1415
import type { BrowserRequest, BrowserResponse, BrowserRouteRegistrar } from "./types.js";
15-
import {
16-
asyncBrowserRoute,
17-
getProfileContext,
18-
jsonError,
19-
toNumber,
20-
toStringOrEmpty,
21-
} from "./utils.js";
16+
import { asyncBrowserRoute, getProfileContext, jsonError, toStringOrEmpty } from "./utils.js";
2217

2318
function resolveTabsProfileContext(
2419
req: BrowserRequest,
@@ -136,6 +131,27 @@ function readOptionalTabLabel(body: unknown): string | undefined {
136131
return label || undefined;
137132
}
138133

134+
function readTabIndex(
135+
res: BrowserResponse,
136+
body: unknown,
137+
opts?: { required?: boolean },
138+
): number | null | undefined {
139+
const record = body && typeof body === "object" ? (body as Record<string, unknown>) : {};
140+
if (!Object.hasOwn(record, "index")) {
141+
if (opts?.required) {
142+
jsonError(res, 400, "index is required");
143+
return null;
144+
}
145+
return undefined;
146+
}
147+
const index = parseStrictNonNegativeInteger(record.index);
148+
if (index === undefined) {
149+
jsonError(res, 400, "index must be a non-negative integer");
150+
return null;
151+
}
152+
return index;
153+
}
154+
139155
async function runTabTargetMutation(params: {
140156
req: BrowserRequest;
141157
res: BrowserResponse;
@@ -269,7 +285,6 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
269285
"/tabs/action",
270286
asyncBrowserRoute(async (req, res) => {
271287
const action = toStringOrEmpty((req.body as { action?: unknown })?.action);
272-
const index = toNumber((req.body as { index?: unknown })?.index);
273288

274289
await withTabsProfileRoute({
275290
req,
@@ -320,6 +335,10 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
320335
if (!(await ensureBrowserRunning(profileCtx, res))) {
321336
return;
322337
}
338+
const index = readTabIndex(res, req.body);
339+
if (index === null) {
340+
return;
341+
}
323342
const tabs = await profileCtx.listTabs();
324343
const target = resolveIndexedTab(tabs, index);
325344
if (!target) {
@@ -330,8 +349,9 @@ export function registerBrowserTabRoutes(app: BrowserRouteRegistrar, ctx: Browse
330349
}
331350

332351
if (action === "select") {
333-
if (typeof index !== "number") {
334-
return jsonError(res, 400, "index is required");
352+
const index = readTabIndex(res, req.body, { required: true });
353+
if (index === null || index === undefined) {
354+
return;
335355
}
336356
if (!(await ensureBrowserRunning(profileCtx, res))) {
337357
return;

0 commit comments

Comments
 (0)