feat(desktop): native messaging for browser pane extensions

Merged
KI
6d ago
+1482-48

Stacked on #9326. Chrome-style native messaging for browser-pane extensions, so 1Password can talk to the 1Password app.

Changes

  • What was there: electron-chrome-extensions had a broken native messaging path. It treated each chunk of host output as one message, counted characters instead of bytes, never set lastError, and never stopped hosts. The patch removes it.
  • New path: extension-host/native-messaging/ replaces it. It finds hosts the way Chrome does, in Chrome's and Chromium's NativeMessagingHosts folders plus the app's own:
    • the extension's chrome-extension://<id>/ must be listed exactly in allowed_origins
    • the host is started from the main process, with the origin as its argument
    • framing: 4-byte length; host → extension capped at 1 MB, extension → host at 64 MiB
    • onDisconnect reports through lastError
    • sendNativeMessage is one-shot
    • hosts are stopped on disconnect, extension unload and app quit
  • Who gets it: connectNative and sendNativeMessage are added only in extension contexts whose manifest declares nativeMessaging. The main process takes the extension ID from the sender, never from the message. Websites and the agent bridge can't reach it.

Testing

  • 13 unit tests: framing, manifest and allowed_origins checks, permission gate, and a real tiny host (origin argument, working directory, exit, kill on disconnect and unload).
  • Dev app: the real 1Password extension's sendNativeMessage("com.1password.1password") reaches the 1Password helper, which answers BrowserVerificationFailed. That is expected for an unsigned dev build. 1Password only trusts signed browsers installed in /Applications, which users add under Settings → Browser → Add Browser (docs). Unlocking end to end needs a signed canary; not tested yet.
  • Windows path untested.

Summary by cubic

Adds Chrome-style native messaging for browser-pane extensions so 1Password can reach the 1Password app through chrome.runtime.connectNative and sendNativeMessage.

The old electron-chrome-extensions implementation is removed: it treated each chunk of host output as one message, counted characters instead of bytes, never set lastError, and never stopped host processes. The new path in native-messaging/ finds and validates hosts the way Chrome does, spawns them from the main process, and cleans them up on disconnect, unload, and quit.

Notes

  • Hosts are discovered in Chrome's and Chromium's NativeMessagingHosts folders plus the app's own, with registry lookup on Windows; manifests must pass Chrome's name, type: stdio, path, and exact allowed_origins checks.
  • Only extension contexts declaring nativeMessaging get the APIs; the extension ID comes from the sender, never the message, so websites and the agent bridge can't reach it.
  • Framing uses a 4-byte native-endian length with Chrome's limits (1 MB from host, 64 MiB to host); host failures arrive via onDisconnect and runtime.lastError, and sendNativeMessage closes after the first reply.
  • An open port keeps the service worker alive, and hosts are stopped on disconnect, worker stop, frame navigation or close, extension unload, and before-quit; a host that stops reading its input closes the port instead of buffering without limit.
  • End-to-end unlock requires a signed build added to 1Password's browser settings; the Windows path is untested.

Summary by CodeRabbit

  • New Features
    • Browser extensions with the required permission can connect to native messaging hosts, exchange messages, and receive connection errors.
    • Browser-action and context-menu interactions can grant temporary access to the active tab; access is revoked when the tab changes origin or is removed.
  • Bug Fixes
    • Extension access to tabs, windows, cookies, and other browser features is more consistently checked against permissions.
    • Invalid or inaccessible native messaging hosts and unauthorized extension requests are rejected.
    • Window lookup handles unavailable last-focused windows more reliably, and browser-action popups open only when configured.
    • File and data URLs are rejected where extension URL access is restricted.