Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Passwords
DescriptionInappropriate implementation in Passwords
ComponentPasswords
Bug ClassLogic Error
Tracker523699355
Fix commit0be87b4e25bf (chromium/src) +58/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-23

Changed Functions

FunctionChangeNotes
if
components/autofill/core/common/password_form_fill_data.cc
modified
for
components/autofill/core/common/password_form_fill_data.cc
modified
TEST
components/password_manager/core/browser/password_form_filling_unittest.cc
modified

Files Changed

  • components/autofill/core/common/password_form_fill_data.cc
  • components/password_manager/core/browser/password_form_filling_unittest.cc
From 0be87b4e25bf905accc522efa61a431de40a2ae9 Mon Sep 17 00:00:00 2001
From: Anna Tsvirchkova <atsvirchkova@google.com>
Date: Thu, 18 Jun 2026 05:48:25 -0700
Subject: [PATCH] Fix leak of grouped credentials to renderer

If there are multiple matches e. g. the preferred match is an exact
match and the additional one is a grouped match (or PSL), there is a
possibility that the additional one will be leaked to the renderer
process though the filling on page load flow. On the rederer side, all
matches with non-empty realm are not used for filling anyway
(https://source.chromium.org/chromium/chromium/src/+/main:components/autofill/content/renderer/password_autofill_agent.cc;l=229;bpv=0;bpt=1),
so this fix clears the passwords for non-empty realm credentials to
avoid the leak.

Bug: 523699355
Change-Id: I341cbb03752537c1ebc67b005d29de065eeca53c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7941892
Commit-Queue: Anna Tsvirchkova <atsvirchkova@google.com>
Reviewed-by: Maria Kazinova <kazinova@google.com>
Cr-Commit-Position: refs/heads/main@{#1648968}
---

diff --git a/components/autofill/core/common/password_form_fill_data.cc b/components/autofill/core/common/password_form_fill_data.cc
index 2fcba04..b9e9488 100644
--- a/components/autofill/core/common/password_form_fill_data.cc
+++ b/components/autofill/core/common/password_form_fill_data.cc
@@ -95,14 +95,19 @@
   // field), credentials from |additional_logins| could be used for filling
   // on load. So in case of filling on load nor |password_field| nor
   // |additional_logins| can't be cleared
-  bool is_fallback = data.password_element_renderer_id.is_null();
-  if (!data.wait_for_username && !is_fallback) {
-    return data;
-  }
+  bool no_fill_on_page_load =
+      data.wait_for_username || data.password_element_renderer_id.is_null();
   PasswordFormFillData result(data);
-  result.preferred_login.password_value.clear();
+  if (no_fill_on_page_load) {
+    result.preferred_login.password_value.clear();
+  }
   for (auto& credentials : result.additional_logins) {
-    credentials.password_value.clear();
+    // Realm is only set for credentials initially associated with a different
+    // domain. For security reasons, such credentials are never filled on page
+    // load and should not be passed to the renderer.
+    if (!credentials.realm.empty() || no_fill_on_page_load) {
+      credentials.password_value.clear();
+    }
   }
   return result;
 }
diff --git a/components/password_manager/core/browser/password_form_filling_unittest.cc b/components/password_manager/core/browser/password_form_filling_unittest.cc
index 5a218471..949d150 100644
--- a/components/password_manager/core/browser/password_form_filling_unittest.cc
+++ b/components/password_manager/core/browser/password_form_filling_unittest.cc
@@ -999,4 +999,51 @@
   EXPECT_TRUE(result.preferred_login.is_grouped_affiliation);
 }
 
