From 403cbd1c7b5221edee5fd29b5d74c1d22ce03189 Mon Sep 17 00:00:00 2001 From: Zuch Huang Date: Sun, 2 Aug 2026 14:04:34 +0800 Subject: [PATCH] Preserve the Browser control secret boundary --- .../environments/browser-service.test.ts | 15 ++++++++++++++- src/server/environments/browser-service.ts | 18 +++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/server/environments/browser-service.test.ts b/src/server/environments/browser-service.test.ts index 8b4b59d..07fc220 100644 --- a/src/server/environments/browser-service.test.ts +++ b/src/server/environments/browser-service.test.ts @@ -47,6 +47,9 @@ test("reuses protected coordinates but admits every HTTP and WebSocket request", }, } as unknown as EnvironmentRuntimeAccessService; const runtime = { + async isEnvironmentBrowserTakeoverAvailable() { + return true; + }, async ensureEnvironmentBrowserService( runtime: EnvironmentRuntimeRecord, restart = false, @@ -65,6 +68,16 @@ test("reuses protected coordinates but admits every HTTP and WebSocket request", } as unknown as RuntimeAdapter; const service = new EnvironmentBrowserService(runtimeAccess, runtime); + assert.deepEqual( + await service.control("user-browser", runtimeRecord.id), + { + owner: "agent", + transport: "playwright", + revision: 0, + takeoverAvailable: true, + }, + ); + assert.deepEqual( await service.httpUpstream( "user-browser", @@ -101,7 +114,7 @@ test("reuses protected coordinates but admits every HTTP and WebSocket request", headers: { "X-Sandpi-Browser-Proxy": "secret" }, }, ); - assert.equal(admissions, 4); + assert.equal(admissions, 6); assert.equal(dashboardEnsures, 1); assert.deepEqual(dashboardRestarts, [false]); }); diff --git a/src/server/environments/browser-service.ts b/src/server/environments/browser-service.ts index 0d08498..145a1e3 100644 --- a/src/server/environments/browser-service.ts +++ b/src/server/environments/browser-service.ts @@ -205,7 +205,7 @@ export class EnvironmentBrowserService { ownership.owner === "human" ? this.cacheTakeoverCapability(runtime, true) : await this.takeoverCapabilityForRuntime(runtime); - return { ...ownership, takeoverAvailable }; + return publicBrowserControl(ownership, takeoverAvailable); }, ); this.invalidateControlState(environmentId); @@ -220,7 +220,7 @@ export class EnvironmentBrowserService { ownership: EnvironmentBrowserOwnership, ): Promise { if (ownership.owner === "human") { - return { ...ownership, takeoverAvailable: true }; + return publicBrowserControl(ownership, true); } const takeoverAvailable = await this.serializeServiceOperation( environmentId, @@ -231,7 +231,7 @@ export class EnvironmentBrowserService { (runtime) => this.takeoverCapabilityForRuntime(runtime), ), ); - return { ...ownership, takeoverAvailable }; + return publicBrowserControl(ownership, takeoverAvailable); } private async takeoverCapabilityForRuntime( @@ -471,6 +471,18 @@ export class EnvironmentBrowserService { } } +function publicBrowserControl( + ownership: EnvironmentBrowserOwnership, + takeoverAvailable: boolean, +): EnvironmentBrowserControl { + return { + owner: ownership.owner, + transport: ownership.transport, + revision: ownership.revision, + takeoverAvailable, + }; +} + function sameViewport( left: BrowserDashboardViewport | undefined, right: BrowserDashboardViewport,