Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Transactions Platform
DescriptionIncorrect authorization in Transactions Platform
ComponentTransactions Platform
Bug ClassLogic Error
Tracker524698525
Fix commitf8b712bd29e7 (chromium/src) +30/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • components/autofill/core/browser/ui/autofill_external_delegate.cc
  • components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
From f8b712bd29e778755681cc75245a65140b8f4107 Mon Sep 17 00:00:00 2001
From: Jihad Hanna <jihadghanna@google.com>
Date: Fri, 17 Jul 2026 11:55:13 -0700
Subject: [PATCH] Hide Autofill popup on AED::OnQuery() to avoid staleness

Fixed: 524698525, 521951328
Change-Id: I09ae8cd3a11f1d630ee4e8bab278b1342ec822ed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8116769
Commit-Queue: Jihad Hanna <jihadghanna@google.com>
Auto-Submit: Jihad Hanna <jihadghanna@google.com>
Reviewed-by: Jan Keitel <jkeitel@google.com>
Cr-Commit-Position: refs/heads/main@{#1664080}
---

diff --git a/components/autofill/core/browser/ui/autofill_external_delegate.cc b/components/autofill/core/browser/ui/autofill_external_delegate.cc
index f3e996d..b67eaae 100644
--- a/components/autofill/core/browser/ui/autofill_external_delegate.cc
+++ b/components/autofill/core/browser/ui/autofill_external_delegate.cc
@@ -379,6 +379,13 @@
     const FormFieldData& field,
     const gfx::Rect& caret_bounds,
     AutofillSuggestionTriggerSource trigger_source) {
+  if ((last_query_.form_id || last_query_.field_id) &&
+      (last_query_.form_id != form.global_id() ||
+       last_query_.field_id != field.global_id())) {
+    manager_->client().HideSuggestions(SuggestionHidingReason::kStaleData,
+                                       /*product=*/std::nullopt);
+  }
+
   last_query_ = {.form_id = form.global_id(),
                  .field_id = field.global_id(),
                  .field_datalist_options = field.datalist_options()};
diff --git a/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc b/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
index fdb281a..c43c82da 100644
--- a/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
+++ b/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
@@ -4278,6 +4278,29 @@
       SuggestionPosition{.multi_index = {0}});
 }
 
+// Tests that OnQuery() hides any open suggestions with kStaleData only when the
+// queried form or field changes from the previous query.
+TEST_F(AutofillExternalDelegateTest,
+       ExternalDelegateHidesSuggestionsWhenQueryTargetChanges) {
+  IssueOnQuery();
+
+  // Re-querying the exact same form and field should not hide suggestions.
+  EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);
+  external_delegate().OnQuery(queried_form(), queried_field(), gfx::Rect(),
+                              kDefaultSuggestionTriggerSource);
+
+  // Querying a different field (or form) should hide suggestions with
+  // kStaleData.
+  EXPECT_CALL(
+      autofill_client(),
+      HideSuggestions(SuggestionHidingReason::kStaleData, Eq(std::nullopt)));
+  FormData new_form = test::GetFormData(
+      {.fields = {{.role = NAME_FULL, .autocomplete_attribute = "name"}}});
+  autofill_manager().OnFormsSeen({new_form}, {});
+  external_delegate().OnQuery(new_form, new_form.fields()[0], gfx::Rect(),
+                              kDefaultSuggestionTriggerSource);
+}
+
 }  // namespace
 
 }  // namespace autofill
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc b/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
index fdb281a..c43c82da 100644
--- a/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
+++ b/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
@@ -4278,6 +4278,29 @@
       SuggestionPosition{.multi_index = {0}});
 }
 
+// Tests that OnQuery() hides any open suggestions with kStaleData only when the
+// queried form or field changes from the previous query.
+TEST_F(AutofillExternalDelegateTest,
+       ExternalDelegateHidesSuggestionsWhenQueryTargetChanges) {
+  IssueOnQuery();
+
+  // Re-querying the exact same form and field should not hide suggestions.
+  EXPECT_CALL(autofill_client(), HideSuggestions).Times(0);
+  external_delegate().OnQuery(queried_form(), queried_field(), gfx::Rect(),
+                              kDefaultSuggestionTriggerSource);
+
+  // Querying a different field (or form) should hide suggestions with
+  // kStaleData.
+  EXPECT_CALL(
+      autofill_client(),
+      HideSuggestions(SuggestionHidingReason::kStaleData, Eq(std::nullopt)));
+  FormData new_form = test::GetFormData(
+      {.fields = {{.role = NAME_FULL, .autocomplete_attribute = "name"}}});
+  autofill_manager().OnFormsSeen({new_form}, {});
+  external_delegate().OnQuery(new_form, new_form.fields()[0], gfx::Rect(),
+                              kDefaultSuggestionTriggerSource);
+}
+
 }  // namespace
 
 }  // namespace autofill
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential cross-origin Autofill bypass and data leak via non-focusable field desynchronization

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: A logic flaw in AutofillExternalDelegate potentially allows a compromised main frame to bypass cross-origin IsSafeToFill boundaries and exfiltrate sensitive payment card or address profile data. By triggering an Autofill suggestions query on a non-focusable field while a legitimate suggestions popup is already visible, the attacker can desynchronize the internal Autofill state. Consequently, when the user selects a suggestion, the decrypted sensitive data is filled into the attacker-controlled origin’s fields.

Affected files:

  • components/autofill/core/browser/ui/autofill_external_delegate.cc
  • components/autofill/core/browser/foundations/browser_autofill_manager.cc
  • chrome/browser/ui/autofill/autofill_popup_controller_impl.cc

Estimated timestamp from git blame: 2026-04-13

Description

A logical vulnerability exists in the browser’s Autofill suggestion pipeline. Specifically, AutofillExternalDelegate::OnQuery() unconditionally updates its active state (query_form_ and query_field_) before checking if the queried field is eligible for suggestions.

If the field is subsequently determined to be non-focusable inside AttemptToDisplayAutofillSuggestions(), the browser triggers an early return without hiding the currently displayed popup. This leads to a permanent state desynchronization where a previously opened, legitimate credit card or address suggestions popup remains visible to the user, but the browser’s active query_field_ is redirected to the attacker’s origin.

Because of this desynchronization, when the user selects a suggestion from the popup, the browser routes the decrypted data (such as credit card PAN or CVC) to the attacker-controlled fields under the assumption that the trigger origin matches the target origin (IsSafeToFill boundary check passes).

Impact

An attacker who has compromised the renderer process of a main frame (origin https://A) that embeds a legitimate checkout frame (origin https://M) could potentially exfiltrate full decrypted credit card numbers (PAN), expiration dates, and CVCs. The attack requires only a single normal user click on a legitimately triggered Autofill popup, making it highly reliable and indistinguishable from normal user behavior.

Potential Step-by-Step Attack Scenario

Note: Our analysis is based on static and dynamic code review; our tooling does not currently have the capability to run a live exploit in a running instance of Chrome.

  1. A compromised main frame https://A embeds a legitimate cross-origin payment iframe https://M.
  2. The user interacts with the credit card number input field in the checkout iframe https://M.
  3. Renderer M triggers a legitimate Autofill query. The browser’s root BrowserAutofillManager invokes AutofillExternalDelegate::OnQuery(), setting:
    • query_form_ = the flattened form structure
    • query_field_ = the focused field in https://M
  4. The browser displays the legitimate credit card suggestions popup over M’s field in the UI.
  5. The compromised root frame https://A programmatically sends a forged mojom::AutofillDriver::AskForValuesToFill IPC for a hidden field field_A inside its own frame, explicitly setting field_A.is_focusable = false and field_A.datalist_options = {}.
  6. In the browser process, BrowserAutofillManager::OnAskForValuesToFillImpl is invoked and calls external_delegate_->OnQuery(form, field_A, ...).
  7. OnQuery() unconditionally overwrites the active delegate state: query_field_ = field_A (origin = https://A, is_focusable = false).
  8. Because the datalist options list is empty, UpdateAutofillDataListValues does not dismiss or hide the existing credit card suggestions popup.
  9. When suggestions are returned for field_A, the delegate executes AttemptToDisplayAutofillSuggestions() and hits the focusability guard:
    // components/autofill/core/browser/ui/autofill_external_delegate.cc:414
    if (!query_field_.is_focusable() || !manager_->driver().CanShowAutofillUi()) {
      return;
    }
    
    The method immediately returns without calling HideSuggestions(), leaving the legitimate credit card popup showing on screen.
  10. The user clicks a card suggestion in the still-visible popup.
  11. The click is handled by AutofillPopupControllerImpl::AcceptSuggestion() and routed to delegate_->DidAcceptSuggestion(), which retrieves the active query_field_ (which is now field_A of origin https://A).
  12. FormFiller::FillOrPreviewForm() is invoked. During safety verification, AutofillDriverRouter::IsSafeToFill() evaluates:
    // components/autofill/core/browser/foundations/autofill_driver_router.cc:119
    return field.origin() == trigger_origin || ...
    
    Since both field (the target hidden field) and trigger_origin (the overwritten query_field_ origin) belong to https://A, the safety check evaluates to true (safe).
  13. The fully decrypted credit card credentials (PAN and CVC) are filled into frame A’s hidden fields via ApplyFormAction and exfiltrated by the attacker’s renderer script.

Suggested Fix

To remediate this logical vulnerability, we recommend ensuring that the suggestions popup is explicitly dismissed if a query target is updated to an ineligible or non-focusable field.

Specifically, in AutofillExternalDelegate::AttemptToDisplayAutofillSuggestions() (at components/autofill/core/browser/ui/autofill_external_delegate.cc:414), instead of a bare return, the delegate should hide any open popup first:

  if (!query_field_.is_focusable() || !manager_->driver().CanShowAutofillUi()) {
    manager_->client().HideSuggestions(SuggestionHidingReason::kNoSuggestions,
                                       /*product=*/std::nullopt);
    return;
  }

Alternatively, validation checks (such as focusability) should be performed before updating query_form_ and query_field_ inside BrowserAutofillManager::OnAskForValuesToFillImpl() or prior to dispatching OnQuery() to the delegate.

Evaluated with Chrome root at commit: 70c6813870b6701fa16670076bf633ee6c3a439f


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
Links in the report