Skip to content

Agent browser: per-pane partition to avoid cert-policy race in controller-preview session #325

Description

@germanescobar

Summary

The cert-verify bypass installed by PR #324 (setPreviewCertPolicy in electron/main.ts) lives on a single session-wide partition ("controller-preview"), shared by every preview pane. When two pane opens overlap, the IPC roundtrip that flips the policy can race a normal open in another pane and clobber an active insecure-mode bypass before the self-signed certificate is fetched, so the agent's --insecure navigation intermittently falls back to Chromium's default and fails with NET::ERR_CERT_AUTHORITY_INVALID.

Repro (race)

  1. Open two worktrees in the same window so both have an active preview pane (pane A and pane B).
  2. From pane A's shell, run controller browser open --insecure https://localhost:5050 against a self-signed dev server.
  3. From pane B's shell, immediately (within the same IPC roundtrip window) run a plain controller browser open https://example.com.
  4. Sometimes pane A loads correctly; sometimes pane A's did-fail-load reports the cert error because pane B's setPreviewCertPolicy({ insecure: false }) IPC reached the main process between pane A's setPreviewCertPolicy({ insecure: true }) and the actual cert handshake for localhost:5050.

The window is small (single IPC roundtrip plus the URL validation) but real, and it widens if any step blocks (e.g. the server-side validatePreviewUrl doing extra work, or the renderer React reconciliation delaying the setPreviewCertPolicy IPC).

What's already there

  • electron/main.ts:438setPreviewCertPolicy(opts) calls electronSession.fromPartition(PREVIEW_PARTITION).setCertificateVerifyProc(...).
  • electron/main.ts:8PREVIEW_PARTITION = "controller-preview" is shared across every pane.
  • client/src/components/PreviewBrowserPool.tsx:447 — the <webview> is rendered with partition: "controller-preview" for every pane.
  • client/src/lib/usePreviewBrowserHost.ts — the renderer fires setPreviewCertPolicy before validatePreviewUrl in the openUrl flow, but there's no per-pane isolation underneath.

Why it matters

The race only affects --insecure flows today, but the same session-wide mutation will become a footgun as we add more per-pane state. Two natural extension points that would re-introduce the race:

Either extension lands on the same race, so fixing the partition shape now is cheaper than fixing each flag individually.

Proposed fix

Per-pane partition. Give each pane its own Electron session partition, derived from the existing browserKey (projectId:worktreeId):

  • Replace PREVIEW_PARTITION = "controller-preview" with a per-key partition string, e.g. controller-preview:${paneKey} (or controller-preview:${browserKey.replace(/[^a-zA-Z0-9_-]/g, "_")} to keep Electron's partition-name grammar happy — partitions only accept alphanumerics, _, and -).
  • The <webview partition={...}> becomes a function of the pane key (already a stable string).
  • setPreviewCertPolicy is called with the target partition as part of the IPC payload (or the main process looks it up from request.frame / the originating webContents — but the explicit payload is simpler).
  • The existing partition-level guards (attachPreviewPartitionGuards, attachPreviewWebviewGuards) iterate over electronSession.fromPartition per pane, or get the same treatment when a pane first appears.

After this lands, two simultaneous opens in different panes cannot race because they don't share a session. The verifier state is per-pane by construction.

Alternative (less recommended)

Per-navigation state on the shared session. Track pending-insecure requests by hostname and only flip the policy off after the cert handshake for the last insecure request completes. More invasive than the per-pane partition, and it leaves the shared-session shape in place — every future per-pane feature re-derives the same race-avoidance.

Acceptance criteria

  • Two simultaneous controller browser open calls in different panes — one --insecure, one not — both succeed.
  • --insecure is still scoped to loopback hosts in both the server-side policy and the Electron cert-verify proc.
  • The existing single-pane flow (controller browser open --insecure https://localhost:5050 against a self-signed cert) still works.
  • No regression on the <webview> webpreferences or the popup / permission guards installed in attachPreviewWebviewGuards.
  • Tests cover the per-pane partition key derivation (Chromane's grammar constraints) and the renderer-side mapping from browserKey to partition string.

Related

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions