Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Autofill
DescriptionInappropriate implementation in Autofill
ComponentAutofill
Bug ClassLogic Error
Tracker523229759
Fix commit99815e08c604 (chromium/src) +313/-161
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
components/autofill/core/browser/foundations/browser_autofill_manager.cc
modified
TEST_F
components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
modified

Files Changed

  • components/autofill/core/browser/foundations/browser_autofill_manager.cc
  • components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
  • components/autofill/core/browser/single_field_fillers/autocomplete/autocomplete_history_manager.cc
From 99815e08c6040f6f7f524da20e3af56117e4dc09 Mon Sep 17 00:00:00 2001
From: Gianmarco Picarella <[email protected]>
Date: Fri, 26 Jun 2026 02:48:32 -0700
Subject: [PATCH] Prevent CVC leakage to autocomplete

This CL implements mitigations to prevent CVCs from being stored in the
autocomplete database under two vulnerability scenarios:

1. Travel parser priority inversion: On travel booking sites, a CVC
field named "flight_verification" matches both the Travel parser (via
"flight") and the Credit Card parser (via "verification"). Because the
Travel parser has higher priority, the field is classified as
UNKNOWN_TYPE, bypassing autocomplete suppression.

2. Standalone CVC email-skip: Standalone CVC parsing is skipped if an
email field is present, causing the CVC field to remain UNKNOWN_TYPE.
Even on forms without email where it is successfully classified as
CREDIT_CARD_STANDALONE_VERIFICATION_CODE, the autocomplete import check
failed to suppress standalone CVCs.

This CL tries to address both issues by implementing the following
changes:

- Updates `BrowserAutofillManager::MaybeImportFromSubmittedForm()` to
suppress autocomplete for both `CREDIT_CARD_VERIFICATION_CODE` and
`CREDIT_CARD_STANDALONE_VERIFICATION_CODE` types.

- Moves the logic to suppress autocomplete for
`CREDIT_CARD_VERIFICATION_CODE`
`CREDIT_CARD_STANDALONE_VERIFICATION_CODE`, `IBAN_VALUE`,
`MERCHANT_PROMO_CODE` and `LOYALTY_MEMBERSHIP_ID` types into
`autocomplete_history_manager.cc` and adds specific unit tests for it.

Regexes in
`AutocompleteHistoryManager::IsFieldNameMeaningfulForAutocomplete()`
will be expanded in a follow-up CL.

Bug: 523229759, 522878450, 40100455
Change-Id: I46bfc1b389f3856c11a28e0d56aad8193a16e6a4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7967519
Reviewed-by: Karol Sygiet <[email protected]>
Reviewed-by: Jihad Hanna <[email protected]>
Commit-Queue: Gianmarco Picarella <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1653012}
---

diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager.cc b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
index 7e1a084..687336b 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager.cc
@@ -625,49 +625,24 @@
 // Triggers the possible import of submitted data at submission time.
 void MaybeImportFromSubmittedForm(AutofillClient& client,
                                   ukm::SourceId ukm_source_id,
-                                  const FormStructure& form_structure) {
+                                  const FormStructure& form) {
   // This intentionally happens prior to `ImportAndProcessFormData()`. See
   // crbug.com/381205586.
   ProfileTokenQuality::SaveObservationsForFilledFormForAllSubmittedProfiles(
-      form_structure, client.GetPersonalDataManager().address_data_manager());
+      form, client.GetPersonalDataManager().address_data_manager());
 
   AutofillAiManager* const ai_manager = client.GetAutofillAiManager();
   const bool autofill_ai_shows_bubble =
-      ai_manager && ai_manager->OnFormSubmitted(form_structure, ukm_source_id);
+      ai_manager && ai_manager->OnFormSubmitted(form, ukm_source_id);
   if (!autofill_ai_shows_bubble) {
     // Update Personal Data with the form's submitted data.
     client.GetFormDataImporter()->ImportAndProcessFormData(
-        form_structure, client.IsAutofillProfileEnabled(),
+        form, client.IsAutofillProfileEnabled(),
         client.GetPaymentsAutofillClient()->IsAutofillPaymentMethodsEnabled(),
         ukm_source_id);
   }
