Medium chrome Info Disclosure 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in Editing
DescriptionInformation leak in Editing
ComponentEditing
Sub-componentBlink>Editing
Bug ClassInfo Disclosure
Tracker556235808
Fix commit3ceb583e03c6 (chromium/src) +84/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
TEST_F
third_party/blink/renderer/core/editing/iterators/text_iterator_test.cc
modified

Files Changed

  • third_party/blink/renderer/platform/runtime_enabled_features.json5
  • third_party/blink/renderer/core/editing/iterators/text_iterator_test.cc
  • third_party/blink/renderer/core/editing/iterators/text_iterator.cc
From 3ceb583e03c61aa4e531ff8399838bcfe9e015f1 Mon Sep 17 00:00:00 2001
From: Kent Tamura <[email protected]>
Date: Thu, 03 Sep 2026 21:04:09 -0700
Subject: [PATCH] Exclude autofilled <select> preview value in TextIterator

TextIteratorAlgorithm<Strategy>::HandleTextNode() checks
EnclosingTextControl(node_) to exclude autofilled values when
ExcludesAutofilledValue() is true. However, EnclosingTextControl() only
finds TextControlElement (<input> and <textarea>), missing
HTMLSelectElement whose autofill preview value is rendered inside a
user-agent shadow root (popover).

This CL updates HandleTextNode() to recognize HTMLSelectElement in UA
shadow roots and exclude its autofilled preview value.
This behavior is guarded by a new runtime feature flag
TextIteratorExcludeAutofilledSelectFix (status: stable).

Bug: 556235808
Change-Id: I1bbfaf1e743291276ddf46db4aa7d58f66fba7be
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8351184
Commit-Queue: Koji Ishii <[email protected]>
Reviewed-by: Koji Ishii <[email protected]>
Auto-Submit: Kent Tamura <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1692167}
---
diff --git a/third_party/blink/renderer/core/editing/iterators/text_iterator.cc b/third_party/blink/renderer/core/editing/iterators/text_iterator.cc
index ca393f3a..206d7bf 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_iterator.cc
+++ b/third_party/blink/renderer/core/editing/iterators/text_iterator.cc
@@ -45,6 +45,7 @@
 #include "third_party/blink/renderer/core/html/forms/html_legend_element.h"
 #include "third_party/blink/renderer/core/html/forms/html_opt_group_element.h"
 #include "third_party/blink/renderer/core/html/forms/html_option_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
 #include "third_party/blink/renderer/core/html/forms/text_control_element.h"
 #include "third_party/blink/renderer/core/html/html_body_element.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
@@ -533,7 +534,15 @@
 template <typename Strategy>
 void TextIteratorAlgorithm<Strategy>::HandleTextNode() {
   if (ExcludesAutofilledValue()) {
-    TextControlElement* control = EnclosingTextControl(node_);
+    HTMLFormControlElement* control = nullptr;
+    if (RuntimeEnabledFeatures::
+            TextIteratorExcludeAutofilledSelectFixEnabled()) {
+      if (node_->IsInUserAgentShadowRoot()) {
+        control = DynamicTo<HTMLFormControlElement>(node_->OwnerShadowHost());
+      }
+    } else {
+      control = EnclosingTextControl(node_);
+    }
     // For security reason, we don't expose suggested value if it is
     // auto-filled.
     // TODO(crbug.com/1472209): Only hide suggested value of previews.
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index b1b5fe4..1b6457d 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -6325,6 +6325,11 @@
       status: {"Android": "stable"},
     },
     {
+      // crbug.com/556235808
+      name: "TextIteratorExcludeAutofilledSelectFix",
+      status: "stable",
+    },
+    {
       name: "TextMetricsBaselines",
       status: "stable",
     },
diff --git a/third_party/blink/renderer/core/editing/iterators/text_iterator_test.cc b/third_party/blink/renderer/core/editing/iterators/text_iterator_test.cc
index 7f55a9f..1b4f519 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_iterator_test.cc
+++ b/third_party/blink/renderer/core/editing/iterators/text_iterator_test.cc
@@ -36,7 +36,10 @@
 #include "third_party/blink/renderer/core/editing/selection_template.h"
 #include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
 #include "third_party/blink/renderer/core/html/forms/text_control_element.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 namespace blink {
@@ -1177,5 +1180,71 @@
   EXPECT_EQ("[test][][;]", Iterate<FlatTree>());
 }
 
+TEST_F(TextIteratorTest, ExcludeAutofilledValueSelect) {
+  SetBodyContent(
+      "<select id='sel'>"
+      "<option value='1' selected>One</option>"
+      "<option value='2'>Two</option>"
+      "</select>");
+  auto* select = To<HTMLSelectElement>(GetElementById("sel"));
+  select->SetSuggestedValue("2");
+  ASSERT_TRUE(select->IsPreviewed());
+  test::RunPendingTasks();
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* popover = select->GetAutofillPreviewElement();
+  ASSERT_TRUE(popover);
+
+  const Position start = Position::FirstPositionInNode(*popover);
+  const Position end = Position::LastPositionInNode(*popover);
+
+  // Without ExcludeAutofilledValue, the preview text in the popover is emitted.
+  EXPECT_EQ("[Two]", IteratePartial<DomTree>(start, end));
+
+  // With ExcludeAutofilledValue, the preview text must be suppressed when the
+  // fix is enabled.
+  TextIteratorBehavior behavior =
+      TextIteratorBehavior::Builder().SetExcludeAutofilledValue(true).Build();
+  EXPECT_EQ("", IteratePartial<DomTree>(start, end, behavior));
+
+  // When the feature is disabled, the previous behavior is restored (preview
+  // text is emitted).
+  ScopedTextIteratorExcludeAutofilledSelectFixForTest disable_fix(false);
+  EXPECT_EQ("[Two]", IteratePartial<DomTree>(start, end, behavior));
+}
+
+TEST_F(TextIteratorTest, ExcludeAutofilledValueListboxSelect) {
+  SetBodyContent(
+      "<select id='sel' size='2'>"
+      "<option value='1' selected>One</option>"
+      "<option value='2'>Two</option>"
+      "</select>");
+  auto* select = To<HTMLSelectElement>(GetElementById("sel"));
+  select->SetSuggestedValue("2");
+  ASSERT_TRUE(select->IsPreviewed());
+  test::RunPendingTasks();
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* popover = select->GetAutofillPreviewElement();
+  ASSERT_TRUE(popover);
+
+  const Position start = Position::FirstPositionInNode(*popover);
+  const Position end = Position::LastPositionInNode(*popover);
+
+  // Without ExcludeAutofilledValue, the preview text in the popover is emitted.
+  EXPECT_EQ("[Two]", IteratePartial<DomTree>(start, end));
+
+  // With ExcludeAutofilledValue, the preview text must be suppressed when the
+  // fix is enabled.
+  TextIteratorBehavior behavior =
+      TextIteratorBehavior::Builder().SetExcludeAutofilledValue(true).Build();
+  EXPECT_EQ("", IteratePartial<DomTree>(start, end, behavior));
+
+  // When the feature is disabled, the previous behavior is restored (preview
+  // text is emitted).
+  ScopedTextIteratorExcludeAutofilledSelectFixForTest disable_fix(false);
+  EXPECT_EQ("[Two]", IteratePartial<DomTree>(start, end, behavior));
+}
+
 }  // namespace text_iterator_test
 }  // namespace blink
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/core/editing/iterators/text_iterator_test.cc b/third_party/blink/renderer/core/editing/iterators/text_iterator_test.cc
index 7f55a9f..1b4f519 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_iterator_test.cc
+++ b/third_party/blink/renderer/core/editing/iterators/text_iterator_test.cc
@@ -36,7 +36,10 @@
 #include "third_party/blink/renderer/core/editing/selection_template.h"
 #include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
 #include "third_party/blink/renderer/core/html/forms/text_control_element.h"
+#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 namespace blink {
@@ -1177,5 +1180,71 @@
   EXPECT_EQ("[test][][;]", Iterate<FlatTree>());
 }
 
+TEST_F(TextIteratorTest, ExcludeAutofilledValueSelect) {
+  SetBodyContent(
+      "<select id='sel'>"
+      "<option value='1' selected>One</option>"
+      "<option value='2'>Two</option>"
+      "</select>");
+  auto* select = To<HTMLSelectElement>(GetElementById("sel"));
+  select->SetSuggestedValue("2");
+  ASSERT_TRUE(select->IsPreviewed());
+  test::RunPendingTasks();
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* popover = select->GetAutofillPreviewElement();
+  ASSERT_TRUE(popover);
+
+  const Position start = Position::FirstPositionInNode(*popover);
+  const Position end = Position::LastPositionInNode(*popover);
+
+  // Without ExcludeAutofilledValue, the preview text in the popover is emitted.
+  EXPECT_EQ("[Two]", IteratePartial<DomTree>(start, end));
+
+  // With ExcludeAutofilledValue, the preview text must be suppressed when the
+  // fix is enabled.
+  TextIteratorBehavior behavior =
+      TextIteratorBehavior::Builder().SetExcludeAutofilledValue(true).Build();
+  EXPECT_EQ("", IteratePartial<DomTree>(start, end, behavior));
+
+  // When the feature is disabled, the previous behavior is restored (preview
+  // text is emitted).
+  ScopedTextIteratorExcludeAutofilledSelectFixForTest disable_fix(false);
+  EXPECT_EQ("[Two]", IteratePartial<DomTree>(start, end, behavior));
+}
+
+TEST_F(TextIteratorTest, ExcludeAutofilledValueListboxSelect) {
+  SetBodyContent(
+      "<select id='sel' size='2'>"
+      "<option value='1' selected>One</option>"
+      "<option value='2'>Two</option>"
+      "</select>");
+  auto* select = To<HTMLSelectElement>(GetElementById("sel"));
+  select->SetSuggestedValue("2");
+  ASSERT_TRUE(select->IsPreviewed());
+  test::RunPendingTasks();
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* popover = select->GetAutofillPreviewElement();
+  ASSERT_TRUE(popover);
+
+  const Position start = Position::FirstPositionInNode(*popover);
+  const Position end = Position::LastPositionInNode(*popover);
+
+  // Without ExcludeAutofilledValue, the preview text in the popover is emitted.
+  EXPECT_EQ("[Two]", IteratePartial<DomTree>(start, end));
+
+  // With ExcludeAutofilledValue, the preview text must be suppressed when the
+  // fix is enabled.
+  TextIteratorBehavior behavior =
+      TextIteratorBehavior::Builder().SetExcludeAutofilledValue(true).Build();
+  EXPECT_EQ("", IteratePartial<DomTree>(start, end, behavior));
+
+  // When the feature is disabled, the previous behavior is restored (preview
+  // text is emitted).
+  ScopedTextIteratorExcludeAutofilledSelectFixForTest disable_fix(false);
+  EXPECT_EQ("[Two]", IteratePartial<DomTree>(start, end, behavior));
+}
+
 }  // namespace text_iterator_test
 }  // namespace blink
Loading diff…

Original Bug Report

reported by [email protected]

Autofill PII Leak via TextIterator ExcludeAutofilledValue Select Omission

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: The ExcludeAutofilledValue guard in TextIteratorAlgorithm::HandleTextNode checks only EnclosingTextControl, which matches input and textarea elements but omits select elements. When an Autofill preview is active, the preview text popover in the select element’s UA shadow root is not suppressed during selection serialization. Page JavaScript can potentially position the DOM selection inside the popover and serialize the previewed option label via getSelection().toString(), leaking Autofill PII.

Affected files:

  • third_party/blink/renderer/core/editing/iterators/text_iterator.cc
  • third_party/blink/renderer/core/html/forms/text_control_element.cc
  • third_party/blink/renderer/core/html/forms/select_type.cc
  • third_party/blink/renderer/core/editing/finder/find_buffer.cc

Estimated timestamp from git blame: 2026-08-10

Summary

Chromium’s TextIteratorAlgorithm<Strategy>::HandleTextNode contains a security guard intended to prevent sensitive Autofill preview text from being exposed to web content during text extraction and selection serialization (AdjustBehaviorFlags<EditingStrategy> forces ExcludeAutofilledValue(true) for DomSelection::toString()). However, this guard determines whether a text node belongs to a previewed control using EnclosingTextControl(node_), which only inspects elements inheriting from TextControlElement (<input> and <textarea>).

