-
Notifications
You must be signed in to change notification settings - Fork 20
[PM-24468] Introduce CipherRiskClient #499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Claude finished @shane-melton's task —— View job Code Review CompleteSummary of Changes Since Last ReviewThe PR has undergone significant refactoring based on reviewer feedback:
Previous Claude Suggestions StatusTwo suggestions were made in previous review comments but have not been addressed:
Critical Issues🔴 Security & Cryptography1. Missing SHA-1 Algorithm Justification (CLAUDE.md Requirement)Location: Per CLAUDE.md guidelines, all crypto algorithm usage requires detailed justification. SHA-1 is cryptographically broken for security purposes, but it's correct here because:
Recommendation: Add a comprehensive comment before the SHA-1 usage explaining this rationale. // SHA-1 is used here as required by the HIBP API specification.
// While SHA-1 is cryptographically broken for collision resistance,
// it is safe in this context because:
// 1. HIBP API requires SHA-1 for backwards compatibility across all clients
// 2. The k-anonymity model (sending only 5-char prefix) protects the password
// 3. This is used purely as a hash function for database lookup, not for
// cryptographic security guarantees
// See: https://haveibeenpwned.com/API/v3#PwnedPasswords
let hash = Sha1::digest(password.as_bytes()); 2. URL Sanitization in Error MessagesLocation: Good job using Bugs & Edge Cases
|
Great job! No new security vulnerabilities introduced in this pull request |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #499 +/- ##
==========================================
+ Coverage 78.36% 78.74% +0.38%
==========================================
Files 291 295 +4
Lines 29343 29960 +617
==========================================
+ Hits 22994 23592 +598
- Misses 6349 6368 +19 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
915fe76
to
a10fef6
Compare
Remove performance test
…er in exposed_count
There was a problem hiding this 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.
let http_client = self.client.internal.get_http_client().clone(); | ||
let password_map = password_map.clone(); | ||
let base_url = options | ||
.hibp_base_url | ||
.clone() | ||
.unwrap_or_else(|| HIBP_DEFAULT_BASE_URL.to_string()); | ||
|
||
async move { | ||
if details.password.is_empty() { | ||
// Skip empty passwords, return default risk values | ||
return CipherRisk { | ||
id: details.id, | ||
password_strength: 0, | ||
exposed_result: ExposedPasswordResult::NotChecked, | ||
reuse_count: None, | ||
}; | ||
} | ||
|
||
let password_strength = Self::calculate_password_strength( | ||
&details.password, | ||
details.username.as_deref(), | ||
); | ||
|
||
// Check exposure via HIBP API if enabled | ||
// Capture errors per-cipher instead of propagating them | ||
let exposed_result = if options.check_exposed { | ||
match Self::check_password_exposed(&http_client, &details.password, &base_url) | ||
.await | ||
{ | ||
Ok(count) => ExposedPasswordResult::Found(count), | ||
Err(e) => ExposedPasswordResult::Error(e.to_string()), | ||
} | ||
} else { | ||
ExposedPasswordResult::NotChecked | ||
}; | ||
|
||
// Check reuse from provided map | ||
let reuse_count = if let Some(map) = &password_map { | ||
map.map.get(&details.password).copied() | ||
} else { | ||
None | ||
}; | ||
|
||
CipherRisk { | ||
id: details.id, | ||
password_strength, | ||
exposed_result, | ||
reuse_count, | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⛏️ Could this be moved to a separate function async fn to_cipher_risk(..)
or similar?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in b7d3077
Will this replace the existing |
@Hinton I originally planned on utilizing their implementation, however it is adding Bitwarden specific inputs as it was being used to specifically check the user's Bitwarden password. It also assumed there was always an email (a requirement for Bitwarden accounts). Not opposed to making this more generic so it can be reused by Auth. However, at that point I'm not sure the Vault crate is the proper spot for a generic password strength checking utility, especially if it means the Auth crate would depend on Vault. |
crates/bitwarden-vault/Cargo.toml
Outdated
uuid = { workspace = true } | ||
wasm-bindgen = { workspace = true, optional = true } | ||
wasm-bindgen-futures = { workspace = true, optional = true } | ||
zxcvbn = ">=3.0.1, <4.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: These should be moved to the root workspace and referenced as workspace dependencies since they are consumed by multiple crates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to the root workspace in c039308
crates/bitwarden-vault/src/error.rs
Outdated
/// Error type for cipher risk evaluation operations | ||
#[allow(missing_docs)] | ||
#[bitwarden_error(flat)] | ||
#[derive(Debug, Error)] | ||
pub enum CipherRiskError { | ||
#[error(transparent)] | ||
Reqwest(#[from] reqwest::Error), | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Having a root error.rs
is a bit of an anti pattern we're slowly moving away from. Defining it where it's used will result in less file jumping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! 5d0e1d2
/// | ||
/// Returns `Some(CipherLoginDetails)` if this is a login cipher with a password, | ||
/// otherwise returns `None`. | ||
pub fn to_login_details(&self) -> Option<crate::cipher::cipher_risk::CipherLoginDetails> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Is this used anywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, it was a leftover. Removed d201755
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] | ||
pub struct CipherLoginDetails { | ||
/// Cipher ID to identify which cipher in results. | ||
pub id: Option<CipherId>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Can CipherId
be empty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, it really doesn't make sense for it to ever be null in this context. Made required in 9b8b001
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: This feature seems completely decoupled from ciphers. Maybe move it to a separate dedicated module, and/or crate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, moved to a separate cipher_risk
module in 5d0e1d2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue: We generally only use clients to expose a public interface, this file seems to currently do a lot of various things.
suggestion:
- Extract two sub-modules, HIBP and reuse detection. These can be unit tested in isolation quite nicely which will also significantly decrease the filesize.
- Consider what public interface you need, is it sufficient to just accept a single struct containing a list of ciphers to check? If so everything except this struct and response can be made internal to the module or crate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored the HIBP and password strength into their own sub-modules to reduce the complexity of the cipher_risk_client
. (the reuse detection is fairly small and didn't seem worth the same overhead).
I also cleaned up the public interface to only export the required input/output structs, the error, and the client itself.
|
🎟️ 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
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 confirmedissue 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