diff --git a/packages/opencode/src/server/shared/public-ui.ts b/packages/opencode/src/server/shared/public-ui.ts index 4a92c9aeac..ee67686ff2 100644 --- a/packages/opencode/src/server/shared/public-ui.ts +++ b/packages/opencode/src/server/shared/public-ui.ts @@ -12,6 +12,13 @@ export const PUBLIC_UI_PATHS = new Set([ // bridge after boot), so it is public-shell class within the same-user // trust model. The widget registry route (/amicode/widgets) stays authed. "/amicode/widget-frame", + // The amico brain graph is the same class: brain-strip.tsx loads /brain.html + // in an iframe (with /brain.js) that cannot carry ?auth_token=, so with a + // password set the frame 401s and paints opaque white. Both are static + // public-shell assets (no secrets; session touches arrive via the same + // postMessage bridge as widgets). Sibling to the widget-frame exemption. + "/brain.html", + "/brain.js", ]) // The app shell's fingerprinted bundles live under /assets/. They are plain diff --git a/packages/opencode/test/server/httpapi-ui.test.ts b/packages/opencode/test/server/httpapi-ui.test.ts index 69e9616aa9..5eb87b84d9 100644 --- a/packages/opencode/test/server/httpapi-ui.test.ts +++ b/packages/opencode/test/server/httpapi-ui.test.ts @@ -502,6 +502,31 @@ describe("HttpApi UI fallback", () => { }), ) + // Regression (amicode): the amico brain graph loads /brain.html (+ /brain.js) + // in an iframe that cannot carry ?auth_token= — same class as widget-frame — + // so with a password set both 401'd and the frame painted opaque white. They + // are static public-shell assets; session touches arrive via the postMessage + // bridge. GET-only; other root paths stay authed. + it.live("serves the brain graph frame + script without auth even when a server password is set", () => + Effect.gen(function* () { + for (const path of ["/brain.html?mode=live&colorScheme=dark", "/brain.js"]) { + const response = yield* uiApp({ + password: "secret", + username: "opencode", + disableEmbeddedWebUi: true, + client: httpClient(new Response("ok")), + }).request(path) + expect(response.status).not.toBe(401) + } + const post = yield* uiApp({ + password: "secret", + username: "opencode", + disableEmbeddedWebUi: true, + }).request("/brain.html", { method: "POST" }) + expect(post.status).toBe(401) + }), + ) + it.live("allows web UI preflight without auth", () => Effect.gen(function* () { const response = yield* app({ password: "secret", username: "opencode" }).request("/", {