-
-  std::vector<FormFieldData> fields_for_autocomplete = base::ToVector(
-      form_structure,
-      [&](const std::unique_ptr<AutofillField>& autofill_field) {
-        FormFieldData field = *autofill_field;
-        FieldType cc_type = autofill_field->Type().GetCreditCardType();
-        if (cc_type == CREDIT_CARD_VERIFICATION_CODE ||
-            cc_type == CREDIT_CARD_STANDALONE_VERIFICATION_CODE) {
-          // However, if Autofill has recognized a field as CVC, that shouldn't
-          // be saved.
-          field.set_should_autocomplete(false);
-        }
-        if (autofill_field->Type().GetLoyaltyCardType() ==
-                LOYALTY_MEMBERSHIP_ID &&
-            autofill_field->last_modifier() == FieldModifier::kAutofill) {
-          // Only store loyalty cards values in Autocomplete if they were filled
-          // manually.
-          field.set_should_autocomplete(false);
-        }
-        return field;
-      });
-
-  // TODO crbug.com/40100455 - Eliminate `form_for_autocomplete`.
-  FormData form_for_autocomplete = form_structure.ToFormData();
-  form_for_autocomplete.set_fields(std::move(fields_for_autocomplete));
   client.GetSingleFieldFillRouter().OnWillSubmitForm(
-      form_for_autocomplete, &form_structure, client.IsAutocompleteEnabled());
+      form.ToFormData(), &form, client.IsAutocompleteEnabled());
 }
 
 // Generates a compose suggestion for the given `form` and `field` if conditions
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
index 33c281f0..90f5476 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
@@ -4350,72 +4350,6 @@
   EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
 }
 
