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 <koerber@google.com>
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 <koerber@google.com>
Commit-Queue: Matthias Körber <koerber@google.com>
Reviewed-by: Jihad Hanna <jihadghanna@google.com>
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