Skip to content

Conversation

@quexten
Copy link
Contributor

@quexten quexten commented Dec 31, 2025

🎟️ Tracking

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

📔 Objective

This PR removes the following individual cryptographic states:

  • User Private Key
  • User Signature Key
  • User Signed Public Key
  • User Signed Security State
    and replaces all subscriptions to these states, with subscriptions to the account cryptography state.

These temporary mappings can be removed once the SDK service subscribes directly to the account cryptographic state, and their services can be removed.

Please note, this also drops the old path for account private key regen, if an account does not have a private key. This can instead be done in the Data Recovery tool.

📸 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

@github-actions
Copy link
Contributor

github-actions bot commented Dec 31, 2025

Logo
Checkmarx One – Scan Summary & Details4e3f3fe9-74d3-4617-a226-f6090856aca2

Great job! No new security vulnerabilities introduced in this pull request

@quexten quexten changed the title poc Remove individual cryptographic-key states Dec 31, 2025
@codecov
Copy link

codecov bot commented Dec 31, 2025

Codecov Report

❌ Patch coverage is 59.70149% with 27 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.25%. Comparing base (2665a29) to head (3a87a5d).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
libs/key-management/src/key.service.ts 30.43% 14 Missing and 2 partials ⚠️
.../security-state/services/security-state.service.ts 0.00% 8 Missing ⚠️
apps/browser/src/background/main.background.ts 0.00% 2 Missing ⚠️
apps/browser/src/popup/services/services.module.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #18164      +/-   ##
==========================================
- Coverage   42.28%   42.25%   -0.03%     
==========================================
  Files        3599     3599              
  Lines      104552   104527      -25     
  Branches    15782    15774       -8     
==========================================
- Hits        44208    44170      -38     
- Misses      58469    58485      +16     
+ Partials     1875     1872       -3     

☔ 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.

);
}

if (tokenResponse.hasMasterKeyEncryptedUserKey()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here, the migration is to just set the account cryptographic state conditionally. Private key creation is happening anymore, so there is no need for this special branching logic.

@quexten quexten force-pushed the km/remove-individual-key-states branch from f206a76 to b3921d5 Compare January 1, 2026 14:07
@quexten quexten changed the title Remove individual cryptographic-key states Remove individual cryptographic-key states & migrate key service Jan 1, 2026
@quexten quexten changed the title Remove individual cryptographic-key states & migrate key service [PM-29208] Remove individual cryptographic-key states & migrate key service Jan 1, 2026
@quexten quexten force-pushed the km/remove-individual-key-states branch from 4425d87 to dc75f19 Compare January 1, 2026 14:12
@quexten quexten added the ai-review Request a Claude code review label Jan 1, 2026
@claude
Copy link
Contributor

claude bot commented Jan 1, 2026

Claude finished @quexten's task in 4m 10s —— View job


Overall Assessment: REQUEST CHANGES

This PR removes four individual cryptographic state keys (privateKey, userSigningKey, userSignedPublicKey, accountSecurityState) and consolidates them into a single accountCryptographicState subscription pattern. The migration is correctly implemented, and the refactoring follows the intended architecture. However, there are critical null safety issues in login strategy implementations.

Code Review Details
  • CRITICAL: Potential null pointer exception - accountKeysResponseModel can be null
    • libs/auth/src/common/login-strategies/password-login.strategy.ts:156
    • libs/auth/src/common/login-strategies/auth-request-login.strategy.ts:128
    • libs/auth/src/common/login-strategies/user-api-login.strategy.ts:87
    • libs/auth/src/common/login-strategies/webauthn-login.strategy.ts:107

Migration Quality: The migration correctly removes all four legacy state keys and is properly registered. Test coverage validates the removal logic.

Service Injection: The dependency ordering changes are correct - accountCryptographicStateService is now instantiated before securityStateService and keyService, which properly resolves the dependency chain.

Architecture: The refactoring successfully eliminates redundant state subscriptions and prepares the codebase for SDK service direct subscription to accountCryptographicState.

await this.keyService.setPrivateKey(
response.privateKey ?? (await this.createKeyPairForOldAccount(userId)),
await this.accountCryptographicStateService.setAccountCryptographicState(
response.accountKeysResponseModel.toWrappedAccountCryptographicState(),
Copy link
Contributor

Choose a reason for hiding this comment

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

CRITICAL: Null reference exception when accountKeysResponseModel is null

Details and fix

This code unconditionally calls .toWrappedAccountCryptographicState() on response.accountKeysResponseModel, but this field can be null according to the type definition in IdentityTokenResponse (line 30: accountKeysResponseModel: PrivateKeysResponseModel | null = null).

When this breaks:

  • JIT-provisioned SSO users who don't yet have asymmetric key pairs
  • Any scenario where the server doesn't return accountKeys (noted in sync service comment about PM-21768)

Execution path:

  1. User logs in with password
  2. Server returns token response with accountKeysResponseModel = null
  3. Line 156 executes: response.accountKeysResponseModel.toWrappedAccountCryptographicState()
  4. Throws: Cannot read property 'toWrappedAccountCryptographicState' of null

Fix required:
Add null check like SSO login strategy does:

protected override async setAccountCryptographicState(
  response: IdentityTokenResponse,
  userId: UserId,
): Promise<void> {
  if (response.accountKeysResponseModel) {
    await this.accountCryptographicStateService.setAccountCryptographicState(
      response.accountKeysResponseModel.toWrappedAccountCryptographicState(),
      userId,
    );
  }
}

Why SSO strategy is correct: See sso-login.strategy.ts lines 338-348 and test at line 199 which explicitly tests for accountKeysResponseModel = null.

Why this matters: The PR description states this removes the "old path for account private key regen", but the replacement assumes accountKeysResponseModel is always present. Per the sync service comment (line 243), the server doesn't always return this field yet (PM-21768 tracking).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not correct. Password login strategy never has null account keys, only SSO login does.

await this.keyService.setPrivateKey(
response.privateKey ?? (await this.createKeyPairForOldAccount(userId)),
await this.accountCryptographicStateService.setAccountCryptographicState(
response.accountKeysResponseModel.toWrappedAccountCryptographicState(),
Copy link
Contributor

Choose a reason for hiding this comment

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

CRITICAL: Null reference exception when accountKeysResponseModel is null

Details and fix

Same null safety issue as password login strategy. This unconditionally accesses response.accountKeysResponseModel.toWrappedAccountCryptographicState() without checking if accountKeysResponseModel is null.

Fix required:

protected override async setAccountCryptographicState(
  response: IdentityTokenResponse,
  userId: UserId,
): Promise<void> {
  if (response.accountKeysResponseModel) {
    await this.accountCryptographicStateService.setAccountCryptographicState(
      response.accountKeysResponseModel.toWrappedAccountCryptographicState(),
      userId,
    );
  }
}

Reference the SSO login strategy (lines 338-348) for the correct pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not correct. Password login strategy never has null account keys, only SSO login does.

await this.keyService.setPrivateKey(
response.privateKey ?? (await this.createKeyPairForOldAccount(userId)),
await this.accountCryptographicStateService.setAccountCryptographicState(
response.accountKeysResponseModel.toWrappedAccountCryptographicState(),
Copy link
Contributor

Choose a reason for hiding this comment

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

CRITICAL: Null reference exception when accountKeysResponseModel is null

Details and fix

Same null safety issue. Add null check:

protected override async setAccountCryptographicState(
  response: IdentityTokenResponse,
  userId: UserId,
): Promise<void> {
  if (response.accountKeysResponseModel) {
    await this.accountCryptographicStateService.setAccountCryptographicState(
      response.accountKeysResponseModel.toWrappedAccountCryptographicState(),
      userId,
    );
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not correct. Password login strategy never has null account keys, only SSO login does.

@quexten quexten removed the ai-review Request a Claude code review label Jan 1, 2026
@quexten
Copy link
Contributor Author

quexten commented Jan 1, 2026

Disabling claude since it's just duplicating.

@quexten
Copy link
Contributor Author

quexten commented Jan 1, 2026

BIT tests are most likely failing due to the outdated server version that BIT is using. I'll check in with autofill about this before merging, but locally everything works.

@quexten quexten marked this pull request as ready for review January 1, 2026 15:07
@quexten quexten requested review from a team as code owners January 1, 2026 15:07
@quexten quexten requested a review from a team as a code owner January 1, 2026 15:07
@bw-ghapp
Copy link
Contributor

bw-ghapp bot commented Jan 1, 2026

⚠️ 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 Jan 1, 2026

⚠️ 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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants