High watch CVSS 4.3 chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Transactions Platform
DescriptionIncorrect authorization in Transactions Platform
ComponentTransactions Platform
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker524698525
Fix commitf8b712bd29e7 (chromium/src) +30/-0
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-06-16 (per vendor advisory)
Fix landed2026-07-17 (public git)
Disclosed2026-08-25
Patch gap39 days — fix public in source before the advisory

Background

`AutofillExternalDelegate` (AED)
the browser-side object that receives an Autofill query for a focused form field, shows the suggestion popup (addresses, payment cards and so on) through the AutofillClient, and acts on the suggestion the user accepts.
`last_query_`
AED state recording the form_id (FormGlobalId) and field_id (FieldGlobalId) of the most recent query, plus the field’s datalist options; it determines which form and field the delegate treats as the current target.
`HideSuggestions` / `SuggestionHidingReason::kStaleData`
the client call that closes the open suggestion popup, here with a reason saying the displayed data no longer matches the current context.

Root Cause Analysis

Before the patch, AutofillExternalDelegate::OnQuery overwrote last_query_ with the new form and field IDs without closing a suggestion popup that was already open for a different form or field. A popup built for one query target could therefore stay on screen while the delegate’s notion of the target had silently moved elsewhere, so the visible suggestions and the field they would act on could disagree, which the commit title calls “staleness”.

The patch adds a check at the top of OnQuery: if a previous query exists (last_query_.form_id or last_query_.field_id set) and either ID differs from the incoming form.global_id() or field.global_id(), it calls manager_->client().HideSuggestions(SuggestionHidingReason::kStaleData, std::nullopt) before updating last_query_. A re-query of the same form and field leaves the popup alone, as the new unit test checks. This turns a potential mismatch between what the user sees and approves and what Autofill targets into an invariant: an open popup always belongs to last_query_. The diff does not show the downstream fill or accept path, so the exact misuse of the stale popup is not shown here.

Key insight
OnQuery changed the delegate’s target (last_query_) without invalidating UI that was authorised for the previous target. The fix hides the existing popup whenever the queried form or field identity changes.

Attack Path

  1. Victim opens Autofill suggestions The user focuses a field on a page and Chrome shows an Autofill popup (for example with stored address or payment data) built for that form and field.
  2. Page shifts the query target While the popup is open, page script causes a new Autofill query for a different form or field, for example by moving focus or changing the DOM. The concrete trigger is (inferred), as the patch only shows that OnQuery can run with a different form.global_id() or field.global_id().
  3. Stale popup remains Before the fix, OnQuery updated last_query_ to the new target and left the old popup visible, so the shown suggestions no longer matched the delegate’s current target.
  4. User acceptance applied out of context If the user then selected a suggestion, the action could be associated with a form or field the user did not intend, for example one the page controls more directly, which could expose Autofill data to it (inferred).

Impact Assessment

This is a UI and authorization-consistency bug in browser-process Autofill logic: a web page could keep Autofill suggestions on screen after changing the queried form or field, so a user’s selection could be applied outside the context they approved. It needs only an ordinary web page plus user interaction (an open popup and an accepted suggestion), with no compromised renderer or special flag shown in the diff. Leaking stored payment or address data to an unintended field or frame is (inferred); the advisory states only “Incorrect authorization in Transactions Platform” with no in-the-wild status.

Files Changed

  • components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
  • components/autofill/core/browser/ui/autofill_external_delegate.cc

Audit Directions

  • Target state updated under open UI
    Look for other Autofill and password-manager delegates (PasswordAutofillManager, payments bubbles, AutofillPopupControllerImpl) where focus or query handlers update their current-field state while a popup or bubble from the previous field stays open.
  • Accept handlers trusting mutable context
    Check that suggestion-acceptance paths re-validate that the popup’s originating form and field still match the delegate’s current last_query_, and do not just read whatever target is current at accept time.
  • Identity comparison completeness
    Where staleness is detected by comparing FormGlobalId and FieldGlobalId, confirm that comparisons cover frame tokens and renderer IDs, so a same-shaped form in another frame is not treated as the same target.
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