Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper access control in Extensions
DescriptionImproper access control in Extensions
ComponentExtensions
Sub-componentExtensions
Bug ClassLogic Error
Tracker501738293
Fix commit52226357d475 (chromium/src) +78/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
chrome/browser/extensions/extension_keybinding_registry.cc
modified
IN_PROC_BROWSER_TEST_F
chrome/browser/extensions/extension_keybinding_interactive_apitest.cc
modified
if
chrome/browser/extensions/BUILD.gn
modified

Files Changed

  • chrome/browser/extensions/extension_keybinding_registry.cc
  • chrome/browser/extensions/extension_keybinding_registry.h
  • chrome/browser/extensions/extension_keybinding_interactive_apitest.cc
  • chrome/browser/extensions/extension_commands_global_registry.cc
  • chrome/browser/extensions/BUILD.gn
From 52226357d4755a5e8e50e029ffcbf190a5d61c2a Mon Sep 17 00:00:00 2001
From: Eva Su <[email protected]>
Date: Thu, 17 Sep 2026 18:18:42 -0700
Subject: [PATCH] [Extensions] Do not grant activeTab permission for hardware media keys

Previously, ExtensionKeybindingRegistry::CommandExecuted unconditionally
granted activeTab permissions via ActiveTabPermissionGranter to any
extension whose keybinding command was executed.

Hardware media keys (e.g., MediaPlayPause, MediaStop, MediaNextTrack)
are ambient, non-exclusive system keys. When a user presses a media key,
they intend to control media playback and do not intend to give full
active tab host permissions (DOM/cookie access) to whichever extension
happens to be registered to listen for media key commands.

This CL updates ExtensionKeybindingRegistry::CommandExecuted to inspect
the command's accelerator via CommandService and bypass
ActiveTabPermissionGranter::GrantIfRequested when the command
accelerator is a media key.

Fixed: 501738293
Change-Id: I6d1c497dca8bc57de0383c9721d5a5b47d18b805
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8379102
Reviewed-by: Tim <[email protected]>
Commit-Queue: Eva Su <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1700364}
---
diff --git a/chrome/browser/extensions/extension_commands_global_registry.cc b/chrome/browser/extensions/extension_commands_global_registry.cc
index d03181d20..b38212d6a 100644
--- a/chrome/browser/extensions/extension_commands_global_registry.cc
+++ b/chrome/browser/extensions/extension_commands_global_registry.cc
@@ -227,7 +227,7 @@
 void ExtensionCommandsGlobalRegistry::ExecuteCommand(
     const ExtensionId& extension_id,
     const std::string& command_id) {
-  CommandExecuted(extension_id, command_id);
+  CommandExecuted(extension_id, command_id, ui::Accelerator());
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_keybinding_registry.cc b/chrome/browser/extensions/extension_keybinding_registry.cc
index 798cdfd..fb7152b 100644
--- a/chrome/browser/extensions/extension_keybinding_registry.cc
+++ b/chrome/browser/extensions/extension_keybinding_registry.cc
@@ -226,7 +226,8 @@
 
 void ExtensionKeybindingRegistry::CommandExecuted(
     const ExtensionId& extension_id,
-    const std::string& command) {
+    const std::string& command,
+    const ui::Accelerator& accelerator) {
   if (!browser_context_) {
     return;
   }
@@ -251,7 +252,7 @@
     ActiveTabPermissionGranter* granter =
         web_contents ? ActiveTabPermissionGranter::FromWebContents(web_contents)
                      : nullptr;
-    if (granter) {
+    if (granter && !accelerator.IsMediaKey()) {
       granter->GrantIfRequested(extension);
     }
 
@@ -439,7 +440,7 @@
     }
 
     if (extension_id.empty() || it->first == extension_id) {
-      CommandExecuted(it->first, it->second);
+      CommandExecuted(it->first, it->second, accelerator);
       executed = true;
     }
   }
diff --git a/chrome/browser/extensions/extension_keybinding_registry.h b/chrome/browser/extensions/extension_keybinding_registry.h
index 70a84b7..58a01d48 100644
--- a/chrome/browser/extensions/extension_keybinding_registry.h
+++ b/chrome/browser/extensions/extension_keybinding_registry.h
@@ -117,7 +117,8 @@
 
   // Notifies appropriate parties that a command has been executed.
   void CommandExecuted(const ExtensionId& extension_id,
-                       const std::string& command);
+                       const std::string& command,
+                       const ui::Accelerator& accelerator);
 
   // Add event target (extension_id, command name) to the target list of
   // `accelerator`. Note that only media keys can have more than one event
diff --git a/chrome/browser/extensions/extension_keybinding_interactive_apitest.cc b/chrome/browser/extensions/extension_keybinding_interactive_apitest.cc
index e5b7a1e..38ca7e4 100644
--- a/chrome/browser/extensions/extension_keybinding_interactive_apitest.cc
+++ b/chrome/browser/extensions/extension_keybinding_interactive_apitest.cc
@@ -16,6 +16,9 @@
 #include "chrome/browser/ui/extensions/extensions_container.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_model.h"
 #include "chrome/browser/ui/ui_features.h"
+#if BUILDFLAG(IS_MAC)
+#include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
+#endif
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "components/sessions/content/session_tab_helper.h"
@@ -26,6 +29,7 @@
 #include "content/public/test/javascript_test_observer.h"
 #include "extensions/browser/extension_action.h"
 #include "extensions/browser/extension_action_manager.h"
+#include "extensions/browser/permissions/active_tab_permission_granter.h"
 #include "extensions/browser/test_event_router_observer.h"
 #include "extensions/buildflags/buildflags.h"
 #include "extensions/common/api/extension_action/action_info.h"
@@ -547,6 +551,69 @@
   ASSERT_TRUE(catcher.GetNextResult());
 }
 
+// Test that media keys do not grant activeTab permissions to an extension.
+IN_PROC_BROWSER_TEST_F(CommandsApiTest, MediaKeysDoNotGrantActiveTab) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(R"(
+    {
+      "name": "Media Key Active Tab Test",
+      "version": "1.0",
+      "manifest_version": 3,
+      "permissions": ["activeTab"],
+      "background": {
+        "service_worker": "background.js"
+      },
+      "commands": {
+        "MediaStop": {
+          "suggested_key": {
+            "default": "MediaStop"
+          },
+          "description": "Media stop command"
+        }
+      }
+    })");
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"(
+    chrome.commands.onCommand.addListener((command) => {
+      chrome.test.sendMessage("command_received");
+    });
+  )");
+
+  ExtensionTestMessageListener listener("command_received");
+  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  content::WebContents* web_contents = GetActiveWebContents();
+  ASSERT_TRUE(NavigateToURL(web_contents, embedded_test_server()->GetURL(
+                                              "/extensions/test_file.txt")));
+
+  ActiveTabPermissionGranter* granter =
+      ActiveTabPermissionGranter::FromWebContents(web_contents);
+  ASSERT_TRUE(granter);
+  EXPECT_FALSE(granter->IsGranted(extension));
+
+  // Activate the Media Stop key. On Mac, ui_controls cannot synthesize NSEvents
+  // for media keycodes (VKEY_MEDIA_STOP), so trigger the accelerator directly
+  // via ExtensionKeybindingRegistryViews.
+#if BUILDFLAG(IS_MAC)
+  ExtensionKeybindingRegistryViews* registry =
+      ExtensionKeybindingRegistryViews::From(GetBrowserWindowInterface());
+  ASSERT_TRUE(registry);
+  registry->AcceleratorPressed(
+      ui::Accelerator(ui::VKEY_MEDIA_STOP, ui::EF_NONE));
+#else
+  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(GetBrowserWindowInterface(),
+                                              ui::VKEY_MEDIA_STOP, false, false,
+                                              false, false));
+#endif
+
+  EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+  // Verify that activeTab permission was NOT granted by the media key command.
+  EXPECT_FALSE(granter->IsGranted(extension));
+}
+
 #if BUILDFLAG(IS_CHROMEOS) && !defined(NDEBUG)
 // TODO(dtseng): Test times out on Chrome OS debug. See crbug.com/40383617.
 #define MAYBE_ContinuePropagation DISABLED_ContinuePropagation
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index bf9c424c..559abe8 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -3275,5 +3275,9 @@
     if (use_ozone) {
       deps += [ "//ui/ozone" ]
     }
+
+    if (toolkit_views) {
+      deps += [ "//chrome/browser/ui/views/extensions:extensions_views_utils" ]
+    }
   }
 }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/extensions/extension_keybinding_interactive_apitest.cc b/chrome/browser/extensions/extension_keybinding_interactive_apitest.cc
index e5b7a1e..38ca7e4 100644
--- a/chrome/browser/extensions/extension_keybinding_interactive_apitest.cc
+++ b/chrome/browser/extensions/extension_keybinding_interactive_apitest.cc
@@ -16,6 +16,9 @@
 #include "chrome/browser/ui/extensions/extensions_container.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_model.h"
 #include "chrome/browser/ui/ui_features.h"
+#if BUILDFLAG(IS_MAC)
+#include "chrome/browser/ui/views/extensions/extension_keybinding_registry_views.h"
+#endif
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "components/sessions/content/session_tab_helper.h"
@@ -26,6 +29,7 @@
 #include "content/public/test/javascript_test_observer.h"
 #include "extensions/browser/extension_action.h"
 #include "extensions/browser/extension_action_manager.h"
+#include "extensions/browser/permissions/active_tab_permission_granter.h"
 #include "extensions/browser/test_event_router_observer.h"
 #include "extensions/buildflags/buildflags.h"
 #include "extensions/common/api/extension_action/action_info.h"
@@ -547,6 +551,69 @@
   ASSERT_TRUE(catcher.GetNextResult());
 }
 
+// Test that media keys do not grant activeTab permissions to an extension.
+IN_PROC_BROWSER_TEST_F(CommandsApiTest, MediaKeysDoNotGrantActiveTab) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(R"(
+    {
+      "name": "Media Key Active Tab Test",
+      "version": "1.0",
+      "manifest_version": 3,
+      "permissions": ["activeTab"],
+      "background": {
+        "service_worker": "background.js"
+      },
+      "commands": {
+        "MediaStop": {
+          "suggested_key": {
+            "default": "MediaStop"
+          },
+          "description": "Media stop command"
+        }
+      }
+    })");
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), R"(
+    chrome.commands.onCommand.addListener((command) => {
+      chrome.test.sendMessage("command_received");
+    });
+  )");
+
+  ExtensionTestMessageListener listener("command_received");
+  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+  ASSERT_TRUE(extension);
+
+  content::WebContents* web_contents = GetActiveWebContents();
+  ASSERT_TRUE(NavigateToURL(web_contents, embedded_test_server()->GetURL(
+                                              "/extensions/test_file.txt")));
+
+  ActiveTabPermissionGranter* granter =
+      ActiveTabPermissionGranter::FromWebContents(web_contents);
+  ASSERT_TRUE(granter);
+  EXPECT_FALSE(granter->IsGranted(extension));
+
+  // Activate the Media Stop key. On Mac, ui_controls cannot synthesize NSEvents
+  // for media keycodes (VKEY_MEDIA_STOP), so trigger the accelerator directly
+  // via ExtensionKeybindingRegistryViews.
+#if BUILDFLAG(IS_MAC)
+  ExtensionKeybindingRegistryViews* registry =
+      ExtensionKeybindingRegistryViews::From(GetBrowserWindowInterface());
+  ASSERT_TRUE(registry);
+  registry->AcceleratorPressed(
+      ui::Accelerator(ui::VKEY_MEDIA_STOP, ui::EF_NONE));
+#else
+  ASSERT_TRUE(ui_test_utils::SendKeyPressSync(GetBrowserWindowInterface(),
+                                              ui::VKEY_MEDIA_STOP, false, false,
+                                              false, false));
+#endif
+
+  EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+  // Verify that activeTab permission was NOT granted by the media key command.
+  EXPECT_FALSE(granter->IsGranted(extension));
+}
+
 #if BUILDFLAG(IS_CHROMEOS) && !defined(NDEBUG)
 // TODO(dtseng): Test times out on Chrome OS debug. See crbug.com/40383617.
 #define MAYBE_ContinuePropagation DISABLED_ContinuePropagation