With SelectAutofillPopoverPreview enabled, <select> elements render Autofill previews inside a UA-shadow popover (::-internal-select-autofill-preview). Because HTMLSelectElement does not inherit from TextControlElement, EnclosingTextControl returns nullptr. Consequently, the preview text in the popover is not suppressed and can potentially be serialized into plaintext via getSelection().toString().

Root Cause Analysis

  1. Guard Implementation (text_iterator.cc:533-543):
template <typename Strategy>
void TextIteratorAlgorithm<Strategy>::HandleTextNode() {
  if (ExcludesAutofilledValue()) {
    TextControlElement* control = EnclosingTextControl(node_);
    // For security reason, we don't expose suggested value if it is
    // auto-filled.
    // TODO(crbug.com/1472209): Only hide suggested value of previews.
    if (control && (control->IsAutofilled() || control->IsPreviewed())) {
      return;
    }
  }
  1. Predicate Limitation (text_control_element.cc:1268-1276):
TextControlElement* EnclosingTextControl(const Node* container) {
  if (!container)
    return nullptr;
  Element* ancestor = container->OwnerShadowHost();
  return ancestor && IsTextControl(*ancestor) &&
                 container->ContainingShadowRoot()->IsUserAgent()
             ? ToTextControl(ancestor)
             : nullptr;
}

IsTextControl (text_control_element.h:435-441) returns true only for TextControlElement instances (HTMLInputElement, HTMLTextAreaElement). HTMLSelectElement derives directly from HTMLFormControlElementWithState, so EnclosingTextControl returns nullptr for nodes within the <select> UA shadow root.

  1. Autofill Select Popover (select_type.cc:2083-2123): When an Autofill preview is shown, SelectType::DidSetSuggestedOption opens autofill_popover_ (HTMLSelectElement::SelectAutofillPreviewElement) and populates autofill_popover_text_ with the previewed option’s label (setInnerText(option->label())).

  2. Selection Serialization (dom_selection.cc:875-896): DomSelection::toString() serializes the normalized DOM range without adjusting for shadow boundaries. Because EnclosingTextControl returns nullptr, HandleTextNode falls through and appends the preview text to the serialized output.

Potential Attack Scenario

Note: The following steps describe a potential exploitation sequence based on source code analysis. Our tooling environment does not currently have the capability to run live exploit code.

  1. The page contains <input autocomplete="name"> and a listbox <select size="2" autocomplete="country" id="s"> styled with #s option { visibility: hidden; }.
  2. The user focuses the input and hovers over an Autofill suggestion in the browser UI, triggering PreviewFormField in the renderer (form_autofill_util.cc:1392-1432).
  3. The select element enters kPreviewed state and renders the previewed option’s label in its UA shadow popover.
  4. Attacker script detects preview onset and clears each option’s value attribute (for (const o of s.options) o.value = '';). This causes HTMLSelectElement::SuggestedValue() to return "" without closing the popover, bypassing FindBuffer::ShouldIgnoreContents (find_buffer.cc:80-86).
  5. Attacker script runs window.find(c) for characters in the probe alphabet. Editor::FindString (editor.cc:800-822) matches the character in the popover and sets the DOM selection inside the popover’s text node.
  6. Script executes getSelection().modify('extend', 'forward', 'lineboundary') followed by getSelection().toString(). Because EnclosingTextControl returns nullptr, the guard is bypassed and the previewed PII substring is returned in plaintext.
  7. Repeating with backward extension reconstructs the full previewed label string without requiring user confirmation.

Suggested Fix

In TextIteratorAlgorithm<Strategy>::HandleTextNode (third_party/blink/renderer/core/editing/iterators/text_iterator.cc), extend the autofill preview check to also handle HTMLSelectElement and its preview popovers:

  • Check if node_->OwnerShadowHost() is an HTMLSelectElement that satisfies select->IsPreviewed() || select->IsAutofilled().
  • Alternatively, generalize the check to inspect any HTMLFormControlElementWithState host that is in a previewed or autofilled state before serializing UA shadow content.

Evaluated with Chrome root at commit: 53c437ac4ea52cc89d0265eb9e6d68666fdb303e


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