Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Views
DescriptionInappropriate implementation in Views
ComponentViews
Bug ClassLogic Error
Tracker519230894
Fix commitf419acfcd74b (chromium/src) +232/-36
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
modified
switch
content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
modified
if
content/browser/renderer_host/render_widget_host_view_mac.mm
modified

Files Changed

  • content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
  • content/browser/renderer_host/render_widget_host_view_mac.mm
  • content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
From f419acfcd74bbcab41700900117cb6a970587832 Mon Sep 17 00:00:00 2001
From: Avi Drissman <[email protected]>
Date: Wed, 03 Jun 2026 13:15:44 -0700
Subject: [PATCH] Treat "kinda password" fields as password fields

When a password field is revealed by the user, the field is tagged with
the flag TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD. When a normal text field is
detected to be used by the web page as a password field, the field is
tagged with the flag TEXT_INPUT_FLAG_HAS_BEEN_CUSTOM_PASSWORD.

In both cases, treat such a field as a password field in various places
in the code.

Fixed: 519228697, 519230894, 519233776
Change-Id: I7809e4c77f6a5a90ff33e6e2671b7c466a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7896033
Reviewed-by: Keren Zhu <[email protected]>
Commit-Queue: Avi Drissman <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1641141}
---

diff --git a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
index a04aa49..24c98863 100644
--- a/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
+++ b/content/app_shim_remote_cocoa/render_widget_host_view_cocoa.mm
@@ -566,23 +566,30 @@
 
 - (void)requestTextSuggestions {
   auto* touchBarItem = _candidateListTouchBarItem;
-  if (!touchBarItem)
+  if (!touchBarItem) {
     return;
+  }
   [touchBarItem
       updateWithInsertionPointVisibility:_textSelectionRange.is_empty()];
-  if (_textInputType == ui::TEXT_INPUT_TYPE_PASSWORD)
+  if (_textInputType == ui::TEXT_INPUT_TYPE_PASSWORD ||
+      _textInputFlags & ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD ||
+      _textInputFlags & ui::TEXT_INPUT_FLAG_HAS_BEEN_CUSTOM_PASSWORD) {
     return;
-  if (!touchBarItem.candidateListVisible)
+  }
+  if (!touchBarItem.candidateListVisible) {
     return;
+  }
   if (!_textSelectionRange.IsValid() ||
-      _availableTextOffset > _textSelectionRange.GetMin())
+      _availableTextOffset > _textSelectionRange.GetMin()) {
     return;
+  }
 
   NSRange selectionRange = _textSelectionRange.ToNSRange();
   NSString* selectionText = base::SysUTF16ToNSString(_availableText);
   selectionRange.location -= _availableTextOffset;
-  if (NSMaxRange(selectionRange) > selectionText.length)
+  if (NSMaxRange(selectionRange) > selectionText.length) {
     return;
+  }
 
   // TODO: Fetch the spell document tag from the renderer (or equivalent).
   _textSuggestionsSequenceNumber = [self.spellChecker
@@ -625,12 +632,15 @@
 
 - (NSTextCheckingType)enabledTextCheckingTypes {
   NSTextCheckingType checkingTypes = 0;
-  if (self.automaticQuoteSubstitutionEnabled)
+  if (self.automaticQuoteSubstitutionEnabled) {
     checkingTypes |= NSTextCheckingTypeQuote;
-  if (self.automaticDashSubstitutionEnabled)
+  }
+  if (self.automaticDashSubstitutionEnabled) {
     checkingTypes |= NSTextCheckingTypeDash;
-  if (self.automaticTextReplacementEnabled)
+  }
+  if (self.automaticTextReplacementEnabled) {
     checkingTypes |= NSTextCheckingTypeReplacement;
+  }
   return checkingTypes;
 }
 
@@ -639,10 +649,14 @@
 }
 
 - (bool)canTransformText {
-  if (_textInputType == ui::TEXT_INPUT_TYPE_NONE)
+  if (_textInputType == ui::TEXT_INPUT_TYPE_NONE) {
     return NO;
-  if (_textInputType == ui::TEXT_INPUT_TYPE_PASSWORD)
+  }
+  if (_textInputType == ui::TEXT_INPUT_TYPE_PASSWORD ||
+      _textInputFlags & ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD ||
+      _textInputFlags & ui::TEXT_INPUT_FLAG_HAS_BEEN_CUSTOM_PASSWORD) {
     return NO;
+  }
 
   return YES;
 }
@@ -2383,19 +2397,24 @@
 // Each RenderWidgetHostViewCocoa has its own input context, but we return
 // nil when the caret is in non-editable content or password box to avoid
 // making input methods do their work.
-// We disable input method inside password field as it is normal for Mac OS X
+//
+// We disable input method inside password field as it is normal for macOS
 // password input fields to not allow dead keys or non ASCII input methods.
 // There is also a privacy risk if the composition candidate window shows your
 // password when the user is "composing" inside a password field. See
-// crbug.com/1196101 for more info.
+// https://crbug.com/40759416 for more info.
 - (NSTextInputContext*)inputContext {
-  switch (_textInputType) {
-    case ui::TEXT_INPUT_TYPE_NONE:
-    case ui::TEXT_INPUT_TYPE_PASSWORD:
-      return nil;
-    default:
-      return [super inputContext];
+  if (_textInputType == ui::TEXT_INPUT_TYPE_NONE ||
+      _textInputType == ui::TEXT_INPUT_TYPE_PASSWORD) {
+    return nil;
   }
+
+  if (_textInputFlags & ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD ||
+      _textInputFlags & ui::TEXT_INPUT_FLAG_HAS_BEEN_CUSTOM_PASSWORD) {
+    return nil;
+  }
+
+  return [super inputContext];
 }
 
 - (BOOL)hasMarkedText {
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index 319b54e2..3acdf53d 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -72,6 +72,8 @@
 #import "ui/base/cocoa/secure_password_input.h"
 #include "ui/base/cursor/cursor.h"
 #include "ui/base/ime/mojom/text_input_state.mojom.h"
+#include "ui/base/ime/text_input_flags.h"
+#include "ui/base/ime/text_input_type.h"
 #include "ui/base/mojom/attributed_string.mojom.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/display/display.h"
@@ -1788,14 +1790,33 @@
 }
 
 void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
-  const bool should_enable_password_input =
-      active && GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD;
+  bool should_enable_password_input = [active, this] {
+    if (!active) {
+      return false;
+    }
+
+    if (!this->GetActiveWidget()) {
+      return false;
+    }
+
+    const ui::mojom::TextInputState* state =
+        this->text_input_manager_->GetTextInputState();
+    if (state->type == ui::TEXT_INPUT_TYPE_PASSWORD ||
+        state->flags & ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD ||
+        state->flags & ui::TEXT_INPUT_FLAG_HAS_BEEN_CUSTOM_PASSWORD) {
+      return true;
+    }
+
+    return false;
+  }();
+
   if (should_enable_password_input) {
     password_input_enabler_ =
         std::make_unique<ui::ScopedPasswordInputEnabler>();
   } else {
     password_input_enabler_.reset();
   }
+
   update_windows_timer_.Stop();
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
index 2185bfb8..e1f6dcf 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -60,6 +60,8 @@
 #import "third_party/ocmock/ocmock_extensions.h"
 #include "ui/base/cocoa/secure_password_input.h"
 #include "ui/base/ime/mojom/text_input_state.mojom.h"
+#include "ui/base/ime/text_input_flags.h"
+#include "ui/base/ime/text_input_type.h"
 #import "ui/base/test/cocoa_helper.h"
 #import "ui/base/test/scoped_fake_nswindow_focus.h"
 #include "ui/base/ui_base_features.h"
@@ -1824,9 +1826,11 @@
   }
 
   void SetTextInputType(RenderWidgetHostViewBase* view,
-                        ui::TextInputType type) {
+                        ui::TextInputType type,
+                        ui::TextInputFlags flags = ui::TEXT_INPUT_FLAG_NONE) {
     ui::mojom::TextInputState state;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
index 2185bfb8..e1f6dcf 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -60,6 +60,8 @@
 #import "third_party/ocmock/ocmock_extensions.h"
 #include "ui/base/cocoa/secure_password_input.h"
 #include "ui/base/ime/mojom/text_input_state.mojom.h"
+#include "ui/base/ime/text_input_flags.h"
+#include "ui/base/ime/text_input_type.h"
 #import "ui/base/test/cocoa_helper.h"
 #import "ui/base/test/scoped_fake_nswindow_focus.h"
 #include "ui/base/ui_base_features.h"
@@ -1824,9 +1826,11 @@
   }
 
   void SetTextInputType(RenderWidgetHostViewBase* view,
-                        ui::TextInputType type) {
+                        ui::TextInputType type,
+                        ui::TextInputFlags flags = ui::TEXT_INPUT_FLAG_NONE) {
     ui::mojom::TextInputState state;
     state.type = type;
+    state.flags = flags;
     view->TextInputStateChanged(state);
   }
 
@@ -2089,6 +2093,66 @@
   EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
 }
 
+TEST_F(InputMethodMacTest, SecureHasBeenAPasswordInput) {
+  ASSERT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+  ASSERT_EQ(text_input_manager(), tab_view()->GetTextInputManager());
+
+  // RenderWidgetHostViewMacTest.LostFocusAndGotFocusOnSetActive checks the
+  // GotFocus()/LostFocus() rules, just silence the warnings here.
+  EXPECT_CALL(*host_, Focus()).Times(::testing::AnyNumber());
+  EXPECT_CALL(*host_, Blur()).Times(::testing::AnyNumber());
+
+  [window_ makeFirstResponder:tab_view()->GetInProcessNSView()];
+
+  // Shouldn't enable secure input if it's not a "has been a password"
+  // textfield.
+  tab_view()->SetActive(true);
+  EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+
+  SetTextInputType(child_view_, ui::TEXT_INPUT_TYPE_TEXT,
+                   ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD);
+  ASSERT_EQ(child_widget_.get(), text_input_manager()->GetActiveWidget());
+  ASSERT_EQ(text_input_manager(), tab_view()->GetTextInputManager());
+  ASSERT_EQ(ui::TEXT_INPUT_TYPE_TEXT, tab_view()->GetTextInputType());
+
+  // Single matched calls immediately update IsPasswordInputEnabled().
+  tab_view()->SetActive(true);
+  EXPECT_TRUE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+
+  tab_view()->SetActive(false);
+  EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+}
+
+TEST_F(InputMethodMacTest, SecureHasBeenACustomPasswordInput) {
+  ASSERT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+  ASSERT_EQ(text_input_manager(), tab_view()->GetTextInputManager());
+
+  // RenderWidgetHostViewMacTest.LostFocusAndGotFocusOnSetActive checks the
+  // GotFocus()/LostFocus() rules, just silence the warnings here.
+  EXPECT_CALL(*host_, Focus()).Times(::testing::AnyNumber());
+  EXPECT_CALL(*host_, Blur()).Times(::testing::AnyNumber());
+
+  [window_ makeFirstResponder:tab_view()->GetInProcessNSView()];
+
+  // Shouldn't enable secure input if it's not a "has been a custom password"
+  // textfield.
+  tab_view()->SetActive(true);
+  EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+
+  SetTextInputType(child_view_, ui::TEXT_INPUT_TYPE_TEXT,
+                   ui::TEXT_INPUT_FLAG_HAS_BEEN_CUSTOM_PASSWORD);
+  ASSERT_EQ(child_widget_.get(), text_input_manager()->GetActiveWidget());
+  ASSERT_EQ(text_input_manager(), tab_view()->GetTextInputManager());
+  ASSERT_EQ(ui::TEXT_INPUT_TYPE_TEXT, tab_view()->GetTextInputType());
+
+  // Single matched calls immediately update IsPasswordInputEnabled().
+  tab_view()->SetActive(true);
+  EXPECT_TRUE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+
+  tab_view()->SetActive(false);
+  EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+}
+
 // This test creates a test view to mimic a child frame's view and verifies that
 // calling ImeCancelComposition on either the child view or the tab's view will
 // always lead to a call to cancelComposition on the cocoa view.
diff --git a/ui/base/ime/ash/input_method_ash_unittest.cc b/ui/base/ime/ash/input_method_ash_unittest.cc
index de29d25..ec96a46 100644
--- a/ui/base/ime/ash/input_method_ash_unittest.cc
+++ b/ui/base/ime/ash/input_method_ash_unittest.cc
@@ -429,7 +429,7 @@
   ime.SetFocusedTextInputClient(nullptr);
 }
 
-TEST_F(InputMethodAshTest, HasBeenPasswordShouldTriggerPassowrd) {
+TEST_F(InputMethodAshTest, HasBeenPasswordShouldTriggerPassword) {
   InputMethodAsh ime(this);
   FakeTextInputClient fake_text_input_client(ui::TEXT_INPUT_TYPE_TEXT);
   fake_text_input_client.SetFlags(ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD);
@@ -442,6 +442,19 @@
   ime.SetFocusedTextInputClient(nullptr);
 }
 
+TEST_F(InputMethodAshTest, HasBeenCustomPasswordShouldTriggerPassword) {
+  InputMethodAsh ime(this);
+  FakeTextInputClient fake_text_input_client(ui::TEXT_INPUT_TYPE_TEXT);
+  fake_text_input_client.SetFlags(ui::TEXT_INPUT_FLAG_HAS_BEEN_CUSTOM_PASSWORD);
+
+  ime.SetFocusedTextInputClient(&fake_text_input_client);
+
+  EXPECT_EQ(mock_ime_engine_handler_->last_text_input_context().type,
+            ui::TEXT_INPUT_TYPE_PASSWORD);
+
+  ime.SetFocusedTextInputClient(nullptr);
+}
+
 TEST_F(InputMethodAshTest, GetTextInputClient) {
   EXPECT_EQ(this, input_method_ash_->GetTextInputClient());
   input_method_ash_->SetFocusedTextInputClient(nullptr);
diff --git a/ui/base/ime/input_method_base_unittest.cc b/ui/base/ime/input_method_base_unittest.cc
index 64a5364c..50b98f0 100644
--- a/ui/base/ime/input_method_base_unittest.cc
+++ b/ui/base/ime/input_method_base_unittest.cc
@@ -311,5 +311,19 @@
   EXPECT_EQ(TEXT_INPUT_TYPE_PASSWORD, input_method.GetTextInputType());
 }
 
+TEST_F(InputMethodBaseTest, SetsPasswordWhenHasBeenCustomPassword) {
+  FakeTextInputClient fake_text_input_client(ui::TEXT_INPUT_TYPE_TEXT);
+
+  ClientChangeVerifier verifier;
+  verifier.ExpectClientChange(nullptr, &fake_text_input_client);
+  MockInputMethodBase input_method(&verifier);
+
+  fake_text_input_client.SetFlags(ui::TEXT_INPUT_FLAG_HAS_BEEN_CUSTOM_PASSWORD);
+
+  input_method.SetFocusedTextInputClient(&fake_text_input_client);
+
+  EXPECT_EQ(TEXT_INPUT_TYPE_PASSWORD, input_method.GetTextInputType());
+}
+
 }  // namespace
 }  // namespace ui
diff --git a/ui/views/cocoa/bridged_native_widget_unittest.mm b/ui/views/cocoa/bridged_native_widget_unittest.mm
index 105a9c38..ed21905 100644
--- a/ui/views/cocoa/bridged_native_widget_unittest.mm
+++ b/ui/views/cocoa/bridged_native_widget_unittest.mm
@@ -1101,6 +1101,13 @@
   text_field->SetTextInputFlags(ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD);
   EXPECT_FALSE(ns_view_.inputContext);
 
+  text_field =
+      InstallTextField(test_string, ui::TEXT_INPUT_TYPE_TEXT);
+  EXPECT_TRUE(ns_view_.inputContext);
+
+  text_field->SetTextInputFlags(ui::TEXT_INPUT_FLAG_HAS_BEEN_CUSTOM_PASSWORD);
+  EXPECT_FALSE(ns_view_.inputContext);
+
   GetNSWindowHost()->text_input_host()->SetTextInputClient(nullptr);
   EXPECT_FALSE(ns_view_.inputContext);
diff --git a/ui/views/controls/textfield/textfield_unittest.cc b/ui/views/controls/textfield/textfield_unittest.cc
index 899f467..09a1268f 100644
--- a/ui/views/controls/textfield/textfield_unittest.cc
+++ b/ui/views/controls/textfield/textfield_unittest.cc
@@ -38,6 +38,7 @@
 #include "ui/base/ime/input_method_base.h"
 #include "ui/base/ime/text_edit_commands.h"
 #include "ui/base/ime/text_input_client.h"
+#include "ui/base/ime/text_input_flags.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -5261,6 +5262,7 @@
 }
 
 #if BUILDFLAG(IS_MAC)
+
 // Tests to see if the BiDi submenu items are updated correctly when the
 // textfield's text direction is changed.
 TEST_F(TextfieldTest, TextServicesContextMenuTextDirectionTest) {
@@ -5327,6 +5329,45 @@
   textfield_->OnBlur();
   EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
 }
