Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in IME
DescriptionInappropriate implementation in IME
ComponentIME
Bug ClassLogic Error
Tracker519233776
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 <avi@chromium.org>
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 <kerenzhu@chromium.org>
Commit-Queue: Avi Drissman <avi@chromium.org>
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 vm...@google.com

Potential secure event input bypass in RenderWidgetHostViewMac on password type toggles

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: RenderWidgetHostViewMac fails to check the TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD flag when determining whether to keep secure event input enabled. When a password field is dynamically toggled to a text field (such as via a ‘show password’ button), secure input is deactivated. This potentially exposes sensitive password keystrokes to system-wide keyboard event taps and third-party input method editors (IMEs) on macOS.

Affected files:

  • content/browser/renderer_host/render_widget_host_view_mac.mm

Estimated timestamp from git blame: 2018-03-12

Root Cause

In content/browser/renderer_host/render_widget_host_view_mac.mm, SetTextInputActive manages the lifecycle of ui::ScopedPasswordInputEnabler, which wraps macOS’s system-wide EnableSecureEventInput() security control. However, the decision to hold this enabler is based purely on the un-coerced text input type:

// content/browser/renderer_host/render_widget_host_view_mac.mm:1790-1800
void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
  const bool should_enable_password_input =
      active && GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD;
  if (should_enable_password_input) {
    password_input_enabler_ =
        std::make_unique<ui::ScopedPasswordInputEnabler>();
  } else {
    password_input_enabler_.reset();
  }
  update_windows_timer_.Stop();
}
// content/browser/renderer_host/render_widget_host_view_mac.mm:405-409
ui::TextInputType RenderWidgetHostViewMac::GetTextInputType() {
  if (!GetActiveWidget())
    return ui::TEXT_INPUT_TYPE_NONE;
  return text_input_manager_->GetTextInputState()->type;
}

When a password input field’s type attribute is dynamically toggled to 'text' (e.g., to reveal the password to the user), Blink correctly maintains the kWebTextInputFlagHasBeenPasswordField flag (ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD in the browser) to signal that the field contains sensitive credentials that should still be protected. This flag is set in third_party/blink/renderer/core/editing/ime/input_method_controller.cc:

if (auto* input = DynamicTo<HTMLInputElement>(element)) {
  if (input->HasBeenPasswordField())
    flags |= kWebTextInputFlagHasBeenPasswordField;
}

Unlike Chrome’s Views counterpart (ui/views/cocoa/text_input_host.mm:247-258) or ui::InputMethodBase::GetTextInputType(), RenderWidgetHostViewMac does not consult this flag. As a result, when a password field is toggled, GetTextInputType() returns ui::TEXT_INPUT_TYPE_TEXT, evaluating should_enable_password_input to false. This triggers password_input_enabler_.reset(), disabling secure keyboard entry while the user is actively typing their password.

Potential Trigger Path

Note: These are suggested steps to replicate the behavior based on a code-level review, as our tooling does not have the capability to run code.

  1. Focus a password field on a web page (e.g. <input type="password">). This triggers EnableSecureEventInput() via ScopedPasswordInputEnabler in the browser process.
  2. Execute a script (or click a page element) that dynamically changes the input type to text: inputElement.type = 'text'. This preserves the TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD flag in the renderer’s TextInputState but changes type to TEXT_INPUT_TYPE_TEXT.
  3. Observe that RenderWidgetHostViewMac::SetTextInputActive re-evaluates, sees the new type, and resets password_input_enabler_, actively calling DisableSecureEventInput() at the OS level while the user is still focused and typing.

Impact

This is a potential macOS-specific defense-in-depth bypass. When the secure event input protection is disabled, any background keyloggers (such as those using Carbon/Cocoa CGEventTap with accessibility/input-monitoring privileges) and third-party IMEs can capture the sensitive password keystrokes as the user types them into the revealed field.

Suggested Fix

Update RenderWidgetHostViewMac::SetTextInputActive to check for ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD when evaluating whether to enable secure password input, matching the approach used elsewhere in Chromium:

void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
  bool has_been_password = false;
  if (text_input_manager_ && text_input_manager_->GetTextInputState()) {
    has_been_password = (text_input_manager_->GetTextInputState()->flags &
                         ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD) != 0;
  }
  const bool should_enable_password_input =
      active && (GetTextInputType() == ui::TEXT_INPUT_TYPE_PASSWORD || has_been_password);
  ...

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