Skip to content

fix: signing order, @available guards, redundant popup call for FIDO2 - #1876

Closed
Jesssullivan wants to merge 1 commit into
manaflow-ai:issue-124-passkeys-webauthnfrom
Jesssullivan:dev/fido2-cherrypicks
Closed

Jesssullivan wants to merge 1 commit into
manaflow-ai:issue-124-passkeys-webauthnfrom
Jesssullivan:dev/fido2-cherrypicks

Conversation

@Jesssullivan

@Jesssullivan Jesssullivan commented Mar 20, 2026 •

Copy link
Copy Markdown
Contributor

Fixes for #1823 @ manaflow-ai:issue-124-passkeys-webauthn

From earlier comments made here #1823 (comment)

  • Codesign order: sign app --deep first, then re-sign embedded binaries with narrow entitlements
  • @available(macOS 15.0, *): guards on BrowserPasskeyAuthorizationCoordinator — crashes on Sonoma without this
  • Redundant popup bridge call: remove duplicate configurePasskeyAuthorizationBridge in createNestedPopup

-Jess


Summary by cubic

Fixes passkey WebAuthn crashes on macOS 14 and prevents CAPTCHA failures from cross‑iframe script injection. Also corrects codesign order so embedded binaries keep narrow entitlements (fixes #1823, #1429).

  • Bug Fixes
    • Codesign: sign app bundle first with --deep, then re-sign cmux/ghostty with embedded entitlements (workflows and build script updated).
    • Availability: add @available(macOS 15.0, *) to the passkey coordinator and call sites; guard bridge setup so it no-ops on macOS 14.
    • Popups: remove duplicate configurePasskeyAuthorizationBridge call in createNestedPopup.
    • CAPTCHA: set forMainFrameOnly: true for telemetry, focus tracking, and passkey scripts to avoid tampering detection in third‑party iframes.

Written for commit 0ac2335. Summary will update on new commits.

@vercel

vercel Bot commented Mar 20, 2026

Copy link
Copy Markdown

@Jesssullivan is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 20, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4fec087b-3744-4193-adc4-adcfb0b164ed

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Jesssullivan Jesssullivan changed the title Fix signing order, @available guards, redundant popup call, and CAPTCHA interference fix: signing order, @available guards, redundant popup call, and CAPTCHA interference Mar 20, 2026
@Jesssullivan Jesssullivan changed the title fix: signing order, @available guards, redundant popup call, and CAPTCHA interference fix: signing order, @available guards, redundant popup call for FIDO2 Mar 20, 2026
- Codesign: sign app --deep first, then re-sign embedded binaries
  with narrow entitlements (build-sign-upload.sh, nightly.yml, release.yml)
- Add @available(macOS 15.0, *) on BrowserPasskeyAuthorizationCoordinator
  and call sites — ASAuthorizationWebBrowserPublicKeyCredentialManager
  is 15.0+ only, crashes on Sonoma without this
- Remove redundant configurePasskeyAuthorizationBridge in createNestedPopup
  (already called in init)

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 5 files

@greptile-apps

greptile-apps Bot commented Mar 20, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes three distinct bugs introduced during the passkey/WebAuthn feature work on branch issue-124-passkeys-webauthn: a macOS 14 crash caused by missing availability guards on BrowserPasskeyAuthorizationCoordinator, a codesign ordering bug that allowed the --deep re-sign to overwrite narrow entitlements on embedded binaries, and a double-registration of the passkey message handler bridge in nested popup windows.

  • Codesign order (build-sign-upload.sh, nightly.yml, release.yml): The previous order signed embedded binaries with narrow entitlements first, then signed the whole app bundle with --deep — which overwrote those narrow signatures. The fix reverses this: --deep is applied to the bundle first so all nested content inherits full entitlements, and then the cmux/ghostty helpers are individually re-signed with the narrower cmux.embedded.entitlements. This is consistent across all three build paths.
  • @available(macOS 15.0, *) guards (BrowserPanel.swift): BrowserPasskeyAuthorizationCoordinator uses ASAuthorizationWebBrowserPublicKeyCredentialManager, which is macOS 15+ only. The coordinator class already carried the annotation; this PR correctly extends it to the owning lazy var and adds a guard #available early-return in configurePasskeyAuthorizationBridge, preventing a crash on Sonoma when the bridge setup is invoked.
  • Redundant bridge call removed (BrowserPopupWindowController.swift): createNestedPopup was calling openerPanel?.configurePasskeyAuthorizationBridge(on: configuration) directly before constructing the child BrowserPopupWindowController, whose init already calls the same method on openerPanel. This caused the passkey message handler to be registered twice on the same WKUserContentController, potentially leading to unexpected handler behaviour. Removing the call from createNestedPopup brings it in line with how first-level popups are set up.
  • Note: The auto-generated summary mentions setting forMainFrameOnly: true on the passkey bootstrap script to prevent CAPTCHA false positives, but that change is not present in this diff — it may be intended as a follow-up.

Confidence Score: 4/5

  • Safe to merge — all three fixes are targeted and correct; the only open question is whether forMainFrameOnly: false on the passkey script is intentional.
  • The codesign ordering fix is mechanically correct and consistent across all three build paths. The availability guards are properly scoped and the only access point to the restricted property is inside the guarded function. The redundant bridge call removal is clearly safe since the init already performs the same registration. No logic regressions are introduced. Score is 4 rather than 5 because the passkey bootstrap script is still injected into all iframes (forMainFrameOnly: false), which the auto-generated summary flagged as a CAPTCHA risk — it's unclear whether that was intended as part of this PR or a follow-up.
  • Sources/Panels/BrowserPanel.swift — specifically the forMainFrameOnly: false on the passkey bootstrap WKUserScript at line 2947.

Important Files Changed

Filename Overview
Sources/Panels/BrowserPanel.swift Adds @available(macOS 15.0, *) guard to the passkeyAuthorizationCoordinator lazy property and a guard #available early-return in configurePasskeyAuthorizationBridge; prevents crash on Sonoma. The passkey bootstrap script's forMainFrameOnly: false (injected into all iframes) is unchanged and may warrant follow-up.
Sources/Panels/BrowserPopupWindowController.swift Removes the duplicate openerPanel?.configurePasskeyAuthorizationBridge(on: configuration) call from createNestedPopup; the call already exists in BrowserPopupWindowController.init so the one in createNestedPopup was redundant and could have caused double-registration.
scripts/build-sign-upload.sh Corrects codesign order: app bundle is signed with --deep first (all nested content gets full entitlements), then embedded binaries are individually re-signed with narrower entitlements to prevent the public-key-credential entitlement from leaking into the CLI helpers.
.github/workflows/nightly.yml Applies the same codesign order fix as build-sign-upload.sh inside the per-app-path loop; change is consistent and correct.
.github/workflows/release.yml Applies the same codesign order fix as build-sign-upload.sh and nightly.yml; consistent and correct.

Sequence Diagram

sequenceDiagram
    participant BP as BrowserPanel
    participant BPWC as BrowserPopupWindowController
    participant WKConf as WKWebViewConfiguration
    participant UCC as WKUserContentController

    Note over BP,UCC: First-level popup creation (window.open())
    BP->>BPWC: init(configuration:, openerPanel: self, ...)
    BPWC->>BP: configurePasskeyAuthorizationBridge(on: configuration)
    BP->>BP: guard #available(macOS 15.0, *) [exits early on macOS 14]
    BP->>UCC: addUserScript(passkeyBootstrapScript)
    BP->>UCC: removeScriptMessageHandler(cmuxPasskeyAuthorization)
    BP->>UCC: addScriptMessageHandler(passkeyAuthorizationCoordinator)

    Note over BP,UCC: Nested popup creation (createNestedPopup) — AFTER fix
    BPWC->>BPWC: createNestedPopup(configuration:, windowFeatures:)
    Note right of BPWC: ❌ Removed duplicate configurePasskeyAuthorizationBridge call here
    BPWC->>BPWC: init(configuration:, openerPanel:, nestingDepth: n+1)
    BPWC->>BP: configurePasskeyAuthorizationBridge(on: configuration)
    BP->>UCC: addUserScript / re-register handler (once, not twice)
Loading

Comments Outside Diff (1)

  1. Sources/Panels/BrowserPanel.swift, line 2947 (link)

    P2 Passkey bootstrap script still injected into all iframes

    The passkey bootstrap script is added with forMainFrameOnly: false, meaning it is injected into every sub-frame — including third-party iframes (e.g. CAPTCHA, payment widgets). The auto-generated PR summary mentions "set forMainFrameOnly: true for passkey scripts to avoid tampering detection in third-party iframes," but this change is not present in the diff.

    If that CAPTCHA/iframe fix was intentional, consider flipping this to true here:

    The passkey message handler is wired to the WKUserContentController, which forwards messages from any frame regardless of this flag — so restricting injection to the main frame would still allow the main-page passkey flow while avoiding false-positive CAPTCHA triggers from injected scripts running inside third-party iframes.

Last reviewed commit: "Fix signing order, @..."

Comment on lines +1976 to 1977
@available(macOS 15.0, *)
private lazy var passkeyAuthorizationCoordinator = BrowserPasskeyAuthorizationCoordinator(panel: self)

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.

P2 @available on a lazy stored property in a non-restricted class

Placing @available(macOS 15.0, *) on a lazy var inside a class that itself has no availability restriction is valid in Swift 5.7+ but worth a quick note: unlike a function, the backing storage for a lazy var is part of the class's instance layout regardless of OS version. The guard in configurePasskeyAuthorizationBridge ensures the property is only ever accessed on macOS 15+, so the runtime behaviour is safe as written.

No change required — just noting this for future readers who might wonder why the pattern looks unusual. A brief comment above the property explaining the intent (e.g. "Only accessed after a #available(macOS 15.0, *) check in configurePasskeyAuthorizationBridge") would improve clarity.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@Jesssullivan

Copy link
Copy Markdown
Contributor Author

Hmm, it occurs to meASAuthorizationController requires a signed application identifier; this PR's approach (ASAuthorizationWebBrowserPublicKeyCredentialManager) will fail identically on ad-hoc signed builds, which is annoying.

This is separate from the restricted com.apple.developer.web-browser.public-key-credential entitlement — it's a base requirement of ASAuthorizationController itself. Any app using AuthenticationServices for public key credentials needs to be signed with my Apple Developer identity, which is fine but makes testing with adhoc builds not really an option.

This is somewhat meaningful for folks testing on their own fork and future feature portability-

  • Upstream signed builds (nightly, release): will work once this PR merges, assuming the signing identity has a team ID
  • Fork builds without Apple Developer signing: will not work — ASAuthorizationController rejects the request before any entitlement check
  • Local dev builds via reload.sh: depend on the developer's local signing identity

This is an Apple platform constraint, not something cmux can work around at the AuthenticationServices level. This'll make any future cross platform and local testing work more difficult; the site states macos "for now" and is surely far from being considered for porting to other platforms, but this'll definately further cement this descision. 🤔

The current approach adds com.apple.developer.web-browser.public-key-credential, which:

  1. Requires Apple approval via the Developer portal
  2. Caused launch failures on macOS 26 (cmux NIGHTLY fails to launch on macOS 26 (Tahoe beta) — web-browser entitlement causes RBSRequestErrorDomain Code=5 #1739) when the broader web-browser variant was used
  3. Is needed for WKWebView's native WebAuthn (platform authenticators / passkeys)

For fork culture / future linux support if that is even on the table, there's totally the option of direct CTAP2 communication with security keys over USB HID (via IOKit), bypassing AuthenticationServices entirely. This would work in completely unsigned builds — no team ID or entitlements needed. I'm hoping to woodshed on this a bit this coming week or two, I've worked direct CTAP2 support (granted, not in swift) into other projects.

-Jess

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.

1 participant