Skip to content

fix(desktop): add sidecar credentials from the main process - #47588

Merged
Hona merged 2 commits into
anomalyco:v2from
Hona:sidecar-auth-injection
Sep 6, 2026
Merged

fix(desktop): add sidecar credentials from the main process#47588
Hona merged 2 commits into
anomalyco:v2from
Hona:sidecar-auth-injection

Conversation

@Hona

@Hona Hona commented Sep 6, 2026

Copy link
Copy Markdown
Member

Follow-up to #47560. That PR made Desktop's CORS preflights cacheable; this one removes them from GET requests to the local sidecar entirely, and moves the sidecar credential out of the renderer.

The renderer at oc://renderer is cross-origin to http://127.0.0.1:<port>, and the only thing that made its GETs non-simple was the Authorization header. Per the Fetch spec a request with just CORS-safelisted headers needs no preflight, so if the renderer stops sending credentials and the main process adds them, every GET becomes one round trip instead of two. POSTs with a JSON body still preflight, but those are cached after #47560.

sequenceDiagram
  participant R as renderer (oc://renderer, top frame)
  participant N as Chromium network service
  participant M as main: onBeforeSendHeaders
  participant S as sidecar
  R->>N: GET /api/session/… (no Authorization)
  Note over N: CORS: safelisted headers only → no preflight
  N->>M: extraHeaders hook (details.frame = renderer top frame)
  M-->>N: + Authorization: Basic …
  N->>S: GET /api/session/…
Loading

Electron registers webRequest listeners in Chromium's extraHeaders mode, which runs after the CORS decision; header changes made there do not trigger a preflight (the documented behaviour extensions rely on to add credentials).

Changes — packages/desktop

Credential stays in main. ServerReadyData over IPC is now { url } only; the password never reaches the renderer. SidecarCredentials (main/service/sidecar-credentials.ts) holds { url, password }, is published on the initial connection and every reconnect, and ready() is what the IPC handlers return.

Injection is scoped to the renderer's own top-level frame. Anything else in the session (web views, embedded pages, sub-frames, frame-less requests such as a service worker) can reach the same loopback origin and gets nothing — the same rule allowRendererPermissions already applies to permission requests. Fails closed when details.frame is null.

win.webContents.session.webRequest.onBeforeSendHeaders(
  { urls: ["http://127.0.0.1/*", "http://localhost/*"] },
  (details, callback) => {
    const frame = details.frame
    const renderer = !!frame && frame.parent === null && isRendererUrl(frame.url)
    const authorization = renderer && SidecarCredentials.authorization(SidecarCredentials.get(), details.url)
    if (authorization && !hasHeader(details.requestHeaders, "Authorization")) {
      upsertHeader(details.requestHeaders, "Authorization", authorization)
    }
    callback({ requestHeaders: details.requestHeaders })
  },
)
  • authorization(sidecar, url) matches the request origin exactly against the sidecar origin; remote servers (WSL, Tailscale, manual) keep sending credentials from the renderer as before.
  • Renderer: sidecarHttp passes only the URL into the app's ServerConnection, createSidecarResolver compares URLs, and the migration-status client no longer builds a header. The app package is untouched: a connection without a password already means "send no Authorization".

Against the Electron security checklist

  • Least privilege: the secret lives in one process, the one that already had it.
  • tweak: glob should be able to read certain hidden files/dirs #5/Override Default Keymaps #17 spirit — privileged behaviour gated on the requesting frame's origin, validated with a URL parser, deny by default.
  • Cross-origin redirects: Chromium strips Authorization itself, and the listener re-checks the origin on every leg.
  • Not applicable yet: session.webRequest.setHeaderRules() (declarative, no main-process hop) lands in a newer Electron than 42; worth switching to when Electron is bumped.
  • Pre-existing and out of scope: the desktop renderer defines no Content-Security-Policy (feature: Interactive Configuration #7).

The web app / PWA is unaffected by this PR; it never had the desktop preflight-cache bug and keeps building its own header.

@Hona
Hona requested a review from Brendonovich as a code owner September 6, 2026 06:40
Copilot AI lite review requested due to automatic review settings September 6, 2026 06:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Hona
Hona force-pushed the sidecar-auth-injection branch from 90f5762 to 61329e0 Compare September 6, 2026 06:57
@Hona
Hona enabled auto-merge (squash) September 6, 2026 07:00
@Hona
Hona merged commit b2cecc6 into anomalyco:v2 Sep 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants