Low watch CVSS 6.5 chrome Info Disclosure 🔧 Commit mapped

Overview

Low
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in Passwords
DescriptionInformation leak in Passwords
ComponentPasswords
Sub-componentOTP
Bug ClassInfo Disclosure
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
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.
Tracker532968511
Fix commita7991af9d476 (chromium/src) +78/-5
CWECWE-200 (Information exposure)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-07-09 (per vendor advisory)
Fix landed2026-08-04 (public git)
Disclosed2026-09-08
Patch gap35 days — fix public in source before the advisory

Background

SMS OTP Autofill
a Chrome feature that captures one-time codes delivered by SMS and offers them as autofill suggestions for fields typed as ONE_TIME_CODE.
Secure context (`AutofillClient::IsContextSecure`)
a client method that reports whether the page’s committed main-frame origin is trustworthy (e.g. HTTPS), used to gate sensitive autofill behavior.
`OtpManager::GetOtpSuggestions`
the browser-side call that retrieves the currently available SMS one-time codes and returns them as suggestions via a callback.
Dual suggestion pipelines
OTP suggestions can be produced through the legacy BrowserAutofillManager two-phase path or through the newer OtpSuggestionGenerator, so a gate must exist in both.

Root Cause Analysis

Before the patch, both suggestion pipelines fetched and surfaced SMS one-time codes for ONE_TIME_CODE fields without ever consulting whether the page was a secure context. The invariant that a sensitive, single-use authentication code should only be offered into a trustworthy (HTTPS) origin was therefore never enforced, so a one-time code could be suggested and autofilled on a plain http:// page.

The fix adds an IsContextSecure() guard at each entry point: in the legacy Phase 1, an insecure context now runs the generate_suggestions_and_maybe_show_ui_phase2 callback with an empty result and returns before otp_manager_->GetOtpSuggestions(...) is called; in the legacy Phase 2, BuildOtpSuggestions(one_time_passwords) is only executed when the context is secure; and in OtpSuggestionGenerator, an insecure context returns an empty {kOneTimePassword, {}} payload before the manager is queried. Because every guard executes ahead of the OTP-fetch/build sink, no OTP data reaches an insecure page. The removed #if BUILDFLAG(IS_ANDROID) string include is only dead-code cleanup, since the old code paved a warning-message approach that was dropped in favor of silent suppression.

Key insight
The single mistake was offering sensitive SMS one-time codes as autofill suggestions with no secure-context check, so codes could leak into insecure (HTTP) pages; the fix gates every OTP suggestion entry point on IsContextSecure() and silently returns empty suggestions otherwise.

Attack Path

  1. Serve an insecure OTP form An attacker hosts (or, as a network attacker, injects into) an http:// page containing a field that Chrome classifies as ONE_TIME_CODE.
  2. Victim receives an SMS code While the victim is on that page, a legitimate SMS one-time code arrives and is captured by Chrome’s OTP mechanism.
  3. Suggestion is generated without a security gate Chrome parses the field, and the unpatched BrowserAutofillManager / OtpSuggestionGenerator path calls OtpManager::GetOtpSuggestions and builds a suggestion regardless of context security.
  4. Code is exposed on the insecure page The one-time code is presented (and potentially autofilled) into the untrusted HTTP context, disclosing it to the page and to any on-path network observer.
  5. Attacker obtains the OTP (inferred) The attacker-controlled page or network position reads the disclosed code and uses it to complete an authentication step.

Impact Assessment

An attacker gains disclosure of a victim’s SMS one-time code — a sensitive authentication secret — by having it suggested or autofilled into an insecure (HTTP) context in the browser rather than being restricted to secure origins. Preconditions are that the victim is on an insecure page containing a one-time-code field and has an OTP available to Chrome at that moment. The advisory classifies this as an information leak in Passwords; it reports no exploited-in-the-wild status, and any downstream account compromise from the leaked code is (inferred).

Changed Functions

FunctionChangeNotes
TEST_F
components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
modified
OtpSuggestionGeneratorTest
components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc
modified
TEST_F
components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc
modified

Files Changed

  • components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator_unittest.cc
  • components/autofill/core/browser/foundations/browser_autofill_manager_unittest.cc
  • components/autofill/core/browser/suggestions/one_time_passwords/otp_suggestion_generator.cc
  • components/autofill/core/browser/foundations/browser_autofill_manager.cc

Audit Directions

  • Sensitive-data autofill without a secure-context gate
    Audit every autofill/suggestion producer that surfaces secrets (OTPs, passwords, payment data) and confirm each independent entry point checks IsContextSecure() before fetching or building suggestions.
  • Parallel legacy and new pipelines
    When a feature is migrated to a new generator, verify a security guard added to one pipeline is mirrored in the other, since BrowserAutofillManager and OtpSuggestionGenerator each needed the same fix.
  • Guard-before-sink ordering
    Check that any newly added early-exit guard runs before the data-fetch/callback sink (as here, before GetOtpSuggestions and BuildOtpSuggestions), rather than after the sensitive value is already retrieved.
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/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/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…

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.