+// Tests that `MaybeClearPasswordValues` clears the passwords of non-exact
+// matches (such as grouped credentials) when page-load filling is allowed, and
+// clears all passwords when `wait_for_username` is true.
+TEST(PasswordFormFillDataTest, MaybeClearPasswordValues) {
+  using autofill::PasswordAndMetadata;
+
+  // Create a PasswordFormFillData simulating:
+  // - preferred_login: exact match (has password)
+  // - additional_logins:
+  //   - exact match (realm is empty, has password)
+  //   - grouped match (realm is non-empty, has password)
+  PasswordFormFillData data;
+  data.preferred_login.password_value = u"preferred_password";
+  data.password_element_renderer_id =
+      FieldRendererId(123);  // Non-fallback form
+
+  PasswordAndMetadata exact_additional;
+  exact_additional.realm = "";
+  exact_additional.password_value = u"exact_password";
+  data.additional_logins.push_back(exact_additional);
+
+  PasswordAndMetadata grouped_additional;
+  grouped_additional.realm = "https://grouped.com";
+  grouped_additional.password_value = u"grouped_password";
+  data.additional_logins.push_back(grouped_additional);
+
+  // Scenario 1: wait_for_username = false (allow auto-fill on page load)
+  {
+    data.wait_for_username = false;
+    PasswordFormFillData result = autofill::MaybeClearPasswordValues(data);
+
+    EXPECT_EQ(u"preferred_password", result.preferred_login.password_value);
+    EXPECT_EQ(result.additional_logins[0].password_value, u"exact_password");
+    EXPECT_TRUE(result.additional_logins[1].password_value.empty());
+  }
+
+  // Scenario 2: wait_for_username = true (wait for user interaction)
+  {
+    data.wait_for_username = true;
+    PasswordFormFillData result = autofill::MaybeClearPasswordValues(data);
+
+    EXPECT_TRUE(result.preferred_login.password_value.empty());
+    EXPECT_TRUE(result.additional_logins[0].password_value.empty());
+    EXPECT_TRUE(result.additional_logins[1].password_value.empty());
+  }
+}
+
 }  // namespace password_manager
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/password_manager/core/browser/password_form_filling_unittest.cc b/components/password_manager/core/browser/password_form_filling_unittest.cc
index 5a218471..949d150 100644
--- a/components/password_manager/core/browser/password_form_filling_unittest.cc
+++ b/components/password_manager/core/browser/password_form_filling_unittest.cc
@@ -999,4 +999,51 @@
   EXPECT_TRUE(result.preferred_login.is_grouped_affiliation);
 }
 
+// Tests that `MaybeClearPasswordValues` clears the passwords of non-exact
+// matches (such as grouped credentials) when page-load filling is allowed, and
+// clears all passwords when `wait_for_username` is true.
+TEST(PasswordFormFillDataTest, MaybeClearPasswordValues) {
+  using autofill::PasswordAndMetadata;
+
+  // Create a PasswordFormFillData simulating:
+  // - preferred_login: exact match (has password)
+  // - additional_logins:
+  //   - exact match (realm is empty, has password)
+  //   - grouped match (realm is non-empty, has password)
+  PasswordFormFillData data;
+  data.preferred_login.password_value = u"preferred_password";
+  data.password_element_renderer_id =
+      FieldRendererId(123);  // Non-fallback form
+
+  PasswordAndMetadata exact_additional;
+  exact_additional.realm = "";
+  exact_additional.password_value = u"exact_password";
+  data.additional_logins.push_back(exact_additional);
+
+  PasswordAndMetadata grouped_additional;
+  grouped_additional.realm = "https://grouped.com";
+  grouped_additional.password_value = u"grouped_password";
+  data.additional_logins.push_back(grouped_additional);
+
+  // Scenario 1: wait_for_username = false (allow auto-fill on page load)
+  {
+    data.wait_for_username = false;
+    PasswordFormFillData result = autofill::MaybeClearPasswordValues(data);
+
+    EXPECT_EQ(u"preferred_password", result.preferred_login.password_value);
+    EXPECT_EQ(result.additional_logins[0].password_value, u"exact_password");
+    EXPECT_TRUE(result.additional_logins[1].password_value.empty());
+  }
+
+  // Scenario 2: wait_for_username = true (wait for user interaction)
+  {
+    data.wait_for_username = true;
+    PasswordFormFillData result = autofill::MaybeClearPasswordValues(data);
+
+    EXPECT_TRUE(result.preferred_login.password_value.empty());
+    EXPECT_TRUE(result.additional_logins[0].password_value.empty());
+    EXPECT_TRUE(result.additional_logins[1].password_value.empty());
+  }
+}
+
 }  // namespace password_manager
