Chrome · Passwords
CVE-2026-87565
Logic Error in Passwords
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fcomponents/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc |
modified | |
OtpSuggestionGeneratorTestcomponents/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc |
modified | |
TEST_Fcomponents/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc |
modified |
Files Changed
components/autofill/core/browser/foundations/browser_autofill_manager.cccomponents/autofill/core/browser/foundations/browser_autofill_manager_unittest.cccomponents/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator.cccomponents/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc
Patch
From a7991af9d476b4ca205454aead73a71f6a1d08e5 Mon Sep 17 00:00:00 2001 From: Matthias Koerber <[email protected]> Date: Tue, 04 Aug 2026 04:01:13 -0700 Subject: [PATCH] [OTP] Restrict SMS OTP Autofill to secure contexts This CL ensures that SMS OTP suggestions are only offered on secure (HTTPS) contexts. To achieve this, we silently suppress OTP suggestions when the context is insecure: 1. In the legacy path (BrowserAutofillManager), we skip fetching OTPs from OtpManager in Phase 1 if the context is insecure, and ensure we don't build suggestions in Phase 2. 2. In the new suggestion pipeline (OtpSuggestionGenerator), we early return with empty suggestions if the context is insecure. Silent suppression was chosen over showing a warning message (similar to Payments) to avoid UI noise on HTTP pages when no OTP is actually available. Fixed: 532968511 Change-Id: Iaec97661cb16f38c761473fc8dc462ce02b29000 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8177172 Auto-Submit: Matthias Körber <[email protected]> Commit-Queue: Matthias Körber <[email protected]> Reviewed-by: Jihad Hanna <[email protected]> Cr-Commit-Position: refs/heads/main@{#1673232} --- diff --git a/components/autofill/core/browser/foundations/browser_autofill_manager.cc b/components/autofill/core/browser/foundations/browser_autofill_manager.cc index f09f106c..57c06fc 100644 --- a/components/autofill/core/browser/foundations/browser_autofill_manager.cc +++ b/components/autofill/core/browser/foundations/browser_autofill_manager.cc @@ -1502,6 +1502,10 @@ // autocomplete. if (otp_manager_ && autofill_field && autofill_field->Type().GetTypes().contains(ONE_TIME_CODE)) { + if (!client().IsContextSecure()) { + std::move(generate_suggestions_and_maybe_show_ui_phase2).Run({}); + return; + } otp_manager_->GetOtpSuggestions( std::move(generate_suggestions_and_maybe_show_ui_phase2)); return; @@ -3291,7 +3295,9 @@ } break; case FillingProduct::kOneTimePassword: - suggestions = BuildOtpSuggestions(one_time_passwords); + if (client().IsContextSecure()) { + suggestions = BuildOtpSuggestions(one_time_passwords); + } break; case FillingProduct::kAtMemory: return {}; 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 65976418..19de7d41 100644 --- a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc +++ b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc @@ -7125,6 +7125,44 @@ EXPECT_EQ(otp_value, filled_fields[0].value()); } +// Test that OTP suggestions are silently suppressed on insecure contexts. +TEST_F(BrowserAutofillManagerOtpSuggestionsTest, + OtpSuggestions_InsecureContext) { + autofill_client().set_last_committed_primary_main_frame_url( + GURL("http://example.com")); + + FormData form = + test::GetFormData({.fields = { + {.label = u"Enter one time code", + .form_control_type = FormControlType::kInputText}, + }}); + + // Simulate form parsing results. + auto form_structure = std::make_unique<FormStructure>(form); + form_structure->field(0)->set_heuristic_type( + HeuristicSource::kPasswordManagerMachineLearning, + FieldType::ONE_TIME_CODE); + test_api(autofill_manager()).AddSeenFormStructure(std::move(form_structure)); + + // We should NOT call GetOtpSuggestions on the manager. + EXPECT_CALL(otp_manager(), GetOtpSuggestions).Times(0); + + ON_CALL(autocomplete_history_manager(), OnGetSingleFieldSuggestions) + .WillByDefault( + [](const FormData& form, const FormStructure* form_structure, + const FormFieldData& field, const AutofillField* autofill_field, + const AutofillClient& client, + SingleFieldFillRouter::OnSuggestionsReturnedCallback + on_suggestions_returned) { + std::move(on_suggestions_returned).Run(field.global_id(), {}); + }); + + OnAskForValuesToFill(form, form.fields()[0]); + + // We expect no suggestions. + external_delegate()->CheckNoSuggestions(form.fields()[0].global_id()); +} + // Tests that FillOrPreviewForm correctly passes the blocked_fields to the // FormFiller. TEST_F(BrowserAutofillManagerTest, FillOrPreviewForm_BlockedFields) { diff --git a/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator.cc b/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator.cc index a006afd..8fd0e5d 100644 --- a/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator.cc +++ b/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator.cc @@ -15,9 +15,6 @@ #include "components/autofill/core/browser/data_quality/addresses/profile_token_quality.h" #include "components/autofill/core/browser/field_types.h" #include "components/autofill/core/browser/foundations/autofill_client.h" -#if BUILDFLAG(IS_ANDROID) -#include "components/strings/grit/components_strings.h" -#endif #include "components/autofill/core/browser/integrators/one_time_tokens/otp_manager.h" #include "components/autofill/core/browser/suggestions/suggestion.h" #include "components/autofill/core/browser/suggestions/suggestion_generator.h" @@ -83,6 +80,11 @@ return; } + if (!client.IsContextSecure()) { + std::move(callback).Run({SuggestionDataSource::kOneTimePassword, {}}); + return; + } + otp_manager_->GetOtpSuggestions( base::BindOnce(&OtpSuggestionGenerator::OnOtpReturned, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); diff --git a/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc index 370a1d7c..66eaee6 100644 --- a/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc +++ b/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc @@ -19,16 +19,19 @@ #include "components/autofill/core/common/autofill_test_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" namespace autofill { using ::testing::Field; +using ::testing::IsEmpty; +using ::testing::Pair; class OtpSuggestionGeneratorTest : public testing::Test { protected: OtpSuggestionGeneratorTest() = default; - AutofillClient& client() { return autofill_client_; } + TestAutofillClient& client() { return autofill_client_; } OtpSuggestionGenerator& generator() { return generator_; } MockOtpManager& otp_manager() { return otp_manager_; } @@ -94,4 +97,28 @@ #endif } +// Test that OTP suggestions are silently suppressed on insecure contexts. +TEST_F(OtpSuggestionGeneratorTest, GenerateOtpSuggestions_InsecureContext) { + client().set_last_committed_primary_main_frame_url( + GURL("http://example.com")); + FormData form = test::GetFormData({.fields = {{.role = ONE_TIME_CODE}}}); + FormStructure form_structure(form); + form_structure.field(0)->SetTypeTo(AutofillType(ONE_TIME_CODE), std::nullopt); + + EXPECT_CALL(otp_manager(), GetOtpSuggestions).Times(0); + + base::MockCallback< + base::OnceCallback<void(SuggestionGenerator::ReturnedSuggestions)>> + suggestions_generated_callback; + + EXPECT_CALL( + suggestions_generated_callback, + Run(Pair(SuggestionGenerator::SuggestionDataSource::kOneTimePassword, + IsEmpty()))); + + generator().GenerateSuggestions(form, form.fields()[0], &form_structure, + form_structure.field(0), client(), + suggestions_generated_callback.Get()); +} + } // namespace autofill
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 65976418..19de7d41 100644
--- a/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
@@ -7125,6 +7125,44 @@
EXPECT_EQ(otp_value, filled_fields[0].value());
}
+// Test that OTP suggestions are silently suppressed on insecure contexts.
+TEST_F(BrowserAutofillManagerOtpSuggestionsTest,
+ OtpSuggestions_InsecureContext) {
+ autofill_client().set_last_committed_primary_main_frame_url(
+ GURL("http://example.com"));
+
+ FormData form =
+ test::GetFormData({.fields = {
+ {.label = u"Enter one time code",
+ .form_control_type = FormControlType::kInputText},
+ }});
+
+ // Simulate form parsing results.
+ auto form_structure = std::make_unique<FormStructure>(form);
+ form_structure->field(0)->set_heuristic_type(
+ HeuristicSource::kPasswordManagerMachineLearning,
+ FieldType::ONE_TIME_CODE);
+ test_api(autofill_manager()).AddSeenFormStructure(std::move(form_structure));
+
+ // We should NOT call GetOtpSuggestions on the manager.
+ EXPECT_CALL(otp_manager(), GetOtpSuggestions).Times(0);
+
+ ON_CALL(autocomplete_history_manager(), OnGetSingleFieldSuggestions)
+ .WillByDefault(
+ [](const FormData& form, const FormStructure* form_structure,
+ const FormFieldData& field, const AutofillField* autofill_field,
+ const AutofillClient& client,
+ SingleFieldFillRouter::OnSuggestionsReturnedCallback
+ on_suggestions_returned) {
+ std::move(on_suggestions_returned).Run(field.global_id(), {});
+ });
+
+ OnAskForValuesToFill(form, form.fields()[0]);
+
+ // We expect no suggestions.
+ external_delegate()->CheckNoSuggestions(form.fields()[0].global_id());
+}
+
// Tests that FillOrPreviewForm correctly passes the blocked_fields to the
// FormFiller.
TEST_F(BrowserAutofillManagerTest, FillOrPreviewForm_BlockedFields) {
diff --git a/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc b/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc
index 370a1d7c..66eaee6 100644
--- a/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc
@@ -19,16 +19,19 @@
#include "components/autofill/core/common/autofill_test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
namespace autofill {
using ::testing::Field;
+using ::testing::IsEmpty;
+using ::testing::Pair;
class OtpSuggestionGeneratorTest : public testing::Test {
protected:
OtpSuggestionGeneratorTest() = default;
- AutofillClient& client() { return autofill_client_; }
+ TestAutofillClient& client() { return autofill_client_; }
OtpSuggestionGenerator& generator() { return generator_; }
MockOtpManager& otp_manager() { return otp_manager_; }
@@ -94,4 +97,28 @@
#endif
}
+// Test that OTP suggestions are silently suppressed on insecure contexts.
+TEST_F(OtpSuggestionGeneratorTest, GenerateOtpSuggestions_InsecureContext) {
+ client().set_last_committed_primary_main_frame_url(
+ GURL("http://example.com"));
+ FormData form = test::GetFormData({.fields = {{.role = ONE_TIME_CODE}}});
+ FormStructure form_structure(form);
+ form_structure.field(0)->SetTypeTo(AutofillType(ONE_TIME_CODE), std::nullopt);
+
+ EXPECT_CALL(otp_manager(), GetOtpSuggestions).Times(0);
+
+ base::MockCallback<
+ base::OnceCallback<void(SuggestionGenerator::ReturnedSuggestions)>>
+ suggestions_generated_callback;
+
+ EXPECT_CALL(
+ suggestions_generated_callback,
+ Run(Pair(SuggestionGenerator::SuggestionDataSource::kOneTimePassword,
+ IsEmpty())));
+
+ generator().GenerateSuggestions(form, form.fields()[0], &form_structure,
+ form_structure.field(0), client(),
+ suggestions_generated_callback.Get());
+}
+
} // namespace autofill
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page