Skip to content

Comments

feat: macOS Passkeys Support#148

Merged
iamEvanYT merged 13 commits intomainfrom
evan/passkeys-on-mac
Jan 9, 2026
Merged

feat: macOS Passkeys Support#148
iamEvanYT merged 13 commits intomainfrom
evan/passkeys-on-mac

Conversation

@iamEvanYT
Copy link
Member

@iamEvanYT iamEvanYT commented Jul 11, 2025

Note

Enables macOS passkeys and tightens app security via entitlement updates.

  • Adds com.apple.developer.web-browser.public-key-credential to build/entitlements.mac.plist to support WebAuthn/passkeys
  • Enables hardened runtime with com.apple.security.hardened-runtime
  • Updates .gitignore to ignore .env, .yalc, yalc.lock, and test.sh

Written by Cursor Bugbot for commit 3a5058d. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added WebAuthn/passkeys support enabling credential creation and authentication via native platform authenticators.
  • Security

    • Hardened renderer process with enhanced security configuration settings.
    • Added macOS security entitlements and provisioning profile for improved runtime protection.
  • Chores

    • Updated build dependencies and configuration to support new security features.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 11, 2025

Walkthrough

Introduces WebAuthn/passkeys support by adding native electron-webauthn addon integration with IPC handlers for credential operations, preload-level API bridges for intercepting credentials API calls, FIDO2 data mapping utilities, build configuration updates, and Electron security hardening settings.

Changes

Cohort / File(s) Summary
WebAuthn IPC Implementation
src/main/ipc/webauthn/index.ts, src/main/ipc/webauthn/module.ts, src/main/ipc/webauthn/psl-check.ts
Registers three IPC handlers (webauthn:create, webauthn:get, webauthn:is-available) that validate options, derive origin information, and delegate to native electron-webauthn addon. Implements lazy-loading of addon with caching and public suffix detection utility.
WebAuthn Preload/Client Layer
src/preload/index.ts, src/preload/webauthn/webauthn-utils.ts, src/preload/webauthn/fido2-utils.ts
Patches navigator.credentials.create/get to route through IPC bridge, exposes electronCredentials object, and provides comprehensive data mapping between credential API formats and WebAuthn parameters/results. Includes FIDO2 utilities for binary/base64 conversions and error code translation.
Shared Types & Abstractions
src/shared/types/fido2-types.ts
Defines abstract Fido2ClientService and comprehensive interfaces for CreateCredentialParams/Result and AssertCredentialParams/Result with nested extensions (PRF, largeBlob, credProps), error codes, and user verification enums.
Security & Electron Configuration
build/entitlements.mac.plist, src/main/controllers/tabs-controller/tab.ts, electron-builder.ts
Adds macOS entitlements for hardened-runtime and web-browser public-key-credential support; sets provisioning profile; adds explicit Electron webPreferences (nodeIntegration: false, contextIsolation: true, nodeIntegrationInSubFrames: true).
Build & Dependencies
electron.vite.config.ts, package.json, .gitignore
Migrates externalizeDeps from plugin to build configuration; updates electron-vite to ^5.0.0; adds electron-webauthn, tldts, electron-winstaller, and objc-js dependencies; adds .yalc, yalc.lock, and test.sh to .gitignore.
IPC Registration
src/main/ipc/index.ts
Imports and registers webauthn IPC module as side-effect.

Sequence Diagram(s)

sequenceDiagram
    participant Web as Web App<br/>(Renderer)
    participant Preload as Preload<br/>(Bridge)
    participant Main as Main Process<br/>(IPC)
    participant Addon as Native Addon<br/>(electron-webauthn)

    Web->>Preload: navigator.credentials.create(options)
    Preload->>Preload: Intercept & validate options
    Preload->>Preload: Map to CreateCredentialParams
    Preload->>Main: IPC: webauthn:create(params)
    Main->>Main: Validate options & derive origin
    Main->>Main: Resolve window & extract frame info
    Main->>Addon: createCredential(origin, topFrameOrigin,<br/>isPublicSuffix, handle)
    Addon->>Addon: Process credential creation
    Addon-->>Main: CreateCredentialResult
    Main->>Main: Check for errors
    Main-->>Preload: Result or error
    Preload->>Preload: Map to PublicKeyCredential
    Preload->>Preload: Translate errors to DOMException
    Preload-->>Web: Promise<Credential | null>
Loading
sequenceDiagram
    participant Web as Web App<br/>(Renderer)
    participant Preload as Preload<br/>(Bridge)
    participant Main as Main Process<br/>(IPC)
    participant Addon as Native Addon<br/>(electron-webauthn)

    Web->>Preload: navigator.credentials.get(options)
    Preload->>Preload: Intercept & validate options
    Preload->>Preload: Map to AssertCredentialParams
    Preload->>Main: IPC: webauthn:get(params)
    Main->>Main: Block conditional mediation
    Main->>Main: Validate & derive origin
    Main->>Addon: getCredential(origin, topFrameOrigin,<br/>isPublicSuffix, handle)
    Addon->>Addon: Process assertion
    Addon-->>Main: AssertCredentialResult
    Main-->>Preload: Result or error
    Preload->>Preload: Map to PublicKeyCredential
    Preload->>Preload: Construct response objects
    Preload-->>Web: Promise<Credential | null>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Migrate to Electron Vite #29: Modifies electron.vite.config.ts build configuration and externalizeDeps handling, related to the migration of plugin-based configuration in this PR.

Suggested labels

enhancement

Poem

🐰 With whiskers twitched and nose so keen,
I've woven passkeys in between,
The preload bridge, the addon's call,
Native credentials, catching all!
From IPC halls to browser's door,
WebAuthn magic, forevermore! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: macOS Passkeys Support' clearly and specifically describes the main feature added in this PR—implementing passkeys functionality for macOS.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

@github-actions
Copy link
Contributor

github-actions bot commented Jul 11, 2025

Build artifacts for all platforms are ready! 🚀

Download the artifacts for:

One-line installer (Unstable):
bunx flow-debug-build --open 20861479992

(execution 20861479992 / attempt 1)

@iamEvanYT iamEvanYT force-pushed the evan/passkeys-on-mac branch from e137019 to d24fdc9 Compare January 7, 2026 00:49
@iamEvanYT iamEvanYT marked this pull request as ready for review January 9, 2026 17:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

🤖 Fix all issues with AI agents
In @package.json:
- Around line 112-116: Add explicit documentation for the two entries in the
trustedDependencies list: update the package.json by adding a top-level
"trustedDependenciesNotes" (or similar descriptive field) explaining that
"electron-winstaller" is a transitive dependency used for Windows installer
generation via electron-builder and "objc-js" is a transitive macOS native
dependency required by electron-webauthn; mention that their versions are locked
in bun.lock for reproducibility and security. Ensure the notes reference the
trustedDependencies array and the exact dependency names ("electron-winstaller",
"objc-js") so reviewers can find the rationale without changing the dependency
list itself.
- Line 78: Replace the deprecated externalizeDepsPlugin usage in
electron.vite.config.ts: remove any imports and plugin entries referencing
externalizeDepsPlugin and instead add build.externalizeDeps configuration to
both the main and preload build configs; for the main build set
build.externalizeDeps.exclude to ["electron-context-menu","hono"] and for the
preload build set build.externalizeDeps.exclude to
["electron-chrome-extensions"], keeping all other build settings intact (note
electron-vite v5 enables externalization by default so only these override
arrays are required).

In @src/main/controllers/tabs-controller/tab.ts:
- Around line 98-104: The comment misstates behavior: explain that
nodeIntegration: false disables Node in the main renderer,
nodeIntegrationInSubFrames: true only enables Node in subframes/iframes (not
preload scripts), and contextIsolation: true isolates renderer globals so
preload scripts (enabled via the webPreferences.preload option) are used to
expose safe APIs; confirm whether Node in iframes is intentional and either set
nodeIntegrationInSubFrames to false or keep it and document the reason, and if
you intend to use a preload script, enable the preload option (uncomment/set
webPreferences.preload) and update the comment to accurately describe each
setting (nodeIntegration, nodeIntegrationInSubFrames, contextIsolation,
preload).

In @src/main/ipc/webauthn/index.ts:
- Around line 143-155: The getCredential branch currently awaits
webauthn.getCredential and either returns result.error or result.data but does
not wrap thrown exceptions; update the handler to mirror the create path by
surrounding the await call in a try/catch, catch any thrown error from
webauthn.getCredential(publicKeyOptions, { currentOrigin, topFrameOrigin,
isPublicSuffix, nativeWindowHandle: win.getNativeWindowHandle() }), map/shape
the caught error into the same AssertCredentialErrorCodes | null structure used
by the create handler, and return that shaped error instead of letting the
exception propagate.
- Around line 94-156: The get handler for "webauthn:get" can call
webauthn.getCredential with publicKeyOptions undefined; add an early return when
options.publicKey is missing (same guard as in webauthn:create) to avoid passing
undefined to webauthn.getCredential. Specifically, inside the ipcMain.handle for
"webauthn:get", check if options.publicKey (publicKeyOptions) is falsy and
return null (or the same sentinel used by create) before proceeding to call
webauthn.getCredential, so the addon always receives a defined publicKeyOptions.
- Around line 32-92: The IPC handler for "webauthn:create" currently calls
webauthn.createCredential(...) without a try/catch, so thrown exceptions can
escape the handler; wrap the call to webauthn.createCredential(publicKeyOptions,
{...}) in a try/catch and convert any thrown error into one of the declared
return values (a CreateCredentialErrorCodes value or null) before returning to
the caller, while keeping the existing handling for result.success === false and
returning result.data on success; ensure the catch covers both synchronous
throws and rejected promises and returns a sensible mapped error code (or null)
consistently.
- Around line 11-30: The TypeScript DOM types (BufferSource,
CredentialCreationOptions, CredentialRequestOptions) used by
bufferSourceToBuffer and related WebAuthn code aren’t available in the
main-process tsconfig, so the build will fail; fix by either adding the DOM lib
to the node compiler options (add "lib": ["DOM","ES2022"] to compilerOptions in
tsconfig.node.json) or by creating/using local type declarations (e.g., a shared
types file that exports BufferSource and the WebAuthn interfaces and import
those into src/main/ipc/webauthn/index.ts) and update imports; ensure whichever
approach you pick is applied consistently for all WebAuthn-related symbols
(BufferSource, CredentialCreationOptions, CredentialRequestOptions) referenced
in the file.

In @src/main/ipc/webauthn/module.ts:
- Around line 1-15: The getWebauthnAddon function can race on concurrent calls
and propagate import errors; introduce a module-level Promise cache (e.g.,
webauthnModulePromise: Promise<WebauthnModule | null> | null) alongside the
existing webauthnModule, populate that promise on the first import attempt, and
await it in subsequent callers to prevent duplicate imports; wrap the dynamic
import("electron-webauthn") in try/catch inside that promise, set webauthnModule
on success and return it, and on failure clear the promise cache and return null
(do not throw) so IPC callers receive null instead of an uncaught error.

In @src/main/ipc/webauthn/psl-check.ts:
- Around line 1-9: The isPublicSuffix function currently treats any input with
r.domain === null as a public suffix, which misclassifies invalid hostnames like
"localhost"; update the check to use the parse result's publicSuffix property:
call parseTld(d, { allowPrivateDomains: false }) as before but return true only
when r.publicSuffix !== null && r.publicSuffix === d (i.e., the parsed
publicSuffix exactly matches the input), leaving the function name
isPublicSuffix and the use of parseTld unchanged.

In @src/preload/index.ts:
- Around line 248-253: The implementation of
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable calls
patchedCredentials.isAvailable() twice causing extra IPC; modify the function to
call patchedCredentials.isAvailable() once, store the boolean result in a local
variable, and then return that stored result (falling back to calling
oldIsUserVerifyingPlatformAuthenticatorAvailable() only if patched result is
falsy) so you avoid the duplicate IPC round-trip; locate the method by name
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable and the helper
patchedCredentials.isAvailable()/oldIsUserVerifyingPlatformAuthenticatorAvailable
to apply this change.

In @src/preload/webauthn/fido2-utils.ts:
- Around line 97-105: bufferToString duplicates the base64url conversion logic;
change it to call the existing fromB64toUrlB64 helper instead of performing
manual replace operations. Specifically, after obtaining b64 from
fromBufferToB64, pass it into Fido2Utils.fromB64toUrlB64 (and handle the
null/error case consistently with existing behavior) so bufferToString returns
the normalized base64url string using the shared implementation used by
fromB64toUrlB64.
- Around line 97-105: bufferToString currently casts uint8Array.buffer to
ArrayBuffer which breaks if the backing store is a SharedArrayBuffer; update
bufferToString (and use Fido2Utils.bufferSourceToUint8Array) to detect if
uint8Array.buffer instanceof SharedArrayBuffer and, in that case, create a new
ArrayBuffer by slicing/copying the Uint8Array (e.g., using uint8Array.slice()
and taking its .buffer) before passing it to Fido2Utils.fromBufferToB64,
otherwise continue using uint8Array.buffer directly; ensure the resulting
variable is an ArrayBuffer for fromBufferToB64.

In @src/preload/webauthn/helpers.ts:
- Around line 42-46: rpId.includes(":") wrongly blocks IPv6 and isLocalhostLike
uses isIpLiteral(host) which makes all IPs treated as localhost; instead, ensure
IPv6 rpIds are handled by checking isIpLiteral(host) (or a dedicated
isIPv6/isLoopback helper) before rejecting on colon, and restrict
isLocalhostLike to true only for literal loopback addresses or the hostname
"localhost" (e.g., replace isIpLiteral(host) with an isLoopbackAddress(host)
check). Update the secureEnough expression to use that isLoopbackAddress(host)
with allowInsecureLocalhost && scheme === "http:" so only loopback allows http,
and remove or reorder the rpId.includes(":") guard so IPv6 rpIds pass into the
IP-origin branch; apply the same changes to the other similar block referenced
(lines ~56-64).
- Around line 76-91: Skip PSL rejection for localhost and IP literals by
checking rpId before calling isPublicSuffix: if rpId === "localhost" or it's an
IP literal (IPv4 or IPv6) then treat as safe and do not return the public-suffix
error. Update the branch that calls isPublicSuffix(rpId) to first detect IPs
(use an existing utility like net.isIP or add a small IP check helper) and skip
the check for those cases; also adjust the fallback branch so it does not reject
localhost or IP literals when doing the single-label check; keep the same return
shapes (ok:false, rpId, reason) for other failures and ensure references to
isPublicSuffix and rpId are used to locate the changes.

In @src/shared/types/fido2-types.ts:
- Around line 220-229: Update the JSDoc above the CreateCredentialErrorCodes
type to accurately describe it as error codes for creating a credential (not
asserting); replace the incorrect "Error codes for asserting a credential"
comment with something like "Error codes for creating a credential" and ensure
the AssertCredentialErrorCodes comment remains describing asserting a credential
to avoid the copy-paste mismatch between CreateCredentialErrorCodes and
AssertCredentialErrorCodes.
- Around line 257-262: The FallbackRequestedError class should set the error
name for clearer identification; update the constructor of
FallbackRequestedError to assign this.name = "FallbackRequestedError" (and
optionally call Error.captureStackTrace(this, FallbackRequestedError) for proper
stack traces in V8) while preserving the existing readonly fallbackRequested
flag and super("FallbackRequested") call.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80c5ff4 and a5f5a6a.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • .gitignore
  • build/entitlements.mac.plist
  • build/profile.provisionprofile
  • electron-builder.ts
  • package.json
  • src/main/controllers/tabs-controller/tab.ts
  • src/main/ipc/index.ts
  • src/main/ipc/webauthn/index.ts
  • src/main/ipc/webauthn/module.ts
  • src/main/ipc/webauthn/psl-check.ts
  • src/preload/index.ts
  • src/preload/webauthn/fido2-utils.ts
  • src/preload/webauthn/helpers.ts
  • src/preload/webauthn/webauthn-utils.ts
  • src/shared/types/fido2-types.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

**/*.{js,jsx,ts,tsx}: Use bun <file> instead of node <file> or ts-node <file>
Bun automatically loads .env, so don't use dotenv.
Use Bun.serve() for HTTP servers and WebSockets instead of express.
Use bun:sqlite for SQLite instead of better-sqlite3.
Use Bun.redis for Redis instead of ioredis.
Use Bun.sql for Postgres instead of pg or postgres.js.
Use built-in WebSocket instead of ws.
Prefer Bun.file over node:fs's readFile/writeFile.
Use Bun.$ for shell commands instead of execa.

Files:

  • src/main/ipc/webauthn/module.ts
  • src/main/ipc/webauthn/index.ts
  • src/main/ipc/index.ts
  • src/preload/webauthn/helpers.ts
  • src/preload/index.ts
  • src/main/controllers/tabs-controller/tab.ts
  • src/shared/types/fido2-types.ts
  • src/main/ipc/webauthn/psl-check.ts
  • src/preload/webauthn/webauthn-utils.ts
  • src/preload/webauthn/fido2-utils.ts
  • electron-builder.ts
**/*.{html,ts,css}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

Use bun build <file.html|file.ts|file.css> instead of webpack or esbuild

Files:

  • src/main/ipc/webauthn/module.ts
  • src/main/ipc/webauthn/index.ts
  • src/main/ipc/index.ts
  • src/preload/webauthn/helpers.ts
  • src/preload/index.ts
  • src/main/controllers/tabs-controller/tab.ts
  • src/shared/types/fido2-types.ts
  • src/main/ipc/webauthn/psl-check.ts
  • src/preload/webauthn/webauthn-utils.ts
  • src/preload/webauthn/fido2-utils.ts
  • electron-builder.ts
package.json

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

package.json: Use bun install instead of npm install or yarn install or pnpm install
Use bun run <script> instead of npm run <script> or yarn run <script> or pnpm run <script>

Files:

  • package.json
🧠 Learnings (1)
📚 Learning: 2025-07-24T08:49:07.217Z
Learnt from: CR
Repo: MultiboxLabs/flow-browser PR: 0
File: .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc:0-0
Timestamp: 2025-07-24T08:49:07.217Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use `bun:sqlite` for SQLite instead of `better-sqlite3`.

Applied to files:

  • package.json
🧬 Code graph analysis (4)
src/main/ipc/webauthn/index.ts (2)
src/shared/types/fido2-types.ts (4)
  • CreateCredentialResult (137-160)
  • CreateCredentialErrorCodes (223-229)
  • AssertCredentialResult (200-218)
  • AssertCredentialErrorCodes (234-239)
src/main/ipc/webauthn/module.ts (1)
  • getWebauthnAddon (5-15)
src/preload/webauthn/helpers.ts (1)
src/main/ipc/webauthn/psl-check.ts (1)
  • isPublicSuffix (5-9)
src/preload/index.ts (2)
src/shared/types/fido2-types.ts (4)
  • CreateCredentialResult (137-160)
  • AssertCredentialResult (200-218)
  • AssertCredentialErrorCodes (234-239)
  • CreateCredentialErrorCodes (223-229)
src/preload/webauthn/webauthn-utils.ts (1)
  • WebauthnUtils (9-270)
src/preload/webauthn/fido2-utils.ts (1)
src/shared/types/fido2-types.ts (2)
  • CreateCredentialResult (137-160)
  • AssertCredentialResult (200-218)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (14)
.gitignore (1)

7-10: LGTM!

The added gitignore entries are appropriate. The yalc-related entries (.yalc, yalc.lock) handle local package development artifacts, and test.sh excludes development test scripts from version control.

build/entitlements.mac.plist (2)

7-8: LGTM!

The hardened runtime entitlement is required for macOS notarization and distribution.


35-36: LGTM!

The web-browser public-key-credential entitlement correctly enables WebAuthn/Passkeys support on macOS, allowing the browser to access platform authenticators.

electron-builder.ts (1)

95-95: Provisioning profile file is already present in the repository.

The provisioning profile at build/profile.provisionprofile exists and is committed to the repository. The file is a valid macOS provisioning profile with the correct entitlements, including the web-browser.public-key-credential capability. No action is needed.

src/main/ipc/index.ts (1)

26-28: Side-effect IPC registration import looks fine.

Assuming @/ipc/webauthn only registers handlers once (module cache) and returns NotSupportedError off macOS, this is an appropriate place to wire it in.

package.json (1)

43-43: No action needed—electron-webauthn is already properly guarded.

The package supports Windows and Linux with prebuilt binaries. The codebase already has a runtime check (process.platform !== "darwin") that gracefully returns null on non-macOS platforms, and all callers handle the null case by returning error codes or false. Cross-platform installs and builds will succeed without issues.

Likely an incorrect or invalid review comment.

src/preload/index.ts (2)

150-163: Cached availability check may become stale.

The shouldUseMacOSWebauthnAddon function caches the availability result indefinitely. If the underlying WebAuthn service availability changes during the page lifecycle (e.g., system settings change, hardware events), the cached value won't reflect the new state.

Is this intentional? If platform authenticator availability can change during runtime, consider either removing the cache or implementing cache invalidation. If it's guaranteed to be stable for the page lifetime, this is acceptable.


101-104: Good security practice: temporary exposure with cleanup.

The implementation correctly exposes electronCredentials temporarily via contextBridge.exposeInMainWorld, uses it immediately in executeInMainWorld, and then deletes it to prevent external access. This is a solid pattern for bridging preload and main world contexts securely.

Also applies to: 144-144, 266-271

src/shared/types/fido2-types.ts (1)

1-47: Well-documented abstract service and types.

The Fido2ClientService abstract class with JSDoc references to the W3C WebAuthn specification provides a clean abstraction layer. The generic ParentWindowReference parameter allows flexibility for different window reference implementations.

src/preload/webauthn/fido2-utils.ts (1)

159-175: Good: Proper base64url padding handling.

The fromUrlB64ToB64 method correctly handles all padding cases including the invalid case (length % 4 == 1), which throws an appropriate error.

src/preload/webauthn/webauthn-utils.ts (4)

93-109: Good: AuthenticatorAttestationResponse implementation matches spec.

The implementation correctly provides getter methods (getAuthenticatorData, getPublicKey, getPublicKeyAlgorithm, getTransports) that match the WebAuthn specification. Creating new buffers on each call is consistent with browser behavior.


222-269: Consistent credential assertion result mapping.

The mapCredentialAssertResult implementation follows the same patterns as mapCredentialRegistrationResult, properly handling optional extensions and setting prototype chains for instanceof compatibility.


72-78: Good defensive handling for spec-deviant sites.

The code correctly handles the KeycloakJS edge case where alg may be passed as a string. Converting to Number() and filtering NaN values is a pragmatic approach. The comment documenting this deviation is helpful.


10-15: Remove unused mapping functions or clarify their intended use.

mapCredentialCreationOptions and mapCredentialRequestOptions are defined but never used anywhere in the codebase. The preload script passes options directly to ipcRenderer.invoke without mapping (lines 112, 124-126), and the main process IPC handlers in src/main/ipc/webauthn/index.ts receive and use these options as-is without transformation (lines 36-84, 98-155). These functions should either be removed if they are dead code, or their intended use case should be documented.

Likely an incorrect or invalid review comment.

@iamEvanYT
Copy link
Member Author

@greptile

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

Implements macOS passkeys (WebAuthn) support using the electron-webauthn library, enabling biometric authentication for web applications.

Key Changes

  • Added com.apple.developer.web-browser.public-key-credential entitlement to enable passkeys on macOS
  • Implemented WebAuthn IPC handlers with proper security validation (origin checks, frame validation, public suffix checks)
  • Patched navigator.credentials API in preload to intercept and route WebAuthn calls through electron-webauthn when available
  • Hardened web security by explicitly setting nodeIntegration: false, nodeIntegrationInSubFrames: true, and contextIsolation: true in tab WebContents
  • Added fallback to Chromium's native WebAuthn implementation when electron-webauthn is unavailable

Issues Found

  • src/preload/webauthn/helpers.ts is never imported or used in the codebase
  • electron-vite upgraded from v3 to v5 (major version bump) - build configuration compatibility should be verified

Confidence Score: 4/5

  • Safe to merge with minor cleanup recommended
  • Implementation is solid with proper security validation, frame origin checks, and public suffix list checking. The unused helpers.ts file and electron-vite major version upgrade are minor issues that should be addressed but don't block merging.
  • Pay attention to src/preload/webauthn/helpers.ts (unused file) and verify electron-vite v5 compatibility

Important Files Changed

File Analysis

Filename Score Overview
src/preload/webauthn/helpers.ts 3/5 New file containing isRpIdAllowed helper function, but it's never imported or used anywhere in the codebase
build/entitlements.mac.plist 5/5 Added passkeys entitlement com.apple.developer.web-browser.public-key-credential and hardened runtime settings
src/main/ipc/webauthn/index.ts 5/5 Implements IPC handlers for WebAuthn create/get operations with proper origin and frame validation
src/preload/index.ts 4/5 Patches navigator.credentials API to intercept WebAuthn calls and route them through electron-webauthn when available
src/main/controllers/tabs-controller/tab.ts 5/5 Hardened web security by explicitly setting nodeIntegration: false, nodeIntegrationInSubFrames: true, and contextIsolation: true
package.json 4/5 Added electron-webauthn and tldts dependencies, upgraded electron-vite to v5.0.0

Sequence Diagram

sequenceDiagram
    participant Web as Web Page
    participant Preload as Preload Script
    participant Main as Main Process
    participant Addon as electron-webauthn
    participant macOS as macOS WebAuthn

    Note over Web,macOS: WebAuthn Registration Flow
    
    Web->>Preload: navigator.credentials.create(options)
    Preload->>Preload: Check if electron-webauthn available
    alt electron-webauthn available
        Preload->>Main: IPC: webauthn:create
        Main->>Main: Validate senderFrame & topFrame
        Main->>Main: Extract origin & check security
        Main->>Addon: createCredential(publicKeyOptions)
        Addon->>macOS: Request passkey creation
        macOS-->>Addon: Credential data
        Addon-->>Main: CreateCredentialResult
        Main-->>Preload: Serialized result
        Preload->>Preload: Map to PublicKeyCredential
        Preload-->>Web: PublicKeyCredential
    else electron-webauthn not available
        Preload->>Preload: Fall back to native
        Preload-->>Web: Use Chromium's WebAuthn
    end

    Note over Web,macOS: WebAuthn Authentication Flow
    
    Web->>Preload: navigator.credentials.get(options)
    Preload->>Preload: Check if electron-webauthn available
    alt electron-webauthn available
        Preload->>Main: IPC: webauthn:get
        Main->>Main: Validate senderFrame & topFrame
        Main->>Main: Extract origin & check security
        Main->>Addon: getCredential(publicKeyOptions)
        Addon->>macOS: Request passkey assertion
        macOS-->>Addon: Assertion data
        Addon-->>Main: AssertCredentialResult
        Main-->>Preload: Serialized result
        Preload->>Preload: Map to PublicKeyCredential
        Preload-->>Web: PublicKeyCredential
    else electron-webauthn not available
        Preload->>Preload: Fall back to native
        Preload-->>Web: Use Chromium's WebAuthn
    end
Loading

greptile-apps[bot]

This comment was marked as duplicate.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/controllers/tabs-controller/tab.ts (1)

82-108: Critical: nodeIntegrationInSubFrames: true enables Node APIs in subframes—a documented security risk.

This flag grants Node/renderer APIs to any subframe, increasing XSS/compromise impact. CVE-2022-29247 demonstrated renderer code can leverage this to access ipcRenderer in new renderer processes, leading to privilege escalation if IPC handlers lack proper senderFrame validation. Electron's security guidance explicitly recommends defaulting this to false and enabling only for fully trusted, local content.

The existing comment is misleading—nodeIntegrationInSubFrames does not "enable preload scripts in iframes"; it specifically enables Node integration in subframes. Preload scripts work independently and are unaffected by this flag. Since the preload is currently commented out (line 107), there is no evident need for Node in subframes.

Set nodeIntegrationInSubFrames: false unless there is an explicit, documented requirement for Node APIs in trusted subframes. If that requirement exists, document it clearly and validate all IPC handlers with senderFrame checks to prevent privilege escalation.

Proposed fix
     nodeIntegration: false,
-    nodeIntegrationInSubFrames: true,
+    nodeIntegrationInSubFrames: false,
     contextIsolation: true
🤖 Fix all issues with AI agents
In @src/main/ipc/webauthn/index.ts:
- Line 47: The handler reads options.publicKey into publicKeyOptions and may
pass it undefined to the native addon; add a guard that verifies
options.publicKey is defined before calling the addon (use the same pattern used
in the webauthn:get handler) and return or throw a clear error/log when it's
missing, and when present pass the validated publicKeyOptions to the addon;
update both the create/register flow (the variable publicKeyOptions used for the
addon call around the current addon invocation) and the webauthn:get handler
branch to perform the same existence check and early exit so the addon never
receives undefined.

In @src/preload/index.ts:
- Around line 113-123: Update the type annotations for the IPC responses to
match the actual values returned: change the type of the serialized variable in
the create method (the result of ipcRenderer.invoke("webauthn:create", options))
from CreateCredentialResult | null to CreateCredentialResult |
CreateCredentialErrorCodes | null, and do the same for the get() handler’s
serialized variable (ipcRenderer.invoke("webauthn:get", ...)); keep the existing
runtime checks unchanged so error-code strings still pass through to
tinyPasskeysScript.
- Around line 256-263: The override of
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable redundantly
calls patchedCredentials.isAvailable() twice; change it to call
patchedCredentials.isAvailable() once, store the boolean result in a local
variable (e.g., available) and use that value for both the conditional check and
the return, preserving the fallback to
oldIsUserVerifyingPlatformAuthenticatorAvailable() when unavailable.

In @src/shared/types/fido2-types.ts:
- Around line 49-132: CreateCredentialParams marks timeout as optional but
AssertCredentialParams currently requires timeout, which is inconsistent with
the WebAuthn spec; change the AssertCredentialParams interface's timeout
property to be optional (timeout?: number) to match CreateCredentialParams (or
if intentional, add a comment on AssertCredentialParams explaining the
defaulting behavior). Update any callers of AssertCredentialParams to handle an
undefined timeout or to supply an explicit default where necessary.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5f5a6a and 6b0488d.

📒 Files selected for processing (6)
  • electron.vite.config.ts
  • src/main/controllers/tabs-controller/tab.ts
  • src/main/ipc/webauthn/index.ts
  • src/main/ipc/webauthn/module.ts
  • src/preload/index.ts
  • src/shared/types/fido2-types.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

**/*.{js,jsx,ts,tsx}: Use bun <file> instead of node <file> or ts-node <file>
Bun automatically loads .env, so don't use dotenv.
Use Bun.serve() for HTTP servers and WebSockets instead of express.
Use bun:sqlite for SQLite instead of better-sqlite3.
Use Bun.redis for Redis instead of ioredis.
Use Bun.sql for Postgres instead of pg or postgres.js.
Use built-in WebSocket instead of ws.
Prefer Bun.file over node:fs's readFile/writeFile.
Use Bun.$ for shell commands instead of execa.

Files:

  • src/main/controllers/tabs-controller/tab.ts
  • src/main/ipc/webauthn/module.ts
  • src/main/ipc/webauthn/index.ts
  • src/preload/index.ts
  • src/shared/types/fido2-types.ts
  • electron.vite.config.ts
**/*.{html,ts,css}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

Use bun build <file.html|file.ts|file.css> instead of webpack or esbuild

Files:

  • src/main/controllers/tabs-controller/tab.ts
  • src/main/ipc/webauthn/module.ts
  • src/main/ipc/webauthn/index.ts
  • src/preload/index.ts
  • src/shared/types/fido2-types.ts
  • electron.vite.config.ts
🧠 Learnings (1)
📚 Learning: 2025-04-18T23:20:02.623Z
Learnt from: iamEvanYT
Repo: MultiboxLabs/flow-browser PR: 24
File: electron/browser/tabs/tab.ts:309-310
Timestamp: 2025-04-18T23:20:02.623Z
Learning: In the Flow Browser codebase, `URL.parse` is preferred over `new URL()` for URL parsing because it works without errors in the Electron runtime environment, even though TypeScript's type definitions require `ts-ignore` comments.

Applied to files:

  • src/main/ipc/webauthn/index.ts
🧬 Code graph analysis (1)
src/main/ipc/webauthn/index.ts (2)
src/shared/types/fido2-types.ts (4)
  • CreateCredentialResult (137-160)
  • CreateCredentialErrorCodes (223-229)
  • AssertCredentialResult (200-218)
  • AssertCredentialErrorCodes (234-239)
src/main/ipc/webauthn/module.ts (1)
  • getWebauthnAddon (6-38)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: build (macos-latest)
  • GitHub Check: build (ubuntu-24.04-arm)
  • GitHub Check: build (windows-latest)
  • GitHub Check: build (macos-15-intel)
  • GitHub Check: build (ubuntu-latest)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: format
  • GitHub Check: main-checks
🔇 Additional comments (8)
src/main/ipc/webauthn/module.ts (1)

1-38: Well-structured lazy loading pattern with proper caching.

The implementation correctly handles:

  • Platform-specific loading (macOS only)
  • Module caching to avoid repeated imports
  • In-flight deduplication to prevent concurrent import races
  • Retry-on-failure by clearing the promise cache

One minor observation: the finally block clears webauthnModulePromise even on success. This is fine because webauthnModule is set before the finally runs, so subsequent calls hit the early return on line 13. The pattern is sound.

src/main/ipc/webauthn/index.ts (3)

91-153: Consistent structure with webauthn:create handler.

The webauthn:get handler follows the same pattern as webauthn:create, which is good for maintainability. The conditional mediation check (lines 109-111) correctly returns NotSupportedError as documented.

Same concern applies: publicKeyOptions on line 106 could be undefined.


155-161: Simple availability check.

The webauthn:is-available handler is straightforward. Consider simplifying to:

return !!await getWebauthnAddon();

This is purely stylistic; the current implementation is also correct.


11-30: Remove unused export bufferSourceToBuffer or document its intended use

This utility function is exported but not referenced anywhere in the codebase. If it's part of a public API for external consumers, add a comment explaining this. Otherwise, remove the export to reduce dead code.

src/preload/index.ts (1)

101-286: Well-structured passkeys patching implementation.

The overall approach is sound:

  • Feature detection before patching
  • Proper fallback to native implementations
  • Clean error-to-DOMException mapping that mirrors Chromium behavior
  • Cleanup of exposed bridge after patching

The use of contextBridge.executeInMainWorld to inject the patching script is the correct approach for this type of runtime modification.

src/shared/types/fido2-types.ts (2)

1-262: Comprehensive and well-documented FIDO2/WebAuthn types.

The type definitions are thorough:

  • Clear JSDoc documentation with spec references
  • Base64 encoding requirements are consistently noted
  • Extension types (PRF, largeBlob, credProps) are properly modeled
  • Error code unions align with WebAuthn error handling

16-47: Abstract class appears unused in this PR.

Fido2ClientService is an abstract class that defines the WebAuthn client interface, but the IPC handlers in this PR don't extend it—they directly use the electron-webauthn addon. This class may be intended for future extensibility or external implementations.

electron.vite.config.ts (1)

34-50: Migration to build.externalizeDeps is correct and aligns with electron-vite v5 best practices.

The configuration properly excludes electron-context-menu and hono for the main process, and electron-chrome-extensions for the preload, forcing these packages to be bundled rather than externalized. This approach is the recommended pattern in electron-vite v5, where the deprecated externalizeDepsPlugin has been replaced with the build configuration option.

return null;
}

const publicKeyOptions = options.publicKey;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential undefined publicKeyOptions passed to addon.

options.publicKey may be undefined if the caller provides CredentialCreationOptions without a publicKey property. The addon call on line 76 would then receive undefined, which may cause unexpected behavior depending on the addon's implementation.

Proposed guard for publicKeyOptions
     const publicKeyOptions = options.publicKey;
+    if (!publicKeyOptions) {
+      return null;
+    }
 
     const senderFrame = event.senderFrame;

Apply the same pattern in the webauthn:get handler around line 106.

Also applies to: 76-81

🤖 Prompt for AI Agents
In @src/main/ipc/webauthn/index.ts at line 47, The handler reads
options.publicKey into publicKeyOptions and may pass it undefined to the native
addon; add a guard that verifies options.publicKey is defined before calling the
addon (use the same pattern used in the webauthn:get handler) and return or
throw a clear error/log when it's missing, and when present pass the validated
publicKeyOptions to the addon; update both the create/register flow (the
variable publicKeyOptions used for the addon call around the current addon
invocation) and the webauthn:get handler branch to perform the same existence
check and early exit so the addon never receives undefined.

Comment on lines +113 to +123
create: async (options) => {
const serialized: CreateCredentialResult | null = await ipcRenderer.invoke("webauthn:create", options);

if (!serialized) return null;
if (typeof serialized === "string") {
return serialized;
}

const publicKeyCredential = WebauthnUtils.mapCredentialRegistrationResult(serialized);
return publicKeyCredential;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Type annotation mismatch with actual IPC response.

The type annotation on line 114 shows CreateCredentialResult | null, but the IPC handler returns CreateCredentialResult | CreateCredentialErrorCodes | null. The error code strings pass through and are handled in tinyPasskeysScript, so the logic works, but the types are misleading.

Suggested type correction
       create: async (options) => {
-        const serialized: CreateCredentialResult | null = await ipcRenderer.invoke("webauthn:create", options);
+        const serialized: CreateCredentialResult | CreateCredentialErrorCodes | null = await ipcRenderer.invoke("webauthn:create", options);

The same applies to get() on line 126.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create: async (options) => {
const serialized: CreateCredentialResult | null = await ipcRenderer.invoke("webauthn:create", options);
if (!serialized) return null;
if (typeof serialized === "string") {
return serialized;
}
const publicKeyCredential = WebauthnUtils.mapCredentialRegistrationResult(serialized);
return publicKeyCredential;
},
create: async (options) => {
const serialized: CreateCredentialResult | CreateCredentialErrorCodes | null = await ipcRenderer.invoke("webauthn:create", options);
if (!serialized) return null;
if (typeof serialized === "string") {
return serialized;
}
const publicKeyCredential = WebauthnUtils.mapCredentialRegistrationResult(serialized);
return publicKeyCredential;
},
🤖 Prompt for AI Agents
In @src/preload/index.ts around lines 113 - 123, Update the type annotations for
the IPC responses to match the actual values returned: change the type of the
serialized variable in the create method (the result of
ipcRenderer.invoke("webauthn:create", options)) from CreateCredentialResult |
null to CreateCredentialResult | CreateCredentialErrorCodes | null, and do the
same for the get() handler’s serialized variable
(ipcRenderer.invoke("webauthn:get", ...)); keep the existing runtime checks
unchanged so error-code strings still pass through to tinyPasskeysScript.

Comment on lines +177 to +210
credentials.create = async (options) => {
if (options && (await shouldUseMacOSWebauthnAddon())) {
if (options.publicKey) {
const result = await patchedCredentials.create(options);

// Cannot throw errors in patchedCredentials, so we need to handle the errors here.
const errorCode = result as unknown as CreateCredentialErrorCodes;
if (errorCode === "NotAllowedError") {
// Mirror Chromium's error message.
throw new DOMException(
"The operation either timed out or was not allowed. See: https://www.w3.org/TR/webauthn-2/#sctn-privacy-considerations-client.",
"NotAllowedError"
);
} else if (errorCode === "SecurityError") {
throw new DOMException("The calling domain is not a valid domain.", "SecurityError");
} else if (errorCode === "TypeError") {
throw new DOMException("Failed to parse arguments.", "TypeError");
} else if (errorCode === "AbortError") {
throw new DOMException("The operation was aborted.", "AbortError");
} else if (errorCode === "NotSupportedError") {
throw new DOMException("The user agent does not support this operation.", "NotSupportedError");
} else if (errorCode === "InvalidStateError") {
throw new DOMException(
"The user attempted to register an authenticator that contains one of the credentials already registered with the relying party.",
"InvalidStateError"
);
}

return result;
}
}

return await oldCredentialsCreate(options);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Error handling works but has a subtle flow issue.

The error-to-DOMException mapping (lines 183-203) correctly translates error codes. However, after throwing on an error code, the code still has return result; on line 205. This is unreachable after a throw, but if an unknown error code is returned (not in the union), it would return the string directly to the caller instead of a PublicKeyCredential.

Since the error codes are defined in a union type, this shouldn't happen in practice, but adding a fallback throw for unexpected strings would be more defensive.

Defensive handling for unexpected error codes
                 } else if (errorCode === "InvalidStateError") {
                   throw new DOMException(
                     "The user attempted to register an authenticator that contains one of the credentials already registered with the relying party.",
                     "InvalidStateError"
                   );
+                } else if (typeof result === "string") {
+                  // Unexpected error code - throw generic error
+                  throw new DOMException("An unknown error occurred.", "UnknownError");
                 }

                 return result;

Apply similar logic in the get() handler around line 241.

Comment on lines +256 to +263
const oldIsUserVerifyingPlatformAuthenticatorAvailable =
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable.bind(PublicKeyCredential);
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = async () => {
if (await patchedCredentials.isAvailable()) {
return await patchedCredentials.isAvailable();
}
return await oldIsUserVerifyingPlatformAuthenticatorAvailable();
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Redundant isAvailable() call in isUserVerifyingPlatformAuthenticatorAvailable.

Lines 259-260 call patchedCredentials.isAvailable() twice in succession. The first call checks if the addon is available, and then it returns the result of calling it again. This works but is inefficient.

Simplify to single call
          PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable = async () => {
-           if (await patchedCredentials.isAvailable()) {
-             return await patchedCredentials.isAvailable();
-           }
+           const addonAvailable = await patchedCredentials.isAvailable();
+           if (addonAvailable) {
+             return true; // macOS platform authenticator is available via addon
+           }
            return await oldIsUserVerifyingPlatformAuthenticatorAvailable();
          };
🤖 Prompt for AI Agents
In @src/preload/index.ts around lines 256 - 263, The override of
PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable redundantly
calls patchedCredentials.isAvailable() twice; change it to call
patchedCredentials.isAvailable() once, store the boolean result in a local
variable (e.g., available) and use that value for both the conditional check and
the return, preserving the fallback to
oldIsUserVerifyingPlatformAuthenticatorAvailable() when unavailable.

Comment on lines +49 to +132
/**
* Parameters for creating a new credential.
*/
export interface CreateCredentialParams {
/** The Relaying Parties origin, see: https://html.spec.whatwg.org/multipage/browsers.html#concept-origin */
origin: string;
/**
* A value which is true if and only if the caller’s environment settings object is same-origin with its ancestors.
* It is false if caller is cross-origin.
* */
sameOriginWithAncestors: boolean;
/** The Relying Party's preference for attestation conveyance */
attestation?: "direct" | "enterprise" | "indirect" | "none";
/** The Relying Party's requirements of the authenticator used in the creation of the credential. */
authenticatorSelection?: {
// authenticatorAttachment?: AuthenticatorAttachment; // not used
requireResidentKey?: boolean;
residentKey?: "discouraged" | "preferred" | "required";
userVerification?: UserVerification;
};
/** Challenge intended to be used for generating the newly created credential's attestation object. */
challenge: string; // b64 encoded
/**
* This member is intended for use by Relying Parties that wish to limit the creation of multiple credentials for
* the same account on a single authenticator. The client is requested to return an error if the new credential would
* be created on an authenticator that also contains one of the credentials enumerated in this parameter.
* */
excludeCredentials?: {
id: string; // b64 encoded
transports?: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[];
type: "public-key";
}[];
/**
* This member contains additional parameters requesting additional processing by the client and authenticator.
**/
extensions?: {
appid?: string; // Not supported
appidExclude?: string; // Not supported
uvm?: boolean; // Not supported
credProps?: boolean;
prf?: {
eval?: {
first: string; // b64 encoded
second?: string; // b64 encoded
};
evalByCredential?: Record<
string,
{
first: string; // b64 encoded
second?: string; // b64 encoded
}
>;
};
largeBlob?: {
support?: "required" | "preferred";
read?: boolean;
write?: string; // b64 encoded
};
};
/**
* This member contains information about the desired properties of the credential to be created.
* The sequence is ordered from most preferred to least preferred.
* The client makes a best-effort to create the most preferred credential that it can.
*/
pubKeyCredParams: PublicKeyCredentialParam[];
/** Data about the Relying Party responsible for the request. */
rp: {
id?: string;
name: string;
};
/** Data about the user account for which the Relying Party is requesting attestation. */
user: {
id: string; // b64 encoded
displayName: string;
name: string;
};
/** Forwarded to user interface */
fallbackSupported: boolean;
/**
* This member specifies a time, in milliseconds, that the caller is willing to wait for the call to complete.
* This is treated as a hint, and MAY be overridden by the client.
**/
timeout?: number;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Inconsistent timeout optionality between params interfaces.

In CreateCredentialParams, timeout is optional (line 131: timeout?: number), but in AssertCredentialParams, timeout is required (line 171: timeout: number).

Per the WebAuthn spec, timeout is optional for both operations. If this inconsistency is intentional (e.g., the implementation always provides a default for assertions), document the reason; otherwise, consider aligning them.

Make timeout optional in AssertCredentialParams
   rpId: string;
   origin: string;
   challenge: string;
   userVerification?: UserVerification;
-  timeout: number;
+  timeout?: number;
   sameOriginWithAncestors: boolean;

Also applies to: 162-195

🤖 Prompt for AI Agents
In @src/shared/types/fido2-types.ts around lines 49 - 132,
CreateCredentialParams marks timeout as optional but AssertCredentialParams
currently requires timeout, which is inconsistent with the WebAuthn spec; change
the AssertCredentialParams interface's timeout property to be optional
(timeout?: number) to match CreateCredentialParams (or if intentional, add a
comment on AssertCredentialParams explaining the defaulting behavior). Update
any callers of AssertCredentialParams to handle an undefined timeout or to
supply an explicit default where necessary.

@iamEvanYT iamEvanYT merged commit b373f11 into main Jan 9, 2026
11 checks passed
@iamEvanYT iamEvanYT deleted the evan/passkeys-on-mac branch January 9, 2026 18:36
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