From 5eefeee0ce72a03c70efaeb51ce0e43924a5a516 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Wed, 2 Sep 2026 09:19:52 +1000 Subject: [PATCH] refactor(browser): defer permission enforcement --- packages/core/src/plugin/browser/README.md | 9 ++++++--- packages/core/src/plugin/browser/index.ts | 16 ++-------------- packages/sdk/test/browser-plugin.test.ts | 14 +------------- 3 files changed, 9 insertions(+), 30 deletions(-) diff --git a/packages/core/src/plugin/browser/README.md b/packages/core/src/plugin/browser/README.md index a07c2a6250d..9e6087404c4 100644 --- a/packages/core/src/plugin/browser/README.md +++ b/packages/core/src/plugin/browser/README.md @@ -28,6 +28,9 @@ Control events use OpenCode's existing authenticated, server-wide event feed. Consumers filter by `connectionID`; this identifier is correlation, not private event delivery. State and results use RPC calls rather than broadcast events. -The plugin requests normal agent permissions before acting on a URL. Browser -content is untrusted. Pages use the desktop's network, with no server-side tunnel. -The desktop owns Chromium, page isolation, and native controls. +Per-URL permission checks are deferred to the final permission layer (#46530). +Until that layer lands, browser actions do not enforce URL-specific ask or deny +rules. Attachment ownership, page validation, and cancellation remain enforced. + +Browser content is untrusted. Pages use the desktop's network, with no server-side +tunnel. The desktop owns Chromium, page isolation, and native controls. diff --git a/packages/core/src/plugin/browser/index.ts b/packages/core/src/plugin/browser/index.ts index 4b8f2ca7f52..3b2735fd826 100644 --- a/packages/core/src/plugin/browser/index.ts +++ b/packages/core/src/plugin/browser/index.ts @@ -95,20 +95,8 @@ export default Plugin.define({ Effect.gen(function* () { const browser = browsers.get(tool.sessionID) if (!browser) return yield* new Tool.Error({ message: "No desktop browser is connected." }) - if (action.type !== "open") { - if (!browser.state) return yield* new Tool.Error({ message: "Open the browser first." }) - const url = action.type === "navigate" ? action.url : browser.state.url - yield* ctx.permission - .assert({ - action: "browser", - resources: [url], - metadata: { type: action.type, url }, - sessionID: tool.sessionID, - agent: tool.agent, - source: { type: "tool", messageID: tool.messageID, id: tool.id }, - }) - .pipe(Effect.mapError((error) => new Tool.Error({ message: "Browser action failed", error }))) - } + if (action.type !== "open" && !browser.state) + return yield* new Tool.Error({ message: "Open the browser first." }) const requestID = crypto.randomUUID() const pending = yield* Deferred.make() browser.pending.set(requestID, pending) diff --git a/packages/sdk/test/browser-plugin.test.ts b/packages/sdk/test/browser-plugin.test.ts index 055b8a09032..8b6a3e5a42a 100644 --- a/packages/sdk/test/browser-plugin.test.ts +++ b/packages/sdk/test/browser-plugin.test.ts @@ -29,14 +29,12 @@ const fixture = Effect.gen(function* () { project: false, content: JSON.stringify({ plugins: ["-opencode.browser"], - permissions: [{ action: "browser", resource: "*", effect: "allow" }], }), }, models: { fetch: false }, fs: { filewatcher: false, fff: false }, }) const captured = Promise.withResolvers() - const permissions: Array<{ action: string; resources: readonly string[] }> = [] yield* opencode.plugin({ ...plugin, id: "browser-test" }) yield* opencode.plugin({ id: "browser-test-observer", @@ -47,9 +45,6 @@ const fixture = Effect.gen(function* () { const tool = draft.get("browser") if (tool && ctx.location.directory === location.directory) captured.resolve(tool) }) - yield* ctx.permission.hook("evaluate", (event) => - Effect.sync(() => permissions.push({ action: event.action, resources: event.resources })), - ) }).pipe(Effect.orDie), }) yield* opencode.plugin.list({ location }) @@ -80,7 +75,6 @@ const fixture = Effect.gen(function* () { opencode, location, rpc, - permissions, execute, next, attach: Effect.fn(function* (connectionID: string) { @@ -196,7 +190,7 @@ test( ) test( - "commands use published state and permissions, and RPC results render text and screenshot bytes", + "commands use published state, and RPC results render text and screenshot bytes", () => Effect.gen(function* () { const host = yield* fixture @@ -212,7 +206,6 @@ test( options, ) expect((yield* Fiber.join(open.pending)).metadata).toEqual({ url: state.url }) - expect(host.permissions).toEqual([]) yield* host.rpc.state({ ...attached.input, state }, options) const navigate = yield* host.command({ type: "navigate", url: "https://example.org/next" }) @@ -263,11 +256,6 @@ test( ], metadata: { url: updated.url }, }) - expect(host.permissions).toEqual([ - { action: "browser", resources: [updated.url] }, - { action: "browser", resources: [updated.url] }, - { action: "browser", resources: [updated.url] }, - ]) const failure = yield* host.command({ type: "snapshot" }) yield* host.rpc.result( { ...attached.input, requestID: failure.requestID, outcome: { type: "failure", message: "Stale document" } },