diff --git a/extensions/browser/src/browser-tool.test.ts b/extensions/browser/src/browser-tool.test.ts index 978a4cbad677..7539111a2347 100644 --- a/extensions/browser/src/browser-tool.test.ts +++ b/extensions/browser/src/browser-tool.test.ts @@ -871,6 +871,21 @@ describe("browser tool snapshot maxChars", () => { expect(browserClientMocks.browserStatus).not.toHaveBeenCalled(); }); + it.each([ + ["target=node", { target: "node" }], + ["an explicit node pin", { node: "node-1" }], + ["automatic node routing", {}], + ])("blocks %s when host control is disabled", async (_label, route) => { + mockSingleBrowserProxyNode(); + const tool = createBrowserTool({ allowHostControl: false }); + + await expect(tool.execute?.("call-1", { action: "status", ...route })).rejects.toThrow( + /browser control is disabled by sandbox policy/i, + ); + expect(gatewayMocks.callGatewayTool).not.toHaveBeenCalled(); + expect(browserClientMocks.browserStatus).not.toHaveBeenCalled(); + }); + it("fails node proxy calls cleanly when payloadJSON is malformed", async () => { mockSingleBrowserProxyNode(); gatewayMocks.callGatewayTool.mockResolvedValueOnce({ @@ -1247,7 +1262,7 @@ describe("browser tool snapshot maxChars", () => { setResolvedBrowserProfiles({ user: { driver: "existing-session", attachOnly: true, color: "#00AA00" }, }); - const tool = createBrowserTool(); + const tool = createBrowserTool({ allowHostControl: true }); await tool.execute?.("call-1", { action: "status", profile: "user", target: "node" }); const { options, request } = lastNodeInvokeCall(); diff --git a/extensions/browser/src/browser-tool.ts b/extensions/browser/src/browser-tool.ts index b92f646c9014..463085cfd502 100644 --- a/extensions/browser/src/browser-tool.ts +++ b/extensions/browser/src/browser-tool.ts @@ -262,7 +262,15 @@ async function resolveBrowserNodeTarget(params: { requestedNode?: string; target?: "sandbox" | "host" | "node"; sandboxBridgeUrl?: string; + allowHostControl?: boolean; }): Promise { + if (params.allowHostControl === false) { + if (params.target === "node" || params.requestedNode) { + throw new Error("Node browser control is disabled by sandbox policy."); + } + return null; + } + const cfg = browserToolDeps.getRuntimeConfig(); const policy = cfg.gateway?.nodes?.browser; const mode = policy?.mode ?? "auto"; @@ -538,6 +546,7 @@ export function createBrowserTool(opts?: { requestedNode: requestedNode ?? undefined, target, sandboxBridgeUrl: opts?.sandboxBridgeUrl, + allowHostControl: opts?.allowHostControl, }); } catch (error) { // Keep the logged-in user browser usable on the host when auto-discovery