+
+TEST_F(TextfieldTest, SecureHasBeenAPasswordInput) {
+  InitTextfield();
+  ASSERT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+
+  // Shouldn't enable secure input if it's not a password textfield.
+  textfield_->OnFocus();
+  EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+
+  textfield_->SetTextInputFlags(textfield_->GetTextInputFlags() |
+                                ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD);
+
+  // Single matched calls immediately update IsPasswordInputEnabled().
+  textfield_->OnFocus();
+  EXPECT_TRUE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+
+  textfield_->OnBlur();
+  EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+}
+
+TEST_F(TextfieldTest, SecureHasBeenACustomPasswordInput) {
+  InitTextfield();
+  ASSERT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+
+  // Shouldn't enable secure input if it's not a password textfield.
+  textfield_->OnFocus();
+  EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+
+  textfield_->SetTextInputFlags(textfield_->GetTextInputFlags() |
+                                ui::TEXT_INPUT_FLAG_HAS_BEEN_CUSTOM_PASSWORD);
+
+  // Single matched calls immediately update IsPasswordInputEnabled().
+  textfield_->OnFocus();
+  EXPECT_TRUE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+
+  textfield_->OnBlur();
+  EXPECT_FALSE(ui::ScopedPasswordInputEnabler::IsPasswordInputEnabled());
+}
+
 #endif  // BUILDFLAG(IS_MAC)
 
 TEST_F(TextfieldTest, AccessibilitySelectionEvents) {
Loading diff…

Original Bug Report

reported by [email protected]

Potential macOS secure input bypass for refocused revealed password/PIN fields

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: On macOS, views::Textfield::OnFocus decides whether to enable secure event input purely by checking if the input type is TEXT_INPUT_TYPE_PASSWORD. If a password or PIN field is revealed and then refocused, this check fails because the input type becomes TEXT_INPUT_TYPE_TEXT, bypassing the secure-event-input protection. Consequently, subsequent keystrokes typed into the field can be exposed to local processes using CGEventTap keyloggers.

Affected files:

  • ui/views/controls/textfield/textfield.cc

Estimated timestamp from git blame: 2018-03-12

Description

There is a potential defense-in-depth bypass in Chrome’s browser UI on macOS where the macOS-specific EnableSecureEventInput protection is disabled for refocused, revealed password or PIN fields.

On macOS, views::Textfield::OnFocus decides whether to instantiate ui::ScopedPasswordInputEnabler (which calls Carbon’s EnableSecureEventInput() and restricts IMEs to ASCII-only inputs) purely based on the raw text_input_type_ field:

// ui/views/controls/textfield/textfield.cc
#if BUILDFLAG(IS_MAC)
  if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) {
    password_input_enabler_ =
        std::make_unique<ui::ScopedPasswordInputEnabler>();
  }
#endif

However, when a user clicks the “reveal eye” toggle button (e.g., in the GPM PIN sheet or the Save/Update Password bubble), the following sequence potentially occurs:

  1. Clicking the button forces the textfield to blur (SetRequestFocusOnPress(true)), which runs Textfield::OnBlur() and destroys the password_input_enabler_ (disabling secure event input).
  2. The button’s toggle callback updates the textfield’s input type to TEXT_INPUT_TYPE_TEXT and adds the flag TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD to its input flags.
  3. When the user refocuses the textfield to edit or finish typing, OnFocus() evaluates text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD. Since the type is now TEXT_INPUT_TYPE_TEXT, this check evaluates to false, and the enabler is not recreated.
  4. Consequently, the user enters sensitive information into the field with EnableSecureEventInput inactive, exposing their keystrokes to any local processes on macOS utilizing a CGEventTap keylogger.

Note: These are potential steps based on a static code analysis, as our tooling does not currently have the capability to execute code or run dynamic proofs of concept.

Potential Step-by-Step Reproduction

  1. Run a local application with CGEventTap permissions on macOS to monitor system-level keystrokes.
  2. Open the Google Password Manager (GPM) arbitrary-PIN sheet or trigger the Save/Update Password bubble in Chrome.
  3. Click the reveal button to unmask the input field.
  4. Focus back into the unmasked input field.
  5. Type characters into the field and observe whether they are intercepted by the local CGEventTap listener (which should normally be blocked when entering password/PIN data).

Suggested Fix

To resolve this issue, the secure-event-input check in views::Textfield::OnFocus should also evaluate the TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD flag so that revealed password/PIN fields maintain their protection upon refocusing:

#if BUILDFLAG(IS_MAC)
  if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD ||
      (text_input_flags_ & ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD)) {
    password_input_enabler_ =
        std::make_unique<ui::ScopedPasswordInputEnabler>();
  }
#endif

Evaluated with Chrome root at commit: 87214e6721f6c34afd9181b80769a24c0c601c50


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