Loading diff…

Original Bug Report

reported by rj...@google.com

Cross-domain password leak to compromised renderer via grouped affiliations

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: When the kPasswordFormGroupedAffiliations feature is enabled, a compromised renderer can extract cleartext cross-domain passwords without user interaction. This occurs because the logic determining whether to scrub passwords before IPC evaluates only the preferred match, failing to scrub grouped cross-domain matches in the additional_logins list if an exact match is also present.

Affected files:

  • components/autofill/core/common/password_form_fill_data.cc
  • components/password_manager/core/browser/password_form_filling.cc
  • components/password_manager/core/browser/password_form_manager.cc

Estimated timestamp from git blame: Unknown (Google3 checkout)

Overview

There is a potential Site Isolation bypass regarding cross-domain grouped affiliations. When kPasswordFormGroupedAffiliations is enabled, a compromised renderer process can extract cleartext passwords for affiliated cross-domain sites directly from memory without any user interaction, provided the user also has an exact match credential for the compromised origin.

Background

When Chrome fills a password form, it sends a PasswordFormFillData structure to the renderer. For lower-trust credentials (like PSL, affiliated, or grouped matches), Chrome is designed to wait for explicit user selection before providing the cleartext password. This is enforced by setting wait_for_username = true, which tells autofill::MaybeClearPasswordValues (in components/autofill/core/common/password_form_fill_data.cc) to scrub the passwords before sending the data via Mojo IPC (PasswordAutofillAgent::ApplyFillDataOnParsingCompletion).

The Flaw

The logic determining the wait_for_username flag is flawed when multiple credentials of varying trust levels are present.

In components/password_manager/core/browser/password_form_filling.cc, the function SendFillInformationToRenderer determines wait_for_username based strictly on the preferred_match (the highest priority credential):

  } else if (preferred_match &&
             GetMatchType(*preferred_match) == GetLoginMatchType::kGrouped) {
    wait_for_username_reason = WaitForUsernameReason::kGroupedMatch;
  }

If the preferred_match is an exact match for the current domain, wait_for_username evaluates to false (on standard desktop platforms where kFillOnAccountSelect is disabled).

Concurrently, password_manager_util::FindBestMatches allows grouped cross-domain credentials to be included alongside the exact match if they have different usernames. These grouped credentials are placed into the additional_logins array of the PasswordFormFillData structure by CreatePasswordFormFillData.

Because wait_for_username is false, autofill::MaybeClearPasswordValues returns early, failing to scrub the cross-domain cleartext passwords from the additional_logins array. The data is then serialized and sent to the renderer process.

Potential Attacker Steps

(Note: These are suggested steps based on static analysis; our tooling has not yet executed a live Proof of Concept.)

  1. The victim saves an exact match credential for domain-a.com (username “userA”) and a grouped affiliated credential for domain-b.com (username “userB”).
  2. An attacker discovers an independent memory corruption vulnerability (e.g., in V8) and gains arbitrary code execution in the renderer process for domain-a.com.
  3. The victim visits the attacker-controlled domain-a.com, which contains a standard password form.
  4. The browser processes the form, determines wait_for_username = false based on the exact match, and sends both the exact match password and the grouped cross-domain password over IPC to the renderer.
  5. The attacker dumps the additional_logins list from the PasswordAutofillAgent’s memory, successfully stealing the domain-b.com password without user interaction.

Suggested Fix

Modify autofill::MaybeClearPasswordValues (in components/autofill/core/common/password_form_fill_data.cc) so that it does not solely rely on the global wait_for_username flag. Instead, it should evaluate each credential in additional_logins individually. Any credential flagged with is_grouped_affiliation (or generally any non-exact match type) should have its cleartext password scrubbed until the user explicitly selects it from the UI dropdown.

Evaluated with Chrome root at commit: 65b3256311f3ab6fb9870eaa522de7e6dd2663bb


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker