Improve credential autofill matcher for mobile apps (#1477)
This commit is contained in:
committed by
Leendert de Borst
parent
36b3a19d13
commit
433098d89b
@@ -279,7 +279,49 @@ pub fn filter_credentials(input: CredentialMatcherInput) -> CredentialMatcherOut
|
||||
}
|
||||
}
|
||||
|
||||
// No matches found in Priority 2 or Priority 3
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// PRIORITY 3b: URL Word / Item Name Fallback
|
||||
// No domain or page title matches found - try matching words extracted
|
||||
// from the current URL against item names for credentials without URLs.
|
||||
// Same anti-phishing rule: only credentials with NO URLs are eligible.
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
let url_words = extract_words(¤t_url);
|
||||
|
||||
if !url_words.is_empty() {
|
||||
let url_word_match_ids: Vec<String> = credentials
|
||||
.iter()
|
||||
.filter(|cred| {
|
||||
// SECURITY: Skip credentials that have URLs defined
|
||||
if !cred.item_urls.is_empty()
|
||||
&& cred.item_urls.iter().any(|u| !u.is_empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(item_name) = &cred.item_name {
|
||||
let cred_name_words = extract_words(item_name);
|
||||
|
||||
// Match only complete words, not substrings
|
||||
url_words.iter().any(|url_word| {
|
||||
cred_name_words.iter().any(|cred_word| url_word == cred_word)
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.map(|cred| cred.id.clone())
|
||||
.take(3)
|
||||
.collect();
|
||||
|
||||
if !url_word_match_ids.is_empty() {
|
||||
return CredentialMatcherOutput {
|
||||
matched_ids: url_word_match_ids,
|
||||
matched_priority: 3,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// No matches found in Priority 2, 3, or 3b
|
||||
return CredentialMatcherOutput {
|
||||
matched_ids: vec![],
|
||||
matched_priority: 0,
|
||||
|
||||
@@ -725,3 +725,35 @@ fn test_items_without_urls_not_matched_when_url_match_exists() {
|
||||
assert_eq!(matches.len(), 1, "Should only return URL match, not title matches");
|
||||
assert_eq!(matches[0].item_name.as_deref(), Some("blabla service"));
|
||||
}
|
||||
|
||||
/// [#35] - Items WITHOUT URLs should match via URL-derived word matching on item name
|
||||
#[test]
|
||||
fn test_items_without_urls_matched_by_url_domain_words() {
|
||||
let credentials = vec![
|
||||
create_test_credential("Test Dumpert", "", "user@dumpert.nl"),
|
||||
create_test_credential("Some Other Item", "", "user@other.com"),
|
||||
];
|
||||
|
||||
// When visiting dumpert.nl with no page title, the credential named "Test Dumpert"
|
||||
// should still match because "dumpert" from the URL matches "dumpert" in the item name
|
||||
let matches = filter(credentials, "https://www.dumpert.nl", "");
|
||||
|
||||
assert_eq!(matches.len(), 1, "Should match credential by URL-derived word against item name");
|
||||
assert_eq!(matches[0].item_name.as_deref(), Some("Test Dumpert"));
|
||||
}
|
||||
|
||||
/// [#36] - URL-derived word matching should NOT match credentials that have URLs defined
|
||||
#[test]
|
||||
fn test_url_word_matching_skips_credentials_with_urls() {
|
||||
let credentials = vec![
|
||||
// This credential HAS a URL (different domain) - should NOT match via name
|
||||
create_test_credential("Test Dumpert", "https://other-site.com", "user@dumpert.nl"),
|
||||
// This credential has NO URL - should match via name
|
||||
create_test_credential("Dumpert Account", "", "user@dumpert.nl"),
|
||||
];
|
||||
|
||||
let matches = filter(credentials, "https://www.dumpert.nl", "");
|
||||
|
||||
assert_eq!(matches.len(), 1, "Should only match the credential without URLs");
|
||||
assert_eq!(matches[0].item_name.as_deref(), Some("Dumpert Account"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user