CVE-2026-79082
Overview
Files Changed
components/autofill/core/browser/ui/autofill_external_delegate.cccomponents/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
Patch
From f8b712bd29e778755681cc75245a65140b8f4107 Mon Sep 17 00:00:00 2001 From: Jihad Hanna <[email protected]> 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 <[email protected]> Auto-Submit: Jihad Hanna <[email protected]> Reviewed-by: Jan Keitel <[email protected]> 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
Regression Test / PoC
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
Original Bug Report
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.cccomponents/autofill/core/browser/foundations/browser_autofill_manager.ccchrome/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.
- A compromised main frame
https://Aembeds a legitimate cross-origin payment iframehttps://M. - The user interacts with the credit card number input field in the checkout iframe
https://M. - Renderer
Mtriggers a legitimate Autofill query. The browser’s rootBrowserAutofillManagerinvokesAutofillExternalDelegate::OnQuery(), setting:query_form_= the flattened form structurequery_field_= the focused field inhttps://M
- The browser displays the legitimate credit card suggestions popup over
M’s field in the UI. - The compromised root frame
https://Aprogrammatically sends a forgedmojom::AutofillDriver::AskForValuesToFillIPC for a hidden fieldfield_Ainside its own frame, explicitly settingfield_A.is_focusable = falseandfield_A.datalist_options = {}. - In the browser process,
BrowserAutofillManager::OnAskForValuesToFillImplis invoked and callsexternal_delegate_->OnQuery(form, field_A, ...). OnQuery()unconditionally overwrites the active delegate state:query_field_ = field_A(origin =https://A,is_focusable = false).- Because the datalist options list is empty,
UpdateAutofillDataListValuesdoes not dismiss or hide the existing credit card suggestions popup. - When suggestions are returned for
field_A, the delegate executesAttemptToDisplayAutofillSuggestions()and hits the focusability guard:The method immediately returns without calling// components/autofill/core/browser/ui/autofill_external_delegate.cc:414 if (!query_field_.is_focusable() || !manager_->driver().CanShowAutofillUi()) { return; }HideSuggestions(), leaving the legitimate credit card popup showing on screen. - The user clicks a card suggestion in the still-visible popup.
- The click is handled by
AutofillPopupControllerImpl::AcceptSuggestion()and routed todelegate_->DidAcceptSuggestion(), which retrieves the activequery_field_(which is nowfield_Aof originhttps://A). FormFiller::FillOrPreviewForm()is invoked. During safety verification,AutofillDriverRouter::IsSafeToFill()evaluates:Since both// components/autofill/core/browser/foundations/autofill_driver_router.cc:119 return field.origin() == trigger_origin || ...field(the target hidden field) andtrigger_origin(the overwrittenquery_field_origin) belong tohttps://A, the safety check evaluates totrue(safe).- The fully decrypted credit card credentials (PAN and CVC) are filled into frame
A’s hidden fields viaApplyFormActionand 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.