Skip to content

feat(desktop): passkeys in the in-app browser on macOS - #16370

Draft
bmdavis419 wants to merge 1 commit into
mainfrom
t3/fix-in-app-passkeys
Draft

bmdavis419 wants to merge 1 commit into
mainfrom
t3/fix-in-app-passkeys

Conversation

@bmdavis419

@bmdavis419 bmdavis419 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Passkeys in the in-app browser on macOS. Electron has no WebAuthn UI on macOS, so a passkey prompt in a preview tab (for example Google's "Complete sign-in using your passkey") showed nothing and hung until it timed out. Windows was already fine, because Chromium hands passkeys to Windows Hello there.

There are two paths. Each turns on only when the signed build's provisioning profile authorizes its entitlement, so unsigned and dev builds are unaffected.

  • Touch ID (works in signed builds today).
    • Calls app.configureWebAuthn({ touchID }), the Electron 42+ API, and adds a native account picker for select-webauthn-account.
    • These passkeys are device-bound and live only in T3, so existing iCloud Keychain, 1Password, or phone passkeys don't appear here.
  • System passkey sheet (dormant until Apple approves).
    • Preview pages' navigator.credentials.create/get go over a per-guest IPC handler to macOS AuthenticationServices, using electron-webauthn.
    • That gives the same sheet Safari uses: iCloud Keychain, password managers, phone QR, and security keys, for any site.
    • The main process takes the origin from Chromium's committed senderFrame.origin, never from the page. It rejects subframes, public-suffix RP IDs, unfocused or insecure pages, overlapping requests, and results for pages that navigated mid-ceremony.

Apple steps needed

1. Touch ID: verify only, no Apple request

  • The build adds keychain-access-groups = <TEAM_ID>.com.t3tools.t3code.webauthn, but only when the provisioning profile grants it. Developer ID profiles normally include <TEAM_ID>.*. If the profile doesn't grant it, the build leaves the entitlement off, because macOS refuses to launch an app that claims unauthorized entitlements.
  • Check the profile:
    security cms -D -i t3code.provisionprofile | plutil -extract Entitlements.keychain-access-groups xml1 -o - -
  • On a signed build:
    • The log should read [desktop-artifact] In-app browser passkeys: Touch ID enabled, ….
    • codesign -d --entitlements :- "/Applications/T3 Code.app" should list the keychain group.
    • Creating a passkey in a preview tab (for example from Google account settings) should show a Touch ID prompt, and signing in with it should work.

2. System passkey sheet: needs Apple's managed entitlement

  • Entitlement: com.apple.developer.web-browser.public-key-credential lets a browser make passkey and security-key requests for any relying party. This is what Chrome, Firefox and Flow Browser use. Without it, macOS limits an app to its own associated domains (today that's only the Clerk domain).
  • Who and where: the Account Holder of the T3 Tools Apple Developer org submits https://developer.apple.com/contact/request/macos-browsers-passkeys/ for App ID com.t3tools.t3code. Apple reviews it against the criteria documented here:
    • The app declares the http and https URL schemes in its Info.plist. ⚠️ Open decision: T3 currently declares only t3code and t3code-dev. Adding http/https makes T3 a default-browser candidate in macOS, which also means handling open-url for web links. Decide this before or alongside the request.
    • On launch, the app provides a URL field, search, or bookmarks, and navigates directly to the entered URLs. The case to make is the in-app browser's address bar.
  • After approval:
    • The capability appears on the App ID as a managed capability.
    • Regenerate the Developer ID provisioning profile (Associated Domains plus the new capability), base64-encode it, and update the MACOS_PROVISIONING_PROFILE secret. Both release-desktop.yml and desktop-macos-preview-publish.yml use it.
    • No code change is needed. The packager detects the entitlement in the profile, adds it, and logs browser passkeys enabled.
  • Verify:
    • codesign -d --entitlements :- "/Applications/T3 Code.app" lists com.apple.developer.web-browser.public-key-credential.
    • Signing in to google.com with an iCloud Keychain passkey from a preview tab shows the macOS passkey sheet.

The same steps are summarized in docs/operations/release.md.

Known gaps

  • Sign-in popups: these don't get the system-sheet path yet (they have no preload) and use Touch ID only. Left until the entitlement can actually be tested.
  • Aborted requests: an abort rejects the page's promise, but the native sheet stays open until the user dismisses it. electron-webauthn doesn't expose cancel.
  • Touch ID passkeys after approval: passkeys created through Touch ID before the entitlement lands won't appear in the system sheet afterwards, except in popups.
  • Focus requirement: like Chromium, a request from an unfocused page is refused. A page that asks for a passkey on load while focus sits in T3's address bar fails once; clicking into the page and retrying works.

Important files:

apps/desktop/src/preview/Passkeys.ts

  • New PreviewPasskeys service: Touch ID setup, account picker, and the per-guest ceremony handler (origin pinning, ES256-only registration, rpId default, focus and single-flight gates, deadline).
  • Reads t3codeWebAuthn from the packaged package.json to decide what's enabled.

apps/desktop/src/preview/PasskeyBridge.ts

  • Guest-side WebAuthn bridge. It builds real-looking PublicKeyCredential objects (instanceof, toJSON, extensions) and leaves conditional mediation native.

apps/desktop/src/preview/PasskeyAttestation.ts

  • Small CBOR reader that pulls raw authData out of attestation objects. The library returns JSON instead.

apps/desktop/src/preview-pick-preload.ts, apps/desktop/src/window/DesktopWindow.ts, apps/desktop/src/preview/Manager.ts

  • Pass the bridge flag to guests through additionalArguments, and attach and detach the handlers with each guest and session.

scripts/build-desktop-artifact.ts

  • Parses the provisioning profile's Entitlements plist and adds only the entitlements it grants.
  • Records them in the staged package.json.
  • Keeps pnpm from bundling electron-webauthn's TypeScript peer, about 24 MB.

pnpm-workspace.yaml, third-party-licenses.config.json

  • objc-js prebuilds are allowed without running its install script.
  • The native passkey packages are pinned, since the stage install has no lockfile.
  • License overrides cover packages that ship without license metadata.

Testing

  • New tests:
    • Bridge request routing, credential shapes, and errors and aborts.
    • Main-side origin pinning, frame, focus and secure-origin gates, ES256 negotiation, authData extraction, and the hang deadline.
    • Profile entitlement parsing, including commented and false values.
  • vp test run apps/desktop/src scripts/build-desktop-artifact.test.ts scripts/lib/third-party-licenses.test.ts: 111 files, all passing. Desktop and scripts typecheck and lint are clean.
  • Simulated the staged darwin production install: no TypeScript bundled, and objc-js prebuilds present for arm64 and x64.
  • Not yet run on macOS or in a signed build. The Touch ID check in step 1 is the first real-device test.

Reviewed by a Claude Opus 5.5 subagent and GPT 6.1 Sol (security and correctness); their findings are addressed above or listed as known gaps.

Built with Claude Opus 5.5 in Claude Code, running inside T3 Code.

🤖 Generated with Claude Code

Fixes #5665

Related: #14398 (phone / cross-device passkeys need the system passkey sheet path, which stays dormant until Apple grants the browser passkey entitlement)

Electron has no WebAuthn UI on macOS, so passkey prompts in preview tabs
hung until they timed out. Signed builds now enable Electron's Touch ID
authenticator, and, once Apple grants the managed browser passkey
entitlement, route preview pages' WebAuthn through the system passkey
sheet with the frame's real origin. Each path turns on only when the
provisioning profile authorizes its entitlement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Oct 6, 2026
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 4.9 KiB 5.0 KiB +23 B (+0.5%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB +23 B (+1.9%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 20.8 KiB 20.9 KiB +41 B (+0.2%) 29.3 KiB ✅
Codex Live turn messages 1 2 +1 (+100.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB −17 B (−0.3%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB −17 B (−1.4%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.2 KiB 21.2 KiB 0 B (0.0%) 29.3 KiB ✅
Claude Live turn messages 2 2 0 (0.0%) 8 ✅

Baseline: 250e052 · PR result: bfd26ec · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: In app browser not triggering passkey fingerprint to sign in on mac

2 participants