// Desktop-only: the local bearer-auth model through the REAL Electron app + // sidecar, covering the two flows a code review found the e2e suite missed: // // A. MCP BROWSER APPROVAL (guards the resume-page bearer): an MCP client hits // a gated tool against the desktop's sidecar; the user approves in the // renderer; the agent's resume completes. This drives the whole desktop // bearer path — the renderer carries NO client-side token, so every // /api/mcp-sessions call (the session-scoped paused GET + the resume POST) // relies on the main process injecting the bearer at the session layer. // // B. BEARER SCOPING (guards against the ambient-credential regression): a // NON-app BrowserWindow (what an OAuth popup is) issuing a request to the // sidecar's gated /api must NOT get the bearer auto-attached — it 401s — // while the app's own window does. Pre-fix the injection covered every // webContents in the session, so a popup could ride the bearer (CSRF). // // Both launch the real app via `_electron.launch` against a throwaway HOME and // read the sidecar's origin + bearer from the on-disk manifest. import { execFile } from "node:child_process"; import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; import { createRequire } from "node:module"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; import { promisify } from "node:util"; import { expect } from "@effect/vitest"; import { Effect } from "effect"; import { _electron, type ElectronApplication } from "playwright"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { scenario } from "../src/scenario"; import { RunDir } from "../src/services"; const appDir = fileURLToPath(new URL("../../apps/desktop/", import.meta.url)); const electronBinary = createRequire(join(appDir, "package.json"))("electron") as string; const APPROVAL_TARGET_TOOL = "executor.coreTools.policies.list"; const EXECUTE_CODE = ` const result = await tools.executor.coreTools.policies.list({}); return JSON.stringify(result); `; interface SidecarConn { readonly origin: string; readonly token: string; } const launchDesktop = async (home: string, runDir: string): Promise => _electron.launch({ executablePath: electronBinary, args: [appDir], cwd: appDir, env: { ...process.env, HOME: home }, recordVideo: { dir: join(runDir, ".video-tmp"), size: { width: 1280, height: 800 } }, timeout: 120_000, }); // The sidecar writes server.json (origin + bearer) once it emits EXECUTOR_READY, // which is exactly when firstWindow resolves — so this is always safe to read // after the window is up. const readSidecar = (home: string): SidecarConn => { const manifest = JSON.parse( readFileSync(join(home, ".executor/server-control/server.json"), "utf8"), ) as { connection: { origin: string; auth?: { token?: string } } }; const origin = manifest.connection.origin; const token = manifest.connection.auth?.token; expect(typeof origin, "sidecar origin in the manifest").toBe("string"); expect(typeof token, "sidecar bearer token in the manifest").toBe("string"); return { origin, token: token! }; }; const closeWithVideo = async (app: ElectronApplication, runDir: string, home: string) => { const page = app.windows()[0]; const video = page?.video(); await app.close().catch(() => {}); const recordedPath = await video?.path().catch(() => undefined); if (recordedPath && existsSync(recordedPath)) { await promisify(execFile)("ffmpeg", [ "-y", "-i", recordedPath, "-c:v", "libx264", "-preset", "veryfast", "-crf", "26", "-pix_fmt", "yuv420p", "-movflags", "+faststart", join(runDir, "session.mp4"), ]).catch(() => {}); } rmSync(join(runDir, ".video-tmp"), { recursive: true, force: true }); rmSync(home, { recursive: true, force: true }); }; // --------------------------------------------------------------------------- // A. MCP browser approval through the desktop app // --------------------------------------------------------------------------- scenario( "Desktop · MCP browser approval: gated tool, approve in the app, resume completes", { timeout: 300_000 }, Effect.gen(function* () { const runDir = yield* RunDir; yield* Effect.promise(() => runApproval(runDir)); }), ); const runApproval = async (runDir: string) => { const home = mkdtempSync(join(tmpdir(), "executor-desktop-mcp-")); const app = await launchDesktop(home, runDir); let stepIndex = 0; try { const page = await app.firstWindow({ timeout: 120_000 }); const step = async (label: string, body: () => Promise) => { await body(); stepIndex += 1; await page.screenshot({ path: join( runDir, `${String(stepIndex).padStart(2, "0")}-${label.toLowerCase().replace(/[^a-z0-9]+/g, "-")}.png`, ), }); }; await step("app boots into the web console", async () => { await page.getByText("Settings").first().waitFor({ timeout: 120_000 }); }); const { origin, token } = readSidecar(home); // Plant a require_approval policy so the gated tool elicits (bearer-authed — // the test process is not the app, so it carries the token explicitly). const policyRes = await fetch(`${origin}/api/policies`, { method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${token}` }, body: JSON.stringify({ owner: "org", pattern: APPROVAL_TARGET_TOOL, action: "require_approval", }), }); expect(policyRes.ok, `policy create ok (${policyRes.status})`).toBe(true); const mcp = new Client({ name: "e2e-desktop-approve", version: "1.0.0" }, { capabilities: {} }); const transport = new StreamableHTTPClientTransport( new URL(`${origin}/mcp?elicitation_mode=browser`), { requestInit: { headers: { authorization: `Bearer ${token}` } } }, ); await mcp.connect(transport); // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: the test owns the MCP transport lifecycle try { const executed = await mcp.callTool({ name: "execute", arguments: { code: EXECUTE_CODE } }); const paused = executed.structuredContent as { status: string; executionId: string; approvalUrl: string; }; expect(paused.status, "execute paused for browser approval").toBe("user_approval_required"); await step("approve the gated tool in the desktop renderer", async () => { // The renderer carries no token; the main process injects the bearer for // THIS window. No ?_token (bootstrap no-ops on desktop). await page.goto(paused.approvalUrl, { waitUntil: "domcontentloaded" }); await page.getByRole("button", { name: "Approve" }).waitFor({ timeout: 30_000 }); expect( await page.getByText("This paused execution is no longer available").count(), "approval page loaded the paused execution (session-injected bearer reached the gated GET)", ).toBe(0); await page.getByRole("button", { name: "Approve" }).click(); await page.getByText("Approve sent").waitFor({ timeout: 15_000 }); }); const resumed = await mcp.callTool({ name: "resume", arguments: { executionId: paused.executionId }, }); expect( (resumed.structuredContent as { status: string }).status, "resume completed after the in-app approval", ).toBe("completed"); } finally { await mcp.close(); } } finally { await closeWithVideo(app, runDir, home); } }; // --------------------------------------------------------------------------- // B. Bearer scoping: a non-app webContents does NOT get the bearer // --------------------------------------------------------------------------- scenario( "Desktop · the bearer is scoped to the app window — a popup webContents gets 401", { timeout: 300_000 }, Effect.gen(function* () { const runDir = yield* RunDir; yield* Effect.promise(() => runBearerScoping(runDir)); }), ); const runBearerScoping = async (runDir: string) => { const home = mkdtempSync(join(tmpdir(), "executor-desktop-scope-")); const app = await launchDesktop(home, runDir); try { const page = await app.firstWindow({ timeout: 120_000 }); await page.getByText("Settings").first().waitFor({ timeout: 120_000 }); const { origin } = readSidecar(home); const gatedUrl = `${origin}/api/scope`; // The app's OWN window: the main process injects the bearer → not 401. const mainStatus = await page.evaluate( (url) => fetch(url) .then((r) => r.status) .catch(() => -1), gatedUrl, ); expect(mainStatus, "app window: injected bearer reaches the gated endpoint").not.toBe(401); // A non-app BrowserWindow (what an OAuth popup is) in the SAME session: its // requests to the sidecar must NOT get the bearer auto-attached → 401. const popupStatus = await app.evaluate( async ({ BrowserWindow }, { origin: o, gatedUrl: g }) => { const popup = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true }, }); // Load the SPA (served unauthenticated) so the fetch has the right origin. await popup.loadURL(`${o}/`); const status = await popup.webContents.executeJavaScript( `fetch(${JSON.stringify(g)}).then((r) => r.status).catch(() => -1)`, ); popup.destroy(); return status as number; }, { origin, gatedUrl }, ); expect(popupStatus, "popup webContents: no bearer injected → gated endpoint rejects it").toBe( 401, ); await page.screenshot({ path: join(runDir, "01-bearer-scoping.png") }); } finally { await closeWithVideo(app, runDir, home); } };