Loading diff…

Original Bug Report

reported by [email protected]

ActiveTab permission bypass via hardware media keys

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 without the Chrome Security team.

Overview: Hardware media key presses automatically grant the activeTab permission to all extensions registered for that key. Because media keys are auto-assigned and non-exclusive, malicious extensions can obtain host access to sensitive foreground tabs without any directed user interaction.

Affected files:

  • chrome/browser/extensions/extension_keybinding_registry.cc
  • chrome/browser/ui/views/extensions/extension_keybinding_registry_views.cc
  • chrome/browser/ui/browser_window/internal/browser_window_features.cc
  • chrome/browser/extensions/commands/command_service.cc
  • extensions/browser/permissions/active_tab_permission_granter.cc
  • extensions/common/command.cc

Estimated timestamp from git blame: 2026-03-05

The activeTab permission is designed to provide temporary host access to an extension only when the user explicitly invokes it (e.g., by clicking its toolbar icon or using a specific, user-configured keyboard shortcut). This ensures that host access is granted only through a deliberate user gesture directed at the extension.

However, a logic flaw in how hardware media keys (e.g., Play/Pause, Next Track) are handled allows extensions to bypass this requirement and obtain ambient host access to the active tab.

Technical Details

  1. Auto-assignment and Non-exclusivity: Unlike regular keyboard shortcuts, media keys are automatically assigned to extensions upon installation without user confirmation (CommandService::CanAutoAssign returns true for media keys). Furthermore, media keys are non-exclusive; multiple extensions can register a listener for the exact same media key simultaneously.
  2. Registry and Delegate: In desktop browser windows, extensions are bound via ExtensionKeybindingRegistryViews. This registry is constructed with an ExtensionKeybindingRegistryDelegateTabStrip, whose GetWebContentsForExtension() method returns the WebContents of the currently active tab.
  3. Event Interception: When the user has Chrome focused and presses a media key, the event is caught (via CGEventTap on Mac or views::FocusManager on Windows) and routed to ExtensionKeybindingRegistry::NotifyEventTargets.
  4. Improper Escalation: ExecuteCommands iterates through all extensions listening to that specific media key and calls CommandExecuted for each one. Inside ExtensionKeybindingRegistry::CommandExecuted, the code retrieves the active tab’s WebContents from the delegate and calls ActiveTabPermissionGranter::GrantIfRequested(extension).
  5. Fake User Gesture: Finally, the commands.onCommand event is dispatched to the extension with event->user_gesture = EventRouter::UserGestureState::kEnabled.

Because ActiveTabPermissionGranter does not independently verify if the action was a directed user gesture, it grants full host permissions to every extension listening to the media key. The extension can then immediately call chrome.scripting.executeScript (authorized by the fake user gesture state) to silently exfiltrate data from the active tab.

Since activeTab and scripting permissions do not trigger installation warnings, this provides a reliable, zero-warning path for malicious extensions to steal data when a user presses a media key.

Potential Reproduction Steps

Note: These are suggested steps based on static analysis.

  1. Create an MV3 extension requesting activeTab and scripting permissions.
  2. In manifest.json, register a command with "suggested_key": { "default": "MediaPlayPause" }.
  3. Add a background service worker that listens to chrome.commands.onCommand and calls chrome.scripting.executeScript to read document.body.innerText from the active tab.
  4. Install the extension.
  5. Navigate to a sensitive site in the active tab (e.g., https://mail.google.com).
  6. Press the hardware Play/Pause key on the keyboard.
  7. Observe that the extension silently executes the script and accesses the page content, despite no directed user gesture toward the extension.

Suggested Fix

ExtensionKeybindingRegistry::CommandExecuted should not automatically grant activeTab permissions for commands triggered by non-exclusive, ambient keys like hardware media keys. The logic at line 199 (granter->GrantIfRequested(extension)) should be conditionally bypassed if command.accelerator().IsMediaKey() is true. Alternatively, activeTab should strictly require a verifiable, directed user gesture, and media keys should perhaps dispatch events without UserGestureState::kEnabled.

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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
Links in the report