CVE-2026-113129
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/extensions/extension_keybinding_registry.cc |
modified | |
IN_PROC_BROWSER_TEST_Fchrome/browser/extensions/extension_keybinding_interactive_apitest.cc |
modified | |
ifchrome/browser/extensions/BUILD.gn |
modified |
Files Changed
chrome/browser/extensions/extension_keybinding_registry.ccchrome/browser/extensions/extension_keybinding_registry.hchrome/browser/extensions/extension_keybinding_interactive_apitest.ccchrome/browser/extensions/extension_commands_global_registry.ccchrome/browser/extensions/BUILD.gn
Patch
From 52226357d4755a5e8e50e029ffcbf190a5d61c2a Mon Sep 17 00:00:00 2001
From: Eva Su <evasu@chromium.org>
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 <tjudkins@chromium.org>
Commit-Queue: Eva Su <evasu@chromium.org>
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" ]
+ }
}
}
Regression Test / PoC
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
Original Bug Report
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.ccchrome/browser/ui/views/extensions/extension_keybinding_registry_views.ccchrome/browser/ui/browser_window/internal/browser_window_features.ccchrome/browser/extensions/commands/command_service.ccextensions/browser/permissions/active_tab_permission_granter.ccextensions/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
- Auto-assignment and Non-exclusivity: Unlike regular keyboard shortcuts, media keys are automatically assigned to extensions upon installation without user confirmation (
CommandService::CanAutoAssignreturnstruefor media keys). Furthermore, media keys are non-exclusive; multiple extensions can register a listener for the exact same media key simultaneously. - Registry and Delegate: In desktop browser windows, extensions are bound via
ExtensionKeybindingRegistryViews. This registry is constructed with anExtensionKeybindingRegistryDelegateTabStrip, whoseGetWebContentsForExtension()method returns theWebContentsof the currently active tab. - Event Interception: When the user has Chrome focused and presses a media key, the event is caught (via
CGEventTapon Mac orviews::FocusManageron Windows) and routed toExtensionKeybindingRegistry::NotifyEventTargets. - Improper Escalation:
ExecuteCommandsiterates through all extensions listening to that specific media key and callsCommandExecutedfor each one. InsideExtensionKeybindingRegistry::CommandExecuted, the code retrieves the active tab’sWebContentsfrom the delegate and callsActiveTabPermissionGranter::GrantIfRequested(extension). - Fake User Gesture: Finally, the
commands.onCommandevent is dispatched to the extension withevent->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.
- Create an MV3 extension requesting
activeTabandscriptingpermissions. - In
manifest.json, register a command with"suggested_key": { "default": "MediaPlayPause" }. - Add a background service worker that listens to
chrome.commands.onCommandand callschrome.scripting.executeScriptto readdocument.body.innerTextfrom the active tab. - Install the extension.
- Navigate to a sensitive site in the active tab (e.g.,
https://mail.google.com). - Press the hardware Play/Pause key on the keyboard.
- 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.