CVE-2026-95312
Overview
Background
- `PasswordAutofillManager`
- The browser-process component that manages password autofill suggestions and drives previews and fills in the renderer.
- `PreviewSuggestion`
- A
password_manager_driver_call that sends a username and a masked password string to the renderer to preview a suggestion before the user confirms filling it. - Grouped affiliation
- A property (
is_grouped_affiliation) marking a credential that was matched via affiliation grouping rather than an exact origin match, meaning it can belong to a different (cross-domain) site. - Preview
- A pre-confirmation UI hint sent to the renderer showing the candidate username and a masked password, displayed before the user has agreed to fill.
Root Cause Analysis
Both preview code paths in password_autofill_manager.cc protected the password by always sending a fixed 8-asterisk mask (std::u16string(8, '*')) for cross-domain or grouped credentials, but they still passed the real payload.username / username to PreviewSuggestion and thus to the renderer. The vulnerable path in DidSelectSuggestion (backup credentials) previewed whenever payload.is_cross_domain was true, and the path in the username-based preview helper previewed whenever password_and_metadata->is_grouped_affiliation was true, in both cases leaking the full username of a credential belonging to another domain before any user confirmation. The invariant violated is that sensitive credential data for a grouped/cross-domain match must not reach the renderer until the user explicitly confirms the fill.
The fix makes both paths return early (return; and return false;) instead of previewing, so no username or mask is sent to the renderer for cross-domain/grouped credentials.
Attack Path
- Set up a grouped credential
An attacker relies on the victim having a saved credential that is affiliation-grouped or backup-flagged so it surfaces as a cross-domain suggestion (
is_cross_domain/is_grouped_affiliationtrue) on an attacker-influenced page. - Trigger the preview
The victim hovers or selects the autofill suggestion, invoking
DidSelectSuggestionor the username preview helper, which callsPreviewSuggestionbefore any confirmation. - Leak the username to the renderer The full username for the cross-domain credential is sent to the renderer as part of the preview, exposing it to page-controlled or compromised renderer code without the victim confirming the fill.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/password_manager/core/browser/password_autofill_manager.cc |
modified | |
TEST_Fcomponents/password_manager/core/browser/password_autofill_manager_unittest.cc |
modified |
Files Changed
components/password_manager/core/browser/password_autofill_manager_unittest.cccomponents/password_manager/core/browser/password_autofill_manager.cc
Audit Directions
- Mask-but-not-suppressLook for code that masks one field (e.g., password length) of a sensitive record while still forwarding another identifying field (e.g., username) across a trust boundary.
- Pre-confirmation renderer sendsAudit every
PreviewSuggestionand similar pre-confirmation data flow to the renderer for cross-domain or grouped credentials that should require explicit user confirmation first. - Cross-domain flag handlingReview all branches keyed on
is_cross_domainoris_grouped_affiliationto confirm they suppress data exposure rather than merely transforming it.
Patch
From 18195740b3c50c999f042117abf570b8bf4c68c2 Mon Sep 17 00:00:00 2001
From: dresslerd <dresslerd@google.com>
Date: Tue, 18 Aug 2026 06:11:28 -0700
Subject: [PATCH] Prevent showing a username preview for grouped credentials.
This code already used to protect the length of the password (always
using 8 asterisks), but the full username was still sent to the renderer
for preview before the user was asked to confirm this. This just gets
rid of the preview entirely for grouped credentials.
Fixed: 547832510
Change-Id: Iabcb4fee0b66b8136205ae6cb8f6eb226a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8255316
Reviewed-by: Viktor Semeniuk <vsemeniuk@google.com>
Commit-Queue: Daniel Dressler <dresslerd@google.com>
Reviewed-by: Timofey Chudakov <tchudakov@google.com>
Cr-Commit-Position: refs/heads/main@{#1681319}
---
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index 47aea46..fb30c01 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -298,8 +298,12 @@
->IsBiometricAuthenticationBeforeFillingEnabled()) {
return;
}
- size_t password_length =
- payload.is_cross_domain ? 8 : payload.backup_password.value().length();
+ if (payload.is_cross_domain) {
+ // Do not preview backup credentials marked as cross-domain to avoid
+ // leaking sensitive data to the renderer without user confirmation.
+ return;
+ }
+ size_t password_length = payload.backup_password.value().length();
password_manager_driver_->PreviewSuggestion(
payload.username, std::u16string(password_length, '*'));
return;
@@ -856,10 +860,13 @@
}
if (const autofill::PasswordAndMetadata* password_and_metadata =
GetPasswordAndMetadataForUsername(username, type)) {
- size_t password_length =
- password_and_metadata->is_grouped_affiliation
- ? 8
- : password_and_metadata->password_value.length();
+ if (password_and_metadata->is_grouped_affiliation) {
+ // Do not preview grouped credentials (which can be cross-domain)
+ // to avoid leaking sensitive data to the renderer without user
+ // confirmation.
+ return false;
+ }
+ size_t password_length = password_and_metadata->password_value.length();
password_manager_driver_->PreviewSuggestion(
username, std::u16string(password_length, '*'));
return true;
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index 15933d0..a2250a7 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -489,15 +489,15 @@
password_autofill_manager_->PreviewSuggestionForTest(test_username_));
}
-TEST_F(PasswordAutofillManagerTest, PreviewGroupedSuggestion) {
+TEST_F(PasswordAutofillManagerTest, DoNotPreviewGroupedSuggestion) {
fill_data().preferred_login.is_grouped_affiliation = true;
TestPasswordManagerClient client;
InitializePasswordAutofillManager(&client, nullptr);
- // Grouped suggestions should be masked using an 8-character long mask.
- EXPECT_CALL(*client.mock_driver(),
- PreviewSuggestion(test_username_, std::u16string(8, '*')));
- EXPECT_TRUE(
+ // Grouped suggestions should not be previewed to avoid leaking data to the
+ // renderer without user confirmation.
+ EXPECT_CALL(*client.mock_driver(), PreviewSuggestion).Times(0);
+ EXPECT_FALSE(
password_autofill_manager_->PreviewSuggestionForTest(test_username_));
}
@@ -2507,7 +2507,7 @@
}
TEST_F(PasswordAutofillManagerTest,
- PasswordRecoveryFlow_PreviewGroupedBackupSuggestion) {
+ PasswordRecoveryFlow_DoNotPreviewGroupedBackupSuggestion) {
TestPasswordManagerClient client;
InitializePasswordAutofillManager(&client, nullptr);
const Suggestion::PasswordSuggestionDetails payload(
@@ -2516,9 +2516,9 @@
const Suggestion suggestion = autofill::test::CreateAutofillSuggestion(
autofill::SuggestionType::kBackupPasswordEntry, test_username_, payload);
- // Grouped backup suggestions should be masked using an 8-character long mask.
- EXPECT_CALL(*client.mock_driver(),
- PreviewSuggestion(test_username_, std::u16string(8, '*')));
+ // Grouped backup suggestions should not be previewed to avoid leaking data
+ // without user confirmation.
+ EXPECT_CALL(*client.mock_driver(), PreviewSuggestion).Times(0);
password_autofill_manager_->DidSelectSuggestion(suggestion);
testing::Mock::VerifyAndClearExpectations(client.mock_driver());
Regression Test / PoC
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index 15933d0..a2250a7 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -489,15 +489,15 @@
password_autofill_manager_->PreviewSuggestionForTest(test_username_));
}
-TEST_F(PasswordAutofillManagerTest, PreviewGroupedSuggestion) {
+TEST_F(PasswordAutofillManagerTest, DoNotPreviewGroupedSuggestion) {
fill_data().preferred_login.is_grouped_affiliation = true;
TestPasswordManagerClient client;
InitializePasswordAutofillManager(&client, nullptr);
- // Grouped suggestions should be masked using an 8-character long mask.
- EXPECT_CALL(*client.mock_driver(),
- PreviewSuggestion(test_username_, std::u16string(8, '*')));
- EXPECT_TRUE(
+ // Grouped suggestions should not be previewed to avoid leaking data to the
+ // renderer without user confirmation.
+ EXPECT_CALL(*client.mock_driver(), PreviewSuggestion).Times(0);
+ EXPECT_FALSE(
password_autofill_manager_->PreviewSuggestionForTest(test_username_));
}
@@ -2507,7 +2507,7 @@
}
TEST_F(PasswordAutofillManagerTest,
- PasswordRecoveryFlow_PreviewGroupedBackupSuggestion) {
+ PasswordRecoveryFlow_DoNotPreviewGroupedBackupSuggestion) {
TestPasswordManagerClient client;
InitializePasswordAutofillManager(&client, nullptr);
const Suggestion::PasswordSuggestionDetails payload(
@@ -2516,9 +2516,9 @@
const Suggestion suggestion = autofill::test::CreateAutofillSuggestion(
autofill::SuggestionType::kBackupPasswordEntry, test_username_, payload);
- // Grouped backup suggestions should be masked using an 8-character long mask.
- EXPECT_CALL(*client.mock_driver(),
- PreviewSuggestion(test_username_, std::u16string(8, '*')));
+ // Grouped backup suggestions should not be previewed to avoid leaking data
+ // without user confirmation.
+ EXPECT_CALL(*client.mock_driver(), PreviewSuggestion).Times(0);
password_autofill_manager_->DidSelectSuggestion(suggestion);
testing::Mock::VerifyAndClearExpectations(client.mock_driver());
Original Bug Report
Potential cross-domain username leak for weakly affiliated credentials
Security Bug
Important: Please do not change the component of this bug manually.
Please READ THIS FAQ before filing a bug: https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md
Please see the following link for instructions on filing security bugs: https://www.chromium.org/Home/chromium-security/reporting-security-bugs
Reports may be eligible for reward payments under the Chrome VRP: https://g.co/chrome/vrp
NOTE: Security bugs are normally made public once a fix has been widely deployed.
VULNERABILITY DETAILS Similar to b/522545249, the suggested password popup shows the preview of usernames for grouped/weakly affiliated credentials. These credentials can be cross-domain, and the preview sends them to the renderer.
Note: The preview of a grouped password always shows exactly 8 asterisks, so these are not leaked. That was done in b/521476960.
Code location and presumably a quick fix is in https://crrev.com/c/8255316. This just hides the cross-domain preview entirely, not just the password, similar to b/522545249.
VERSION Chrome Version: 151.0.0.0 stable, code is older, but the password suggestions based on grouped affiliations only launched recently on July 29th 2026. Operating System: Desktop
REPRODUCTION CASE Please include a demonstration of the security bug, such as an attached HTML or binary file that reproduces the bug when loaded in Chrome. PLEASE make the file as small as possible and remove any content not required to demonstrate the bug, or any personal or confidential information.
Please attach files directly, not in zip or other archive formats, and if you’ve created a demonstration site please also attach the files needed to reproduce the demonstration locally.
FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION Type of crash: [tab, browser, etc.] Crash State: [see link above: stack trace with symbols, registers, exception record] Client ID (if relevant): [see link above]
CREDIT INFORMATION Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: [goes here]