Skip to content

Conversation

@abergs
Copy link
Member

@abergs abergs commented Mar 24, 2025

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-8353

📔 Objective

This PR serves as the long running feature branch for passkey provider support.
MacOS Passkey MVP Proposal
Explainer for other teams
Testing Setup
Passkey Workflow Videos

📸 Screenshots

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

@codecov
Copy link

codecov bot commented Mar 24, 2025

Codecov Report

❌ Patch coverage is 34.28571% with 299 lines in your changes missing coverage. Please review.
✅ Project coverage is 41.27%. Comparing base (9e6d0cc) to head (a38bc28).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
.../src/autofill/services/desktop-autofill.service.ts 2.24% 87 Missing ⚠️
...l/services/desktop-fido2-user-interface.service.ts 0.00% 59 Missing ⚠️
...s/desktop/desktop_native/macos_provider/src/lib.rs 0.00% 37 Missing ⚠️
...src/platform/main/autofill/native-autofill.main.ts 0.00% 27 Missing ⚠️
apps/desktop/desktop_native/napi/src/lib.rs 0.00% 19 Missing ⚠️
...esktop/src/autofill/guards/reactive-vault-guard.ts 0.00% 19 Missing ⚠️
...tofill/modal/credentials/fido2-create.component.ts 77.64% 18 Missing and 1 partial ⚠️
...utofill/modal/credentials/fido2-vault.component.ts 86.66% 7 Missing and 1 partial ⚠️
apps/desktop/src/platform/popup-modal-styles.ts 0.00% 6 Missing ⚠️
apps/desktop/src/app/app-routing.module.ts 0.00% 4 Missing ⚠️
... and 7 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #13963      +/-   ##
==========================================
+ Coverage   41.23%   41.27%   +0.03%     
==========================================
  Files        3543     3546       +3     
  Lines      101963   102291     +328     
  Branches    15295    15338      +43     
==========================================
+ Hits        42048    42224     +176     
- Misses      58152    58300     +148     
- Partials     1763     1767       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 24, 2025

Logo
Checkmarx One – Scan Summary & Details9a39ec3a-8799-4db9-8b97-cbda79a854b5

New Issues (7)

Checkmarx found the following issues in this Pull Request

Severity Issue Source File / Package Checkmarx Insight
HIGH CVE-2025-12727 Npm-electron-37.7.0
detailsDescription: Inappropriate implementation in V8 in Google Chrome prior to 142.0.7444.137 allowed a remote attacker to potentially exploit heap corruption via a ...
Attack Vector: NETWORK
Attack Complexity: LOW

ID: QuIjKQo3SsmQhRnzEmkohxQ9Mwk3bPmcDjDNUmk8EV0%3D
Vulnerable Package
HIGH CVE-2025-12907 Npm-electron-37.7.0
detailsDescription: Insufficient validation of untrusted input in Devtools in Google Chrome prior to 140.0.7339.80 allowed a remote attacker to execute arbitrary code ...
Attack Vector: NETWORK
Attack Complexity: LOW

ID: XFYFwxD6vqS8uMhYbKyUkRFARfKMmvRdCbzZoGeN%2B78%3D
Vulnerable Package
MEDIUM CVE-2025-11215 Npm-electron-37.7.0
detailsDescription: Off-by-one Error in V8 in Google Chrome prior to 141.0.7390.54 allowed a remote attacker to perform an Out-of-bounds Memory Read via a crafted HTML...
Attack Vector: NETWORK
Attack Complexity: LOW

ID: NVT%2F4Rn7JRRsnglE9fReLcdWh3gmCJWXjT%2FdsKSdgu4%3D
Vulnerable Package
MEDIUM CVE-2025-11216 Npm-electron-37.7.0
detailsRecommended version: 39.2.0
Description: Inappropriate implementation in Storage in Google Chrome on Mac prior to 141.0.7390.54 allowed a remote attacker to perform domain spoofing via a c...
Attack Vector: NETWORK
Attack Complexity: LOW

ID: J%2B%2FQEE%2B3LWjYq5l%2BtEK9BslgLiBPFQxCVs1xOKHxT%2Fs%3D
Vulnerable Package
MEDIUM CVE-2025-12728 Npm-electron-37.7.0
detailsDescription: Inappropriate implementation in Omnibox in Google Chrome on Android prior to 142.0.7444.137 allowed a remote attacker who convinced a user to engag...
Attack Vector: NETWORK
Attack Complexity: HIGH

ID: p1lasSHcT6O44xENvA5OFRlx0awvOjqdzTBOoL3zFjE%3D
Vulnerable Package
MEDIUM CVE-2025-12729 Npm-electron-37.7.0
detailsDescription: Inappropriate implementation in Omnibox in Google Chrome on Android prior to 142.0.7444.137 allowed a remote attacker who convinced a user to engag...
Attack Vector: NETWORK
Attack Complexity: HIGH

ID: vUBiL%2Fz8YN0svAzm9%2B7Cli3mUY3HjG3L9EQm3U9anZY%3D
Vulnerable Package
LOW CVE-2025-11219 Npm-electron-37.7.0
detailsRecommended version: 39.2.0
Description: Use After Free in V8 in Google Chrome prior to 141.0.7390.54 allowed a remote attacker to potentially perform Out-of-bounds Memory Access via a cra...
Attack Vector: NETWORK
Attack Complexity: HIGH

ID: ajxzdEyNPqSogxVksc3%2B7Uic9oPEIhGj7bKYvCpewA8%3D
Vulnerable Package

abergs and others added 21 commits March 31, 2025 21:53
* React to IPC disconnects

* Minor cleanup

* Update apps/desktop/package.json

Co-authored-by: Daniel García <[email protected]>

* Relaxed ordering

---------

Co-authored-by: Daniel García <[email protected]>
* Passkey stuff

Co-authored-by: Anders Åberg <[email protected]>

* Ugly hacks

* Work On Modal State Management

* Applying modalStyles

* modal

* Improved hide/show

* fixed promise

* File name

* fix prettier

* Protecting against null API's and undefined data

* Only show fake popup to devs

* cleanup mock code

* rename minmimal-app to modal-app

* Added comment

* Added comment

* removed old comment

* Avoided changing minimum size

* Add small comment

* Rename component

* adress feedback

* Fixed uppercase file

* Fixed build

* Added codeowners

* added void

* commentary

* feat: reset setting on app start

* Moved reset to be in main / process launch

* Add comment to create window

* Added a little bit of styling

* Use Messaging service to loadUrl

* Enable passkeysautofill

* Add logging

* halfbaked

* Integration working

* And now it works without extra delay

* Clean up

* add note about messaging

* lb

* removed console.logs

* Cleanup and adress review feedback

* This hides the swift UI

* add modal components

* update modal with correct ciphers and functionality

* add create screen

* pick credential, draft

* Remove logger

* a whole lot of wiring

* not working

* Improved wiring

* Cancel after 90s

* Introduced observable

* update cipher handling

* update to use matchesUri

* Launching bitwarden if its not running

* Passing position from native to electron

* Rename inModalMode to modalMode

* remove tap

* revert spaces

* added back isDev

* cleaned up a bit

* Cleanup swift file

* tweaked logging

* clean up

* Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift

Co-authored-by: Andreas Coroiu <[email protected]>

* Update apps/desktop/src/platform/main/autofill/native-autofill.main.ts

Co-authored-by: Andreas Coroiu <[email protected]>

* Update apps/desktop/src/platform/services/desktop-settings.service.ts

Co-authored-by: Andreas Coroiu <[email protected]>

* adress position feedback

* Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift

Co-authored-by: Andreas Coroiu <[email protected]>

* Removed extra logging

* Adjusted error logging

* Use .error to log errors

* remove dead code

* Update desktop-autofill.service.ts

* use parseCredentialId instead of guidToRawFormat

* Update apps/desktop/src/autofill/services/desktop-autofill.service.ts

Co-authored-by: Andreas Coroiu <[email protected]>

* Change windowXy to a Record instead of [number,number]

* Update apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts

Co-authored-by: Andreas Coroiu <[email protected]>

* Remove unsued dep and comment

* changed timeout to be spec recommended maxium, 10 minutes, for now.

* Correctly assume UP

* Removed extra cancelRequest in deinint

* Add timeout and UV to confirmChoseCipher

UV is performed by UI, not the service

* Improved docs regarding undefined cipherId

* cleanup: UP is no longer undefined

* Run completeError if ipc messages conversion failed

* don't throw, instead return undefined

* Disabled passkey provider

* Throw error if no activeUserId was found

* removed comment

* Fixed lint

* removed unsued service

* reset entitlement formatting

* Update entitlements.mas.plist

* Fix build issues

* Fix import issues

* Update route names to use `fido2`

* Fix being unable to select a passkey

* Fix linting issues

* Followup to fix merge issues and other comments

* Update `userHandle` value

* Add error handling for missing session or other errors

* Remove unused route

* Fix linting issues

* Simplify updateCredential method

* Followup to remove comments and timeouts and handle errors

* Address lint issue by using `takeUntilDestroyed`

* PR Followup for typescript and vault concerns

* Add try block for cipher creation

* Make userId manditory for cipher service

---------

Co-authored-by: Justin Baur <[email protected]>
Co-authored-by: Anders Åberg <[email protected]>
Co-authored-by: Anders Åberg <[email protected]>
Co-authored-by: Colton Hurst <[email protected]>
Co-authored-by: Andreas Coroiu <[email protected]>
Co-authored-by: Evan Bassler <[email protected]>
Co-authored-by: Andreas Coroiu <[email protected]>
* Implemented a SendNativeStatus command

This allows reporting status or asking the electron app to do something.

* fmt

* Update apps/desktop/src/autofill/services/desktop-autofill.service.ts

Co-authored-by: Daniel García <[email protected]>

* clean up

* Don't add empty callbacks

* Removed comment

---------

Co-authored-by: Daniel García <[email protected]>
* works

* Add mapping

* remove the build script

* cleanup
* Add support for padding in base64url decode

* whitespace

* whitespace
* Passkey stuff

Co-authored-by: Anders Åberg <[email protected]>

* Ugly hacks

* Work On Modal State Management

* Applying modalStyles

* modal

* Improved hide/show

* fixed promise

* File name

* fix prettier

* Protecting against null API's and undefined data

* Only show fake popup to devs

* cleanup mock code

* rename minmimal-app to modal-app

* Added comment

* Added comment

* removed old comment

* Avoided changing minimum size

* Add small comment

* Rename component

* adress feedback

* Fixed uppercase file

* Fixed build

* Added codeowners

* added void

* commentary

* feat: reset setting on app start

* Moved reset to be in main / process launch

* Add comment to create window

* Added a little bit of styling

* Use Messaging service to loadUrl

* Enable passkeysautofill

* Add logging

* halfbaked

* Integration working

* And now it works without extra delay

* Clean up

* add note about messaging

* lb

* removed console.logs

* Cleanup and adress review feedback

* This hides the swift UI

* add modal components

* update modal with correct ciphers and functionality

* add create screen

* pick credential, draft

* Remove logger

* a whole lot of wiring

* not working

* Improved wiring

* Cancel after 90s

* Introduced observable

* update cipher handling

* update to use matchesUri

* Launching bitwarden if its not running

* Passing position from native to electron

* Rename inModalMode to modalMode

* remove tap

* revert spaces

* added back isDev

* cleaned up a bit

* Cleanup swift file

* tweaked logging

* clean up

* Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift

Co-authored-by: Andreas Coroiu <[email protected]>

* Update apps/desktop/src/platform/main/autofill/native-autofill.main.ts

Co-authored-by: Andreas Coroiu <[email protected]>

* Update apps/desktop/src/platform/services/desktop-settings.service.ts

Co-authored-by: Andreas Coroiu <[email protected]>

* adress position feedback

* Update apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift

Co-authored-by: Andreas Coroiu <[email protected]>

* Removed extra logging

* Adjusted error logging

* Use .error to log errors

* remove dead code

* Update desktop-autofill.service.ts

* use parseCredentialId instead of guidToRawFormat

* Update apps/desktop/src/autofill/services/desktop-autofill.service.ts

Co-authored-by: Andreas Coroiu <[email protected]>

* Change windowXy to a Record instead of [number,number]

* Update apps/desktop/src/autofill/services/desktop-fido2-user-interface.service.ts

Co-authored-by: Andreas Coroiu <[email protected]>

* Remove unsued dep and comment

* changed timeout to be spec recommended maxium, 10 minutes, for now.

* Correctly assume UP

* Removed extra cancelRequest in deinint

* Add timeout and UV to confirmChoseCipher

UV is performed by UI, not the service

* Improved docs regarding undefined cipherId

* cleanup: UP is no longer undefined

* Run completeError if ipc messages conversion failed

* don't throw, instead return undefined

* Disabled passkey provider

* Throw error if no activeUserId was found

* removed comment

* Fixed lint

* removed unsued service

* reset entitlement formatting

* Update entitlements.mas.plist

* Fix build issues

* Fix import issues

* Update route names to use `fido2`

* Fix being unable to select a passkey

* Fix linting issues

* Added support for handling a locked vault

* Followup to fix merge issues and other comments

* Update `userHandle` value

* Add error handling for missing session or other errors

* Remove unused route

* Fix linting issues

* Simplify updateCredential method

* Add master password reprompt on passkey create

* Followup to remove comments and timeouts and handle errors

* Address lint issue by using `takeUntilDestroyed`

* Add MP prompt to cipher selection

* Change how timeout is handled

* Include `of` from rxjs

* Hide blue header for passkey popouts (#14095)

* Hide blue header for passkey popouts

* Fix issue with test

* Fix ngOnDestroy complaint

* Import OnDestroy correctly

* Only require master password if item requires it

---------

Co-authored-by: Justin Baur <[email protected]>
Co-authored-by: Anders Åberg <[email protected]>
Co-authored-by: Anders Åberg <[email protected]>
Co-authored-by: Colton Hurst <[email protected]>
Co-authored-by: Andreas Coroiu <[email protected]>
Co-authored-by: Evan Bassler <[email protected]>
Co-authored-by: Andreas Coroiu <[email protected]>
This changes the behaviour to react to logoff, but not to account locks. It also adds better error handling on the native side.
* Enables autofill extension building

* Try use macos-14

* add --break-system-packages for macos14

* revert using build-native

* try add rustup target add x86_64-apple-darwin

* add more rustup target add x86_64-apple-darwin

* try to force sdk version

* Show SDK versions

* USE KVC for excludedCredentials

* added xcodebuild deugging

* Revert "try to force sdk version"

This reverts commit d94f255.

* Use macos-15

* undo merge

* remove macos-15 from cli

* remove macos-15 from browser

---------

Co-authored-by: Anders Åberg <[email protected]>
coroiu
coroiu previously requested changes May 12, 2025
abergs and others added 2 commits May 13, 2025 14:16
* Delay IPC server start

* Better ipc handling

* Rename ready() to listenerReady()

---------

Co-authored-by: Daniel García <[email protected]>
* Show items for url that don't have passkey

* Show existing login items in the UI

* Filter available cipher results (#14399)

* Filter available cipher results

* Fix linting issues

* Update logic for eligible ciphers

* Remove unused method to check matching username

* PM-20608 update styling for excludedCredentials (#14444)

* PM-20608 update styling for excludedCredentials

* Have flow correctly move to creation for excluded cipher

* Remove duplicate confirmNeCredential call

* Revert fido2-authenticator changes and move the excluded check

* Create a separate component for excluded cipher view

* Display traffic light MacOS buttons when the vault is locked (#14673)

* Remove unneccessary filter for excludedCiphers

* Remove dead code from the excluded ciphers work

* Remove excludedCipher checks from fido2 create and vault

* Remove excludedCipher remnants from vault and simplify create cipher logic

* Move cipherHasNoOtherPasskeys to shared fido2-utils

* Remove all containsExcludedCipher references

* Use `bufferToString` to convert `userHandle`

---------

Co-authored-by: Jeffrey Holland <[email protected]>
Co-authored-by: Jeffrey Holland <[email protected]>
</bit-section>

<bit-section class="tw-bg-background-alt tw-p-4 tw-flex tw-flex-col">
<div *ngIf="(ciphers$ | async)?.length === 0; else hasCiphers">
Copy link

Choose a reason for hiding this comment

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

Angular Control Flow: This PR should use the modern @if / @else syntax instead of *ngIf with ng-template.

Suggested change
<div *ngIf="(ciphers$ | async)?.length === 0; else hasCiphers">
<div @if ((ciphers$ | async)?.length === 0) {

This applies to the entire template structure. The modern syntax improves readability and is the recommended pattern for new code. Since this is a new component being added, it should follow current best practices from the start.

</div>
</div>
<ng-template #hasCiphers>
<bit-item *ngFor="let c of ciphers$ | async" class="">
Copy link

Choose a reason for hiding this comment

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

Angular Control Flow: Use @for instead of *ngFor for iteration.

Suggested change
<bit-item *ngFor="let c of ciphers$ | async" class="">
<bit-item @for (c of ciphers$ | async; track c.id) {

The @for syntax is the modern Angular pattern and should be used in new components. Note the required track expression for performance optimization.


#[uniffi::export]
impl MacOSProviderClient {
// FIXME: Remove unwraps! They panic and terminate the whole application.
Copy link

Choose a reason for hiding this comment

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

⚠️ Acknowledged tech debt: The FIXME comment correctly identifies that unwraps should be removed. These can cause panics and crash the extension.

The comment acknowledges this is a known issue. Consider creating a follow-up ticket to address this before the feature exits beta/MVP status, as production crashes from panics would significantly impact user experience.

return MacOsProviderClient.connect()
}()
self.client = newClient
return newClient!
Copy link

Choose a reason for hiding this comment

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

⚠️ Force unwrap on optional: newClient! will crash if the connection loop completes without establishing a connection.

guard let client = newClient else {
    logger.error("[autofill-extension] Failed to create client after retries")
    throw NSError(domain: "com.bitwarden.desktop", code: 1, userInfo: nil)
}
self.client = client
return client

This prevents crashes and provides better error messaging to users.

changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Fido2CreateComponent implements OnInit, OnDestroy {
session?: DesktopFido2UserInterfaceSession = null;
Copy link

Choose a reason for hiding this comment

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

⚠️ Inconsistent optional typing: session is typed as DesktopFido2UserInterfaceSession | undefined but initialized to null.

Suggested change
session?: DesktopFido2UserInterfaceSession = null;
session?: DesktopFido2UserInterfaceSession;

Use either undefined (via ?) or null, but not both. The ? syntax is preferred and matches how it's checked elsewhere in the code.

changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Fido2VaultComponent implements OnInit, OnDestroy {
session?: DesktopFido2UserInterfaceSession = null;
Copy link

Choose a reason for hiding this comment

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

⚠️ Same inconsistent typing: Use ? for optional instead of = null.

Suggested change
session?: DesktopFido2UserInterfaceSession = null;
session?: DesktopFido2UserInterfaceSession;


.tw-app-region-no-drag {
-webkit-app-region: no-drag;
}
Copy link

Choose a reason for hiding this comment

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

💭 Documentation needed: These custom utility classes lack documentation about their purpose and when to use them.

Consider adding a comment block:

/**
 * macOS window dragging utilities
 * .tw-app-region-drag - Makes the element draggable for window movement (macOS titlebar)
 * .tw-app-region-no-drag - Prevents dragging on interactive elements within draggable areas
 */

This helps future developers understand the purpose of these non-standard Tailwind classes.

"fileSavedToDevice": {
"message": "File saved to device. Manage from your device downloads."
},
"importantNotice": {
Copy link

Choose a reason for hiding this comment

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

💭 Localization process: Since this adds new user-facing strings, confirm these have been or will be submitted for translation via Crowdin.

Per the earlier comment thread, design approval was obtained. Just verifying the i18n process is also complete or planned.


function normalizePosition(position: { x: number; y: number }): { x: number; y: number } {
// Add 100 pixels to the x-coordinate to offset the native OS dialog positioning.
const xPostionOffset = 100;
Copy link

Choose a reason for hiding this comment

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

⚠️ Typo in variable name: xPostionOffset should be xPositionOffset

changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Fido2CreateComponent implements OnInit, OnDestroy {
session?: DesktopFido2UserInterfaceSession = null;
Copy link

Choose a reason for hiding this comment

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

💭 TypeScript inconsistency: The type annotation DesktopFido2UserInterfaceSession | undefined suggests it could be undefined, but it's initialized to null. Consider using null in the type annotation instead for consistency: DesktopFido2UserInterfaceSession | null

changeDetection: ChangeDetectionStrategy.OnPush,
})
export class Fido2VaultComponent implements OnInit, OnDestroy {
session?: DesktopFido2UserInterfaceSession = null;
Copy link

Choose a reason for hiding this comment

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

💭 Same as in fido2-create.component.ts: initialized to null but typed as undefined. Consider using consistent null/undefined semantics.


#[uniffi::export]
impl MacOSProviderClient {
// FIXME: Remove unwraps! They panic and terminate the whole application.
Copy link

Choose a reason for hiding this comment

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

⚠️ As noted in the FIXME comment, unwraps should be removed. While this specific panic location may be acceptable during initialization, consider using .expect() with descriptive messages for better debugging. The current comment acknowledges this but the issue persists.

}),
),
);
}
Copy link

Choose a reason for hiding this comment

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

Observable anti-pattern: Using map with an async function doesn't wait for the promise to complete. The observable will emit the unresolved Promise, not the result.

Fix: Change map to switchMap:

async updateCredential(cipher: CipherView): Promise<void> {
  this.logService.info("updateCredential");
  await firstValueFrom(
    this.accountService.activeAccount$.pipe(
      switchMap(async (a) => {
        if (a) {
          const encCipher = await this.cipherService.encrypt(cipher, a.id);
          await this.cipherService.updateWithServer(encCipher);
        }
      }),
    ),
  );
}

This ensures the async operations complete before the observable completes.

return MacOsProviderClient.connect()
}()
self.client = newClient
return newClient!
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential crash: Force unwrapping newClient! could crash if all connection attempts fail. Consider handling this case gracefully:

guard let client = newClient else {
    logger.error("[autofill-extension] Failed to establish connection after all retries")
    // Handle failure appropriately - perhaps throw an error or return a default/stub client
    fatalError("Unable to connect to Bitwarden Desktop")
}
self.client = client
return client

Or make the return type optional and handle nil at call sites.

}

const userHandle = Fido2Utils.bufferToString(
new Uint8Array(lastRegistrationRequest.userHandle),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential error: Consider validating lastRegistrationRequest.userHandle before conversion to prevent potential crashes:

const userHandle = lastRegistrationRequest.userHandle 
  ? Fido2Utils.bufferToString(new Uint8Array(lastRegistrationRequest.userHandle))
  : null;

if (!userHandle) {
  this.logService.error("Invalid userHandle in registration request");
  return;
}


function normalizePosition(position: { x: number; y: number }): { x: number; y: number } {
// Add 100 pixels to the x-coordinate to offset the native OS dialog positioning.
const xPostionOffset = 100;
Copy link

Choose a reason for hiding this comment

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

🎨 Minor improvement: Typo in variable name and could be extracted to class-level constant:

// At class level:
private readonly POSITION_X_OFFSET = 100;

// In function:
return {
  x: Math.round(position.x + this.POSITION_X_OFFSET),
  y: Math.round(position.y),
};

Note: "xPostionOffset" → "POSITION_X_OFFSET"

this.registrationRequest = request;

this.logService.debug("listenPasskeyRegistration", clientId, sequenceNumber, request);
this.logService.debug("listenPasskeyRegistration2", this.convertRegistrationRequest(request));
Copy link

Choose a reason for hiding this comment

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

Security concern: This debug log outputs the full converted registration request which may contain sensitive user data (userHandle, userName, etc.).

While this doesn't log decrypted vault data directly, it does log user identification information that should not appear in production logs.

Recommendation: Either:

  1. Remove the second debug log entirely (the first on line 212 already logs the basic info)
  2. Gate this behind a development-only check
  3. Create a sanitized version that logs only non-sensitive fields (rpId, algorithms count, etc.)
// Option 1: Remove entirely
this.logService.debug("listenPasskeyRegistration", clientId, sequenceNumber, request);

// Option 2: Development only
if (isDevelopment()) {
  this.logService.debug("listenPasskeyRegistration2", this.convertRegistrationRequest(request));
}

// Option 3: Sanitized logging
this.logService.debug("listenPasskeyRegistration2", {
  rpId: request.rpId,
  algorithmCount: request.supportedAlgorithms.length,
  hasExcludedCredentials: request.excludedCredentials.length > 0
});

return;
}

this.logService.debug("listenPasskeyAssertion", clientId, sequenceNumber, request);
Copy link

Choose a reason for hiding this comment

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

Similar concern: This logs the full assertion request which includes credential identifiers and user context.

Recommendation: Consider reducing to essential info only:

this.logService.debug("listenPasskeyAssertion", {
  clientId,
  sequenceNumber,
  rpId: request.rpId,
  credentialCount: request.allowedCredentials?.length ?? 0
});

// Create a new client instance for each retry
newClient = MacOsProviderClient.connect()
try? await Task.sleep(nanoseconds: UInt64(100 * attempt + (delayMs * 1_000_000))) // Convert ms to nanoseconds
let connectionStatus = newClient!.getConnectionStatus()
Copy link

Choose a reason for hiding this comment

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

⚠️ Force unwrap risk: While the retry logic makes this unlikely to fail, there's still a path where all 20 connection attempts fail and newClient could be nil, causing a crash here.

Recommendation: Use safer optional handling:

guard let client = newClient else {
    logger.error("[autofill-extension] Failed to establish client after \(maxRetries) attempts")
    // Could throw an error or return a failed client state
    fatalError("Unable to establish IPC connection to Bitwarden Desktop")
}
let connectionStatus = client.getConnectionStatus()

This makes the failure mode explicit rather than risking an unexpected crash.

return MacOsProviderClient.connect()
}()
self.client = newClient
return newClient!
Copy link

Choose a reason for hiding this comment

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

⚠️ Same force unwrap concern: This should use guard or optional chaining to handle the nil case gracefully.

guard let client = self.client, let newClient = newClient else {
    logger.error("[autofill-extension] Client initialization failed")
    fatalError("Unable to establish IPC connection to Bitwarden Desktop")
}
return client ?? newClient

Comment on lines +133 to +151
combineLatest([this.cipherService.cipherListViews$(activeUserId), this.cipherIds$ || of(null)])
.pipe(
map(([ciphers, cipherIds]) => {
// Filter out deleted ciphers
const activeCiphers = ciphers.filter((cipher) => !cipher.deletedDate);

// If specific IDs provided, filter by them
if (cipherIds?.length > 0) {
return activeCiphers.filter((cipher) => cipherIds.includes(cipher.id as string));
}

return activeCiphers;
}),
takeUntil(this.destroy$),
)
.subscribe({
next: (ciphers) => this.ciphersSubject.next(ciphers as CipherView[]),
error: (error: unknown) => this.logService.error("Failed to load ciphers", error),
});
Copy link

Choose a reason for hiding this comment

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

💭 Question about subscription pattern: This implementation uses explicit subscribe with takeUntil(this.destroy$) which is valid, but I'm curious about the pattern choice.

Given ADR-0003's preference for the async pipe pattern, was there a specific reason not to use:

// In template:
<div *ngFor="let cipher of ciphers$ | async">

This would:

  1. Handle subscription cleanup automatically
  2. Be more consistent with the Observable Data Services pattern
  3. Eliminate the need for manual BehaviorSubject management

Is the manual subscription needed for error handling or some other requirement I'm missing? Just want to understand the design decision.

@bw-ghapp
Copy link
Contributor

bw-ghapp bot commented Nov 20, 2025

⚠️ Changes in this PR impact the Autofill experience of the browser client ⚠️

BIT has tested the core experience with these changes and the feature flag configuration used by vault.bitwarden.com

Caution

Unfortunately, one or more of these tests failed. 😞

Please resolve the failure before merging; reach out to @bitwarden/team-autofill-dev if you'd like help.

You can view the detailed results of the tests here.

@bw-ghapp
Copy link
Contributor

bw-ghapp bot commented Nov 20, 2025

⚠️ Changes in this PR impact the Autofill experience of the browser client ⚠️

BIT has tested the core experience with these changes and all feature flags disabled.

Caution

Unfortunately, one or more of these tests failed. 😞

Please resolve the failure before merging; reach out to @bitwarden/team-autofill-dev if you'd like help.

You can view the detailed results of the tests here.

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

Labels

needs-qa Marks a PR as requiring QA approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.