Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in Extensions
DescriptionIncorrect security UI in Extensions
ComponentExtensions
Bug ClassLogic Error
Tracker521601450
Fix commit0971f338ea32 (chromium/src) +46/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
ExtensionsMenuAllowButton
chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
modified
SectionContainer
chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
modified

Files Changed

  • chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
  • chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
From 0971f338ea328c6fce6f55d273b2c43fcbe698bf Mon Sep 17 00:00:00 2001
From: Eva Su <[email protected]>
Date: Thu, 11 Jun 2026 15:16:08 -0700
Subject: [PATCH] [Extensions] Add input protection to extensions menu "Allow" button

According to crbug.com/521601450, an extension can call the host access
request API, triggering a layout reflow that shifts the menu elements.
This can potentially place the newly appeared 'Allow' button directly
under the user's cursor during an active click, resulting in an
unintended permission grant.

This CL adds input protection to the “Allow” button in the menu. This is
similar to the change we made for the ExtensionsRequestAccessButton in
crrev.com/c/7876925.

Fixed: 521601450
Change-Id: Iac4136c5bdf855b5b345dd710f0f8d7498536852
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7927809
Reviewed-by: Andrea Orru <[email protected]>
Commit-Queue: Eva Su <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1645645}
---

diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
index 2d46c7e..e01b131d 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
@@ -48,6 +48,7 @@
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/controls/separator.h"
+#include "ui/views/input_event_activation_protector.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/flex_layout_view.h"
 #include "ui/views/layout/layout_types.h"
@@ -94,6 +95,47 @@
 DEFINE_UI_CLASS_PROPERTY_TYPE(ExtensionIdWrapper*)
 DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(ExtensionIdWrapper, kExtensionIdKey)
 
+// A button in the extensions menu requesting access section that grants one
+// time site access to the extension. It uses an input event activation
+// protector to prevent unintended clicks.
+class ExtensionsMenuAllowButton : public views::MdTextButton {
+ public:
+  ExtensionsMenuAllowButton() = default;
+  ExtensionsMenuAllowButton(const ExtensionsMenuAllowButton&) = delete;
+  ExtensionsMenuAllowButton& operator=(const ExtensionsMenuAllowButton&) =
+      delete;
+  ~ExtensionsMenuAllowButton() override = default;
+
+  // views::View:
+  void VisibilityChanged(views::View* starting_from, bool is_visible) override {
+    views::MdTextButton::VisibilityChanged(starting_from, is_visible);
+    input_protector_.VisibilityChanged(is_visible);
+  }
+
+  void OnBoundsChanged(const gfx::Rect& previous_bounds) override {
+    views::MdTextButton::OnBoundsChanged(previous_bounds);
+    input_protector_.MaybeUpdateViewProtectedTimeStamp();
+  }
+
+  void NotifyClick(const ui::Event& event) override {
+    if (input_protector_.IsPossiblyUnintendedInteraction(
+            event, /*allow_key_events=*/false)) {
+      return;
+    }
+    views::MdTextButton::NotifyClick(event);
+  }
+
+ private:
+  views::InputEventActivationProtector input_protector_;
+};
+
+BEGIN_VIEW_BUILDER(/* No Export */,
+                   ExtensionsMenuAllowButton,
+                   views::MdTextButton)
+END_VIEW_BUILDER
+
+DEFINE_VIEW_BUILDER(/* No Export */, ExtensionsMenuAllowButton)
+
 // Base class for a container inside the extensions menu.
 class SectionContainer : public views::BoxLayoutView {
  public:
@@ -288,7 +330,7 @@
                   .SetAccessibleName(l10n_util::GetStringFUTF16(
                       IDS_EXTENSIONS_MENU_REQUESTS_ACCESS_SECTION_DISMISS_BUTTON_ACCESSIBLE_NAME,
                       request.extension_name)),
-              views::Builder<views::MdTextButton>()
+              views::Builder<ExtensionsMenuAllowButton>()
                   .SetCallback(base::BindRepeating(
                       &ExtensionsMenuHandler::OnAllowExtensionClicked,
                       base::Unretained(menu_handler_), request.extension_id))
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
index c33c5acb..ed0967c 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
@@ -44,6 +44,7 @@
 #include "ui/views/controls/styled_label.h"
 #include "ui/views/vector_icons.h"
 #include "ui/views/view_utils.h"
+#include "ui/views/views_switches.h"
 
 namespace {
 
@@ -193,6 +194,8 @@
 
 void ExtensionsMenuMainPageViewUnitTest::SetUp() {
   ExtensionsToolbarUnitTest::SetUp();
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      views::switches::kDisableInputEventActivationProtectionForTesting);
   // Menu needs web contents at construction, so we need to add them to every
   // test.
   web_contents_tester_ = AddWebContentsAndGetTester();
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
index c33c5acb..ed0967c 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view_unittest.cc
@@ -44,6 +44,7 @@
 #include "ui/views/controls/styled_label.h"
 #include "ui/views/vector_icons.h"
 #include "ui/views/view_utils.h"
+#include "ui/views/views_switches.h"
 
 namespace {
 
@@ -193,6 +194,8 @@
 
 void ExtensionsMenuMainPageViewUnitTest::SetUp() {
   ExtensionsToolbarUnitTest::SetUp();
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      views::switches::kDisableInputEventActivationProtectionForTesting);
   // Menu needs web contents at construction, so we need to add them to every
   // test.
   web_contents_tester_ = AddWebContentsAndGetTester();
Loading diff…

Original Bug Report

reported by [email protected]

Potential clickjacking risk on Extensions Menu 'Allow' button due to missing input protection

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 ‘Allow’ button in the Extensions Menu main page view lacks an input event activation protector. An extension can call the gesture-free host access request API, triggering a layout reflow that shifts the menu elements. This can potentially place the newly appeared ‘Allow’ button directly under the user’s cursor during an active click, resulting in an unintended permission grant.

Affected files:

  • chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc

Estimated timestamp from git blame: 2023-05-18

Potential Security Issue: Lack of InputEventActivationProtector on Extensions Menu ‘Allow’ Button

Root Cause Analysis

In chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc, the method AddExtensionRequestingAccess constructs the per-extension Allow button as a standard views::MdTextButton without any unintended-interaction guard:

views::Builder<views::MdTextButton>()
    .SetCallback(base::BindRepeating(
        &ExtensionsMenuHandler::OnAllowExtensionClicked,
        base::Unretained(menu_handler_), request.extension_id))
    .SetStyle(ui::ButtonStyle::kText)
    ...
    .Build();

When a host access request is dynamically added or removed, the parent view synchronously changes the visibility of requests_section_ and triggers a layout reflow via SizeToPreferredSize():

void ExtensionsMenuMainPageView::SetOptionalSectionVisibility(
    ExtensionsMenuViewModel::OptionalSection optional_section) {
  ...
  case ExtensionsMenuViewModel::OptionalSection::kHostAccessRequests:
    reload_section_->SetVisible(false);
    requests_section_->SetVisible(
        !requests_entries_view_->children().empty());
    break;
  ...
  SizeToPreferredSize();
}

Because the requests_section_ is inserted vertically above menu_entries_, making it visible pushes all standard menu entries downward.

While sibling surfaces (like ExtensionsRequestAccessButton in extensions_request_access_button.cc) protect against accidental clicks using a views::InputEventActivationProtector, the Allow button in the main page of the extensions menu has no such protection. As a result, click events are processed immediately upon layout shifts.

Potential Trigger Steps

Please note: The following are suggested potential steps as our tooling agent has not executed this code to verify a functional proof of concept.

  1. Install an extension that requests host permissions for a specific site (e.g., https://example.com/*), but configure the permissions to be “withheld” by default (or set to “on click”).
  2. Open https://example.com/ in the active browser tab.
  3. Open the Extensions Toolbar Menu (the puzzle icon).
  4. The extension calls chrome.permissions.addHostAccessRequest({ tabId: activeTabId }) from its service worker or content script (which does not require a user gesture).
  5. This call dynamically inserts a new request row into the extensions menu. Because this section is located above the extension list, the list shifts down synchronously.
  6. If the user was moving their mouse cursor to click an element in the extension list, the newly inserted “Allow” button can shift directly under the cursor, intercepting the click.
  7. The click lands on the “Allow” button, immediately executing the permission grant callback and persistently writing the kOnSite host permission to ExtensionPrefs on disk without user intent.

Suggested Fix

Integrate views::InputEventActivationProtector into ExtensionsMenuMainPageView (or wrap the button callback handler to validate click events), similar to how input protection is implemented in ExtensionsRequestAccessButton. Before executing OnAllowExtensionClicked, verify that the click event is not a potentially unintended interaction by checking that a sufficient delay has elapsed since the layout reflow occurred.

Evaluated with Chrome root at commit: 3947e01999a53d4e2382e39736cb79d79c7dffcf


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