Skip to content

Commit 0a9f53f

Browse files
committed
fix(browser): enforce sandbox policy for node routes
1 parent 18b2ff6 commit 0a9f53f

2 files changed

Lines changed: 25 additions & 1 deletion

File tree

extensions/browser/src/browser-tool.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,21 @@ describe("browser tool snapshot maxChars", () => {
871871
expect(browserClientMocks.browserStatus).not.toHaveBeenCalled();
872872
});
873873

874+
it.each([
875+
["target=node", { target: "node" }],
876+
["an explicit node pin", { node: "node-1" }],
877+
["automatic node routing", {}],
878+
])("blocks %s when host control is disabled", async (_label, route) => {
879+
mockSingleBrowserProxyNode();
880+
const tool = createBrowserTool({ allowHostControl: false });
881+
882+
await expect(tool.execute?.("call-1", { action: "status", ...route })).rejects.toThrow(
883+
/browser control is disabled by sandbox policy/i,
884+
);
885+
expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled();
886+
expect(browserClientMocks.browserStatus).not.toHaveBeenCalled();
887+
});
888+
874889
it("fails node proxy calls cleanly when payloadJSON is malformed", async () => {
875890
mockSingleBrowserProxyNode();
876891
gatewayMocks.callGatewayTool.mockResolvedValueOnce({
@@ -1247,7 +1262,7 @@ describe("browser tool snapshot maxChars", () => {
12471262
setResolvedBrowserProfiles({
12481263
user: { driver: "existing-session", attachOnly: true, color: "#00AA00" },
12491264
});
1250-
const tool = createBrowserTool();
1265+
const tool = createBrowserTool({ allowHostControl: true });
12511266
await tool.execute?.("call-1", { action: "status", profile: "user", target: "node" });
12521267

12531268
const { options, request } = lastNodeInvokeCall();

extensions/browser/src/browser-tool.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,15 @@ async function resolveBrowserNodeTarget(params: {
262262
requestedNode?: string;
263263
target?: "sandbox" | "host" | "node";
264264
sandboxBridgeUrl?: string;
265+
allowHostControl?: boolean;
265266
}): Promise<BrowserNodeTarget | null> {
267+
if (params.allowHostControl === false) {
268+
if (params.target === "node" || params.requestedNode) {
269+
throw new Error("Node browser control is disabled by sandbox policy.");
270+
}
271+
return null;
272+
}
273+
266274
const cfg = browserToolDeps.getRuntimeConfig();
267275
const policy = cfg.gateway?.nodes?.browser;
268276
const mode = policy?.mode ?? "auto";
@@ -538,6 +546,7 @@ export function createBrowserTool(opts?: {
538546
requestedNode: requestedNode ?? undefined,
539547
target,
540548
sandboxBridgeUrl: opts?.sandboxBridgeUrl,
549+
allowHostControl: opts?.allowHostControl,
541550
});
542551
} catch (error) {
543552
// Keep the logged-in user browser usable on the host when auto-discovery

0 commit comments

Comments
 (0)