-// Test that inputs detected to be CVC inputs are forced to
-// !should_autocomplete for SingleFieldFillRouter::OnWillSubmitForm.
-TEST_F(BrowserAutofillManagerTest, DontSaveCvcInAutocompleteHistory) {
-  FormData form_seen_by_ahm;
-  EXPECT_CALL(single_field_fill_router(), OnWillSubmitForm(_, _, true))
-      .WillOnce(SaveArg<0>(&form_seen_by_ahm));
-
-  FormData form = test::GetFormData(
-      {.fields = {
-           {.role = CREDIT_CARD_NUMBER, .value = u"4234-5678-9012-3456"},
-           {.role = CREDIT_CARD_VERIFICATION_CODE, .value = u"123"},
-           {.role = CREDIT_CARD_EXP_4_DIGIT_YEAR, .value = u"04/2020"}}});
-
-  FormsSeen({form});
-  FormSubmitted(form);
-
-  EXPECT_EQ(form.fields().size(), form_seen_by_ahm.fields().size());
-  ASSERT_EQ(3u, form_seen_by_ahm.fields().size());
-  EXPECT_TRUE(form_seen_by_ahm.fields()[0].should_autocomplete());
-  EXPECT_FALSE(form_seen_by_ahm.fields()[1].should_autocomplete());
-  EXPECT_TRUE(form_seen_by_ahm.fields()[2].should_autocomplete());
-}
-
-// Test that inputs detected to be standalone CVC inputs are forced to
-// !should_autocomplete for SingleFieldFillRouter::OnWillSubmitForm.
-TEST_F(BrowserAutofillManagerTest, DontSaveStandaloneCvcInAutocompleteHistory) {
-  FormData form_seen_by_ahm;
-  EXPECT_CALL(single_field_fill_router(),
-              OnWillSubmitForm(_, _, /*is_autocomplete_enabled=*/true))
-      .WillOnce(SaveArg<0>(&form_seen_by_ahm));
-
-  FormData form = test::GetFormData(
-      {.fields = {{.role = CREDIT_CARD_STANDALONE_VERIFICATION_CODE,
-                   .value = u"123"}}});
-  autofill_manager().AddSeenForm(form,
-                                 {CREDIT_CARD_STANDALONE_VERIFICATION_CODE});
-  FormSubmitted(form);
-
-  ASSERT_EQ(1u, form_seen_by_ahm.fields().size());
-  EXPECT_FALSE(form_seen_by_ahm.fields()[0].should_autocomplete());
-}
-
-// Test that autofilled loyalty card fields are forced to !should_autocomplete.
-TEST_F(BrowserAutofillManagerTest,
-       DontSaveAutofilledLoyaltyCardsInAutocompleteHistory) {
-  FormData form_seen_by_ahm;
-  EXPECT_CALL(single_field_fill_router(), OnWillSubmitForm(_, _, true))
-      .WillOnce(SaveArg<0>(&form_seen_by_ahm));
-
-  // Set up form.
-  FormData form = test::GetFormData({.fields = {
-                                         {.role = LOYALTY_MEMBERSHIP_ID},
-                                     }});
-  autofill_manager().AddSeenForm(form, {LOYALTY_MEMBERSHIP_ID});
-  // Mark the loyalty card field as autofilled.
-  test_api(autofill_manager())
-      .FindCachedFormById(form.global_id())
-      ->field(0)
-      ->AddFieldModifier(FieldModifier::kAutofill);
-  test_api(form).field(0).set_value(u"LOYALTYCARDNUMBER");
-
-  FormSubmitted(form);
-  ASSERT_EQ(form.fields().size(), form_seen_by_ahm.fields().size());
-  EXPECT_FALSE(test_api(form_seen_by_ahm).field(0).should_autocomplete());
-}
-
 // Regression test for crbug.com/428900385.
 TEST_F(BrowserAutofillManagerTest, NullAutofillFieldDoesNotCrash) {
   FormData form = test::GetFormData({.fields = {
diff --git a/components/autofill/core/browser/single_field_fillers/autocomplete/autocomplete_history_manager.cc b/components/autofill/core/browser/single_field_fillers/autocomplete/autocomplete_history_manager.cc
index 0101d11f..dc08f16 100644
--- a/components/autofill/core/browser/single_field_fillers/autocomplete/autocomplete_history_manager.cc
+++ b/components/autofill/core/browser/single_field_fillers/autocomplete/autocomplete_history_manager.cc
@@ -23,6 +23,7 @@
 #include "base/version_info/version_info.h"
 #include "components/autofill/core/browser/at_memory/at_memory_enablement_utils.h"
 #include "components/autofill/core/browser/data_quality/validation.h"
+#include "components/autofill/core/browser/field_types.h"
 #include "components/autofill/core/browser/form_structure.h"
 #include "components/autofill/core/browser/foundations/autofill_client.h"
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
@@ -47,6 +48,162 @@
 
 namespace autofill {
 
+namespace {
+// Returns true if the field type is eligible to be saved in the autocomplete
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
index 33c281f0..90f5476 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
@@ -4350,72 +4350,6 @@
   EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
 }
 
-// Test that inputs detected to be CVC inputs are forced to
-// !should_autocomplete for SingleFieldFillRouter::OnWillSubmitForm.
-TEST_F(BrowserAutofillManagerTest, DontSaveCvcInAutocompleteHistory) {
-  FormData form_seen_by_ahm;
-  EXPECT_CALL(single_field_fill_router(), OnWillSubmitForm(_, _, true))
-      .WillOnce(SaveArg<0>(&form_seen_by_ahm));
-
-  FormData form = test::GetFormData(
-      {.fields = {
-           {.role = CREDIT_CARD_NUMBER, .value = u"4234-5678-9012-3456"},
-           {.role = CREDIT_CARD_VERIFICATION_CODE, .value = u"123"},
-           {.role = CREDIT_CARD_EXP_4_DIGIT_YEAR, .value = u"04/2020"}}});
-
-  FormsSeen({form});
-  FormSubmitted(form);
-
-  EXPECT_EQ(form.fields().size(), form_seen_by_ahm.fields().size());
-  ASSERT_EQ(3u, form_seen_by_ahm.fields().size());
-  EXPECT_TRUE(form_seen_by_ahm.fields()[0].should_autocomplete());
-  EXPECT_FALSE(form_seen_by_ahm.fields()[1].should_autocomplete());
-  EXPECT_TRUE(form_seen_by_ahm.fields()[2].should_autocomplete());
-}
-
-// Test that inputs detected to be standalone CVC inputs are forced to
-// !should_autocomplete for SingleFieldFillRouter::OnWillSubmitForm.
-TEST_F(BrowserAutofillManagerTest, DontSaveStandaloneCvcInAutocompleteHistory) {
-  FormData form_seen_by_ahm;
-  EXPECT_CALL(single_field_fill_router(),
-              OnWillSubmitForm(_, _, /*is_autocomplete_enabled=*/true))
-      .WillOnce(SaveArg<0>(&form_seen_by_ahm));
-
-  FormData form = test::GetFormData(
-      {.fields = {{.role = CREDIT_CARD_STANDALONE_VERIFICATION_CODE,
-                   .value = u"123"}}});
-  autofill_manager().AddSeenForm(form,
-                                 {CREDIT_CARD_STANDALONE_VERIFICATION_CODE});
-  FormSubmitted(form);
-
-  ASSERT_EQ(1u, form_seen_by_ahm.fields().size());
-  EXPECT_FALSE(form_seen_by_ahm.fields()[0].should_autocomplete());
-}
-
-// Test that autofilled loyalty card fields are forced to !should_autocomplete.
-TEST_F(BrowserAutofillManagerTest,
-       DontSaveAutofilledLoyaltyCardsInAutocompleteHistory) {
-  FormData form_seen_by_ahm;
-  EXPECT_CALL(single_field_fill_router(), OnWillSubmitForm(_, _, true))
-      .WillOnce(SaveArg<0>(&form_seen_by_ahm));
-
-  // Set up form.
-  FormData form = test::GetFormData({.fields = {
-                                         {.role = LOYALTY_MEMBERSHIP_ID},
-                                     }});
-  autofill_manager().AddSeenForm(form, {LOYALTY_MEMBERSHIP_ID});
-  // Mark the loyalty card field as autofilled.
-  test_api(autofill_manager())
-      .FindCachedFormById(form.global_id())
-      ->field(0)
-      ->AddFieldModifier(FieldModifier::kAutofill);
-  test_api(form).field(0).set_value(u"LOYALTYCARDNUMBER");
-
-  FormSubmitted(form);
-  ASSERT_EQ(form.fields().size(), form_seen_by_ahm.fields().size());
-  EXPECT_FALSE(test_api(form_seen_by_ahm).field(0).should_autocomplete());
-}
-
 // Regression test for crbug.com/428900385.
 TEST_F(BrowserAutofillManagerTest, NullAutofillFieldDoesNotCrash) {
   FormData form = test::GetFormData({.fields = {
diff --git a/components/autofill/core/browser/single_field_fillers/autocomplete/autocomplete_history_manager_unittest.cc b/components/autofill/core/browser/single_field_fillers/autocomplete/autocomplete_history_manager_unittest.cc
index 66a8cd2..340dc93d 100644
--- a/components/autofill/core/browser/single_field_fillers/autocomplete/autocomplete_history_manager_unittest.cc
+++ b/components/autofill/core/browser/single_field_fillers/autocomplete/autocomplete_history_manager_unittest.cc
@@ -21,7 +21,9 @@
 #include "base/time/time.h"
 #include "base/version_info/version_info.h"
 #include "build/build_config.h"
+#include "components/autofill/core/browser/form_structure_test_api.h"
 #include "components/autofill/core/browser/foundations/test_autofill_client.h"
+#include "components/autofill/core/browser/test_utils/autofill_form_test_utils.h"
 #include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
 #include "components/autofill/core/browser/webdata/autocomplete/autocomplete_entry.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
@@ -160,6 +162,7 @@
   EXPECT_CALL(*(web_data_service_.get()), AddFormFields(_)).Times(0);
   autocomplete_manager_->OnWillSubmitFormWithFields(
       form.fields(),
+      /*form=*/nullptr,
       /*is_autocomplete_enabled=*/true);
 }
 
@@ -183,7 +186,7 @@
 
   EXPECT_CALL(*(web_data_service_.get()), AddFormFields(_));
   autocomplete_manager_->OnWillSubmitFormWithFields(
-      form.fields(),
+      form.fields(), /*form=*/nullptr,
       /*is_autocomplete_enabled=*/true);
 }
 
@@ -204,7 +207,7 @@
 
   EXPECT_CALL(*web_data_service_, AddFormFields(_)).Times(0);
   autocomplete_manager_->OnWillSubmitFormWithFields(
-      form.fields(),
+      form.fields(), /*form=*/nullptr,
       /*is_autocomplete_enabled=*/true);
 }
 
@@ -225,7 +228,7 @@
 
   EXPECT_CALL(*web_data_service_, AddFormFields(_)).Times(0);
   autocomplete_manager_->OnWillSubmitFormWithFields(
-      form.fields(),
+      form.fields(), /*form=*/nullptr,
       /*is_autocomplete_enabled=*/true);
 }
 
@@ -247,7 +250,7 @@
 
   EXPECT_CALL(*(web_data_service_.get()), AddFormFields(_));
   autocomplete_manager_->OnWillSubmitFormWithFields(
-      form.fields(),
+      form.fields(), /*form=*/nullptr,
       /*is_autocomplete_enabled=*/true);
 }
 
@@ -268,7 +271,7 @@
 
   EXPECT_CALL(*(web_data_service_.get()), AddFormFields(_)).Times(0);
   autocomplete_manager_->OnWillSubmitFormWithFields(
-      form.fields(),
+      form.fields(), /*form=*/nullptr,
       /*is_autocomplete_enabled=*/false);
 }
 
@@ -296,7 +299,7 @@
 
   EXPECT_CALL(*(web_data_service_.get()), AddFormFields(_)).Times(0);
   autocomplete_manager_->OnWillSubmitFormWithFields(
-      form.fields(),
+      form.fields(), /*form=*/nullptr,
       /*is_autocomplete_enabled=*/true);
 }
 
@@ -322,7 +325,7 @@
 
   EXPECT_CALL(*web_data_service_, AddFormFields(_)).Times(0);
   autocomplete_manager_->OnWillSubmitFormWithFields(
-      form.fields(),
+      form.fields(), /*form=*/nullptr,
       /*is_autocomplete_enabled=*/true);
 }
 
@@ -346,7 +349,7 @@
 
   EXPECT_CALL(*web_data_service_, AddFormFields(_)).Times(0);
   autocomplete_manager_->OnWillSubmitFormWithFields(
-      form.fields(),
+      form.fields(), /*form=*/nullptr,
       /*is_autocomplete_enabled=*/true);
 }
 
@@ -372,7 +375,7 @@
 
   EXPECT_CALL(*(web_data_service_.get()), AddFormFields(_));
   autocomplete_manager_->OnWillSubmitFormWithFields(
-      form.fields(),
+      form.fields(), /*form=*/nullptr,
       /*is_autocomplete_enabled=*/true);
 }
 #endif
@@ -397,7 +400,7 @@
 
   EXPECT_CALL(*web_data_service_, AddFormFields(_)).Times(0);
   autocomplete_manager_->OnWillSubmitFormWithFields(
-      form.fields(),
+      form.fields(), /*form=*/nullptr,
       /*is_autocomplete_enabled=*/true);
 }
 
@@ -468,7 +471,7 @@
 
   // Simulate request for suggestions.
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
   run_loop.Run();
@@ -497,7 +500,7 @@
       .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
 
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
 
@@ -525,7 +528,7 @@
       .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
   // Simulate request for suggestions.
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
   run_loop.Run();
@@ -550,7 +553,7 @@
         .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
   // Simulate request for suggestions.
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
   run_loop.Run();
@@ -574,7 +577,7 @@
       .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
   // Simulate request for suggestions.
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
   run_loop.Run();
@@ -611,7 +614,7 @@
 
   // Simulate request for suggestions.
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
 
@@ -651,7 +654,7 @@
 
   // Simulate request for suggestions.
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
 
@@ -684,7 +687,7 @@
       .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
 
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
 
@@ -716,7 +719,7 @@
   EXPECT_CALL(mock_callback, Run(test_field_.global_id(), IsEmpty()))
       .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
 
@@ -753,7 +756,7 @@
       .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
 
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
 
@@ -816,7 +819,7 @@
   MockSuggestionsReturnedCallback mock_callback;
   EXPECT_CALL(mock_callback, Run).Times(0);
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
 
@@ -827,7 +830,7 @@
                                                u"SomePrefixTwo")))
       .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
   autocomplete_manager_->OnGetSingleFieldSuggestions(
-      test_form_data_, /*form_structure=*/nullptr, test_field_,
+      test_form_data_, /*form=*/nullptr, test_field_,
       /*trigger_autofill_field=*/nullptr, autofill_client_,
       mock_callback.Get());
 
@@ -858,7 +861,7 @@
   EXPECT_CALL(mock_callback, Run(test_field_.global_id(), testing::IsEmpty()))
       .WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
... (truncated)
Loading diff…

Original Bug Report

reported by [email protected]

Priority inversion in TravelFieldParser allows cross-origin CVC leakage via Autocomplete

Flapjack, 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 Autofill heuristics allows sensitive Credit Card Verification Codes (CVC) to be misclassified as UNKNOWN_TYPE due to a priority inversion where the Travel parser’s score (1.2) overrides the Credit Card parser’s score (1.0). This misclassification bypasses explicit security checks meant to block CVC storage, resulting in the CVC being saved to the cross-origin Autocomplete history where malicious sites can retrieve it.

Affected files:

  • components/autofill/core/browser/form_parsing/travel_field_parser.cc
  • components/autofill/core/browser/form_parsing/form_field_parser.h
  • components/autofill/core/browser/foundations/browser_autofill_manager.cc
  • components/autofill/core/browser/single_field_fillers/autocomplete/autocomplete_history_manager.cc

Estimated timestamp from git blame: 2019-02-08

Summary

A potential vulnerability in Chrome’s Autofill system allows sensitive Credit Card Verification Codes (CVC) to bypass security checks and be saved in cleartext to the origin-agnostic Autocomplete database. This occurs due to a priority inversion where the TravelFieldParser assigns an UNKNOWN_TYPE classification with a higher score than the specific CREDIT_CARD_VERIFICATION_CODE classification assigned by the CreditCardFieldParser or StandaloneCvcFieldParser. Once saved, any site can retrieve the sensitive data by using a field with the same name.

Technical Details

When Chrome parses a form to determine field types, it uses multiple local heuristics. The process is orchestrated by FormFieldParser::ParseFormFields (components/autofill/core/browser/form_parsing/form_field_parser.cc).

  1. Priority Inversion: If a form contains an input named flight_verification, it matches patterns for two different parsers:
    • TravelFieldParser::Parse matches the FLIGHT regex (airline|flight in legacy_regex_patterns.json:3242). It assigns the candidate UNKNOWN_TYPE with a base score of 1.2f (kBaseTravelParserScore defined in form_field_parser.h:252).
    • CreditCardFieldParser::Parse (or StandaloneCvcFieldParser) matches the CREDIT_CARD_VERIFICATION_CODE regex, which explicitly includes the substring verification (legacy_regex_patterns.json:2783). It assigns the candidate CREDIT_CARD_VERIFICATION_CODE with a base score of 1.0f (kBaseCreditCardParserScore defined in form_field_parser.h:254).
    • During candidate resolution (FieldCandidates::BestHeuristicType() in field_candidates.cc:34-47), the candidate with the highest score wins. Because 1.2f > 1.0f, the field’s best_heuristic_type is incorrectly set to UNKNOWN_TYPE.
  2. Security Check Bypass: BrowserAutofillManager::MaybeImportFromSubmittedForm (browser_autofill_manager.cc:587) processes fields prior to saving them to Autocomplete. It contains a specific security check to block CVCs: if (autofill_field->Type().GetCreditCardType() == CREDIT_CARD_VERIFICATION_CODE) { field.set_should_autocomplete(false); } (browser_autofill_manager.cc:610-615). Because the heuristic classification was overridden to UNKNOWN_TYPE, this check evaluates to false, and should_autocomplete remains true.
  3. Blocklist Gap: The field is forwarded to AutocompleteHistoryManager::OnWillSubmitFormWithFields, which validates the field name using IsFieldNameMeaningfulForAutocomplete (autocomplete_history_manager.cc:155-166). The blocklist regex (kRegex) forbids names containing cvc, cvn, cvv, passw, pwd, or pin, but completely lacks terms used by the CVC heuristics, like verification or security code. Thus, flight_verification is deemed a valid, saveable name.

The sensitive CVC is subsequently saved to the local Web Data SQLite database, associated with the origin-agnostic name flight_verification.

Suggested Attacker Steps

Note: These are potential steps as our tooling agent cannot execute code to verify the exploit end-to-end.

  1. An attacker compromises a benign airline checkout page (or creates a seemingly benign one) and ensures the CVC input field is named <input type="text" name="flight_verification">.
  2. A victim enters their CVC and submits the form. Due to the parser conflict, the CVC is saved to their local Autocomplete database.
  3. The attacker hosts a separate, cross-origin malicious site containing the same field: <input type="text" name="flight_verification">.
  4. When the victim visits the malicious site and clicks anywhere on the page (or interacts with a decoy field), Chrome offers the saved CVC as an Autocomplete suggestion.
  5. If the victim accepts the suggestion, the CVC is filled, and the attacker’s JavaScript immediately exfiltrates it.

Suggested Remediation

  1. Adjust Parser Scoring: Modify TravelFieldParser (or candidate resolution logic) so that generic classifications like UNKNOWN_TYPE do not override specific, sensitive classifications like CREDIT_CARD_VERIFICATION_CODE from other parsers.
  2. Expand Autocomplete Blocklist: Update the regex kRegex in AutocompleteHistoryManager::IsFieldNameMeaningfulForAutocomplete to include missing sensitive keywords used by the CVC parsers, such as verification and security.?code.

Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff


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