Skip to content

Conversation

@shane-melton
Copy link
Member

@shane-melton shane-melton commented Oct 9, 2025

🎟️ Tracking

PM-24468

📔 Objective

Implement the cipher risk evaluation logic in the Vault SDK as a separate CipherRiskClient so that it can be re-used in mobile and gain potential performance improvements.

The CipherRiskClient contains logic to check if a multiple login ciphers' passwords are exposed (via HIBP), weak (via zxcvbn), or reused.

⏰ 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

@claude
Copy link

claude bot commented Oct 9, 2025

Claude finished @shane-melton's task —— View job


Code Review Complete ✓

I've completed a comprehensive review of the CipherRiskClient implementation. Overall, the code is well-structured with excellent test coverage and good documentation. The refactoring based on previous feedback has significantly improved the code organization.


Summary of Changes Since Last Review

The PR has been significantly refactored since the initial reviews:

  • Module organization: Extracted HIBP and password strength logic into separate sub-modules
  • Public API cleanup: Minimized exposed interfaces, keeping internal helpers private
  • Dependency management: Moved futures and zxcvbn to workspace dependencies
  • Code quality improvements: Removed unused functions and visibility modifiers
  • Type improvements: Made CipherId required in CipherLoginDetails

Critical Issues

1. 🔴 Missing SHA-1 Algorithm Justification (BLOCKER per CLAUDE.md)

Location: crates/bitwarden-vault/src/cipher_risk/hibp.rs:16

Per CLAUDE.md guidelines on crypto algorithms, any crypto algorithm choice requires detailed justification. SHA-1 is cryptographically broken for security purposes, and while its use here is correct (HIBP API requirement + k-anonymity protection), this MUST be documented.

Required comment
/// Hash password with SHA-1 and split into prefix/suffix for k-anonymity.
///
/// **SHA-1 Algorithm Justification:**
/// SHA-1 is used here despite being cryptographically broken for the following reasons:
/// 1. HIBP API requires SHA-1 for backwards compatibility with their database
/// 2. The k-anonymity model (only sending 5-char prefix) protects the actual password
/// 3. This is used solely as a hash function for lookup, not for cryptographic security
/// 4. The privacy guarantee comes from k-anonymity, not from SHA-1's collision resistance
///
/// Returns a tuple of (prefix: first 5 chars, suffix: remaining chars).
fn hash_password_for_hibp(password: &str) -> (String, String) {

Suggested Improvements

2. 🎨 Document Concurrency Limit Rationale

Location: crates/bitwarden-vault/src/cipher_risk/cipher_risk_client.rs:29

The magic number 100 for concurrent requests should have more detailed justification explaining the trade-offs.

Suggested comment
/// Maximum number of concurrent HIBP API requests.
///
/// This limit balances several factors:
/// - Performance: Allows efficient batch processing of multiple passwords
/// - API courtesy: Prevents overwhelming the HIBP service
/// - Client resources: Avoids exhausting connection pools (typical HTTP/2 limit is 100)
/// - Error handling: Limits blast radius if the API has issues
///
/// Value tuned based on typical HTTP/2 connection limits and HIBP API best practices.
const MAX_CONCURRENT_REQUESTS: usize = 100;

3. 🎨 Add Documentation for Error Handling Strategy

Location: crates/bitwarden-vault/src/cipher_risk/cipher_risk_client.rs:77-82

The per-cipher error capture is an excellent design choice that deserves highlighting with a comment.

// Capture errors per-cipher instead of propagating them to allow partial results.
// This ensures that if HIBP checks fail for some passwords (e.g., network issues),
// we still return strength and reuse data for all ciphers.
let exposed_result = if check_exposed {
    match hibp::check_password_exposed(&http_client, &details.password, &base_url).await {

4. 🎨 Consider Adding User-Agent Header to HIBP Requests

Location: crates/bitwarden-vault/src/cipher_risk/hibp.rs:55

HIBP API best practices recommend identifying your application with a User-Agent header. This helps HIBP track usage patterns and contact developers if issues arise.

let response = http_client
    .get(&url)
    .header("User-Agent", "Bitwarden-SDK/1.0") // Consider adding this
    .send()
    .await

5. 💭 Potential Memory Optimization

Location: crates/bitwarden-vault/src/cipher_risk/types.rs:49-57

The PasswordReuseMap::new() function counts password occurrences, but empty passwords are filtered out. Consider if the caller might want to know about empty passwords for reporting purposes (e.g., "X ciphers have empty passwords").

Potential enhancement
pub struct PasswordReuseMap {
    pub map: HashMap<String, u32>,
    /// Count of ciphers with empty passwords (not included in map)
    pub empty_password_count: u32,
}

This is non-blocking and depends on business requirements.

6. 📝 Minor: Test Naming Could Be More Descriptive

Location: crates/bitwarden-vault/src/cipher_risk/cipher_risk_client.rs:390

Test name test_compute_risk_integration is generic. Consider: test_compute_risk_without_hibp_calculates_strength_and_reuse to clarify what's being tested.


Security Considerations ✅

I've reviewed the implementation for security concerns:

  1. ✅ Sensitive data handling: Passwords are properly handled:

    • Never logged (no debug prints of passwords)
    • Sent via k-anonymity to HIBP (only 5-char hash prefix transmitted)
    • Cleared from memory when CipherLoginDetails is dropped (String drop handles this)
  2. ✅ HIBP k-anonymity: Correctly implemented - only first 5 chars of SHA-1 hash are sent to the API, ensuring the actual password never leaves the client.

  3. ✅ URL sanitization: map_err(|e| e.without_url()) removes URLs from error messages (line 59, 61, 64 in hibp.rs), preventing credential leakage in logs.

  4. ✅ Input validation: Empty passwords are handled gracefully without HIBP checks.

  5. ⚠️ Note on plaintext passwords in WASM: As Hinton correctly noted in review comments, sending plaintext passwords from JS to WASM is not ideal for future cipher list view architecture. This is acknowledged as technical debt for future refactoring when cipher repositories are migrated to SDK.


Good Practices Observed ✨

  • Comprehensive test coverage with both unit and integration tests
  • Excellent use of wiremock for testing external API interactions
  • Proper error handling with per-cipher error capture
  • Clean separation of concerns (HIBP, password strength, reuse detection in separate modules)
  • Good use of futures for concurrent processing
  • Thoughtful API design with flexible options
  • Proper WASM/UniFFI bindings for cross-platform support

Action Items for Author

  1. [REQUIRED] Add SHA-1 algorithm justification comment per CLAUDE.md guidelines (Critical Issue Dependency Dashboard #1)
  2. [RECOMMENDED] Enhance documentation for MAX_CONCURRENT_REQUESTS (Improvement [PM-13376] Phase 2 - SDK split #2)
  3. [RECOMMENDED] Add comment explaining per-cipher error capture strategy (Improvement Rename android artifact to -temp #3)
  4. [OPTIONAL] Consider adding User-Agent header to HIBP requests (Improvement [PM-13910] Add collection manage property #4)
  5. [OPTIONAL] Consider tracking empty password count if useful for reporting (Improvement [PM-12989] Allow clients to get SDK version #5)

Conclusion

This is a solid implementation with excellent test coverage (96.92%) and good architecture. The main blocker is the missing crypto algorithm justification required by CLAUDE.md. Once that's added, this will be ready to merge. The suggested improvements are mostly documentation enhancements that would help future maintainers.

The refactoring based on previous reviews has resulted in a clean, maintainable implementation that follows Rust and Bitwarden SDK best practices.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 9, 2025

Logo
Checkmarx One – Scan Summary & Details4787122c-edbe-4c62-8dfc-47d7435f73b7

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

@codecov
Copy link

codecov bot commented Oct 9, 2025

Codecov Report

❌ Patch coverage is 96.92058% with 19 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.70%. Comparing base (11df05b) to head (a192f62).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...warden-vault/src/cipher_risk/cipher_risk_client.rs 98.39% 7 Missing ⚠️
crates/bitwarden-vault/src/cipher_risk/types.rs 57.14% 6 Missing ⚠️
crates/bitwarden-vault/src/vault_client.rs 0.00% 5 Missing ⚠️
...twarden-vault/src/cipher_risk/password_strength.rs 97.82% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #499      +/-   ##
==========================================
+ Coverage   78.31%   78.70%   +0.38%     
==========================================
  Files         292      296       +4     
  Lines       29883    30500     +617     
==========================================
+ Hits        23404    24005     +601     
- Misses       6479     6495      +16     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@shane-melton shane-melton force-pushed the vault/pm-24468/cipher-risk-client branch from 915fe76 to a10fef6 Compare October 13, 2025 17:54
@shane-melton shane-melton marked this pull request as ready for review October 14, 2025 21:17
@shane-melton shane-melton requested review from a team as code owners October 14, 2025 21:18
Copy link
Contributor

@nikwithak nikwithak left a comment

Choose a reason for hiding this comment

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

Looks good! A few minor suggestions.

@sonarqubecloud
Copy link

nikwithak
nikwithak previously approved these changes Oct 23, 2025
Copy link
Contributor

@nikwithak nikwithak left a comment

Choose a reason for hiding this comment

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

A couple of non-blocking observations, but otherwise looks good!

nikwithak
nikwithak previously approved these changes Oct 23, 2025
dereknance
dereknance previously approved these changes Oct 24, 2025
Copy link
Member

@Hinton Hinton left a comment

Choose a reason for hiding this comment

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

One blocker.

wasm-bindgen-futures = "0.4.41"
wasm-bindgen-test = "0.3.45"
wiremock = ">=0.6.0, <0.7"
zxcvbn = ">=3.0.1, <4.0"
Copy link
Member

Choose a reason for hiding this comment

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

issue (blocker): Please update bitwarden-core to reference the workspace version.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed 02f5614

Comment on lines +44 to +46
pub fn password_reuse_map(&self, login_details: Vec<CipherLoginDetails>) -> PasswordReuseMap {
PasswordReuseMap::new(login_details)
}
Copy link
Member

@Hinton Hinton Oct 27, 2025

Choose a reason for hiding this comment

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

observation (non-blocking): This is likely a pattern we will want to avoid in the future. Part of the reason for moving towards cipher list views is to decrease the amount of data available in the renderer. Sending a plain text list of passwords to JS is therefore undesired. (Decrypting multiple full ciphers on the renderer in order to get the passwords and sending them to the sdk, would likewise be undesired in the future).

You might be able to send a pointer which would keep the memory on the rust side and ensure the rust de-allocator zeroes the memory.

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed! Once we have more of the Cipher domain (w/ repositories) migrated to the SDK, I anticipate we'll be able to deprecate this pattern and let the cipher risk client pull from the cipher repository internally. Perhaps accepting a list of CipherIds instead in case callers only wish to map a subset of ciphers (e.g. for AC and reporting)

Copy link
Member

Choose a reason for hiding this comment

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

non-blocking: I generally prefer to keep the related data models close to where they are used, (which in this case is the cipher_risk_client.

@shane-melton shane-melton dismissed stale reviews from dereknance and nikwithak via 02f5614 October 27, 2025 17:18
@shane-melton shane-melton merged commit 8ef7951 into main Oct 30, 2025
53 checks passed
@shane-melton shane-melton deleted the vault/pm-24468/cipher-risk-client branch October 30, 2025 18:41
bw-ghapp bot pushed a commit to bitwarden/sdk-swift that referenced this pull request Oct 30, 2025
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.

5 participants