Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Extensions
DescriptionInappropriate implementation in Extensions
ComponentExtensions
Bug ClassLogic Error
Tracker513148038
Fix commitf08221cc7cb2 (chromium/src) +39/-96
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
registry_for_active_window_
chrome/browser/extensions/extension_commands_global_registry.cc
modified
shortcut_handling_suspended_
chrome/browser/extensions/extension_keybinding_registry.cc
modified
if
chrome/browser/extensions/extension_keybinding_registry.cc
modified
TabListInterface
chrome/browser/extensions/extension_keybinding_registry.h
modified
BrowserContext
chrome/browser/extensions/extension_keybinding_registry.h
modified
WebContents
chrome/browser/extensions/extension_keybinding_registry.h
modified
Delegate
chrome/browser/extensions/extension_keybinding_registry.h
modified
ExtensionKeybindingRegistryDelegateAndroid
chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc
modified
toolbar_view_model_
chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc
modified

Files Changed

  • chrome/browser/extensions/extension_commands_global_registry.cc
  • chrome/browser/extensions/extension_keybinding_registry.cc
  • chrome/browser/extensions/extension_keybinding_registry.h
  • chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc
  • chrome/browser/ui/android/extensions/extension_keybinding_registry_android.h
From f08221cc7cb21bd45158ef18cc17bc1e82e6403f Mon Sep 17 00:00:00 2001
From: Hidehiko Abe <[email protected]>
Date: Sun, 17 May 2026 19:54:43 -0700
Subject: [PATCH] Remove ExtensionKeybingindRegistry::Delegate.

The delegate was designed to inject active tab, which needed different
operations depending on platforms.
Now, we have TabListInterface, abstracting the platform gap,
so we can simply rely on the code.

BUG=513148038
TEST=Tryjob

Change-Id: I3e09b8171d0291960f2a859a51b97863b68be466
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7851696
Reviewed-by: James Cook <[email protected]>
Commit-Queue: Hidehiko Abe <[email protected]>
Reviewed-by: Masa Fujita <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1631947}
---

diff --git a/chrome/browser/extensions/extension_commands_global_registry.cc b/chrome/browser/extensions/extension_commands_global_registry.cc
index cd22521f..bc6a5f1 100644
--- a/chrome/browser/extensions/extension_commands_global_registry.cc
+++ b/chrome/browser/extensions/extension_commands_global_registry.cc
@@ -67,8 +67,8 @@
 ExtensionCommandsGlobalRegistry::ExtensionCommandsGlobalRegistry(
     content::BrowserContext* context)
     : ExtensionKeybindingRegistry(context,
-                                  ExtensionKeybindingRegistry::ALL_EXTENSIONS,
-                                  nullptr),
+                                  /*tab_list_interface=*/nullptr,
+                                  ExtensionKeybindingRegistry::ALL_EXTENSIONS),
       browser_context_(context),
       registry_for_active_window_(nullptr) {
   Init();
diff --git a/chrome/browser/extensions/extension_keybinding_registry.cc b/chrome/browser/extensions/extension_keybinding_registry.cc
index 99308a1..4fec35c 100644
--- a/chrome/browser/extensions/extension_keybinding_registry.cc
+++ b/chrome/browser/extensions/extension_keybinding_registry.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/extensions/tab_helper.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/tab_list/tab_list_interface.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/media_keys_listener_manager.h"
 #include "content/public/browser/web_contents.h"
@@ -40,11 +41,11 @@
 
 ExtensionKeybindingRegistry::ExtensionKeybindingRegistry(
     content::BrowserContext* context,
-    ExtensionFilter extension_filter,
-    std::unique_ptr<Delegate> delegate)
+    TabListInterface* tab_list_interface,
+    ExtensionFilter extension_filter)
     : browser_context_(context),
+      tab_list_interface_(tab_list_interface),
       extension_filter_(extension_filter),
-      delegate_(std::move(delegate)),
       shortcut_handling_suspended_(false) {
   extension_registry_observation_.Observe(
       ExtensionRegistry::Get(browser_context_));
@@ -185,12 +186,13 @@
   args.Append(command);
 
   base::Value tab_value;
-  if (delegate_) {
+  if (tab_list_interface_) {
+    auto* active_tab = tab_list_interface_->GetActiveTab();
     content::WebContents* web_contents =
-        delegate_->GetWebContentsForExtension();
+        active_tab ? active_tab->GetContents() : nullptr;
     // Grant before sending the event so that the permission is granted before
     // the extension acts on the command. NOTE: The Global Commands handler does
-    // not set the delegate as it deals only with named commands (not
+    // not set the TabListInterface as it deals only with named commands (not
     // page/browser actions that are associated with the current page directly).
     ActiveTabPermissionGranter* granter =
         web_contents ? ActiveTabPermissionGranter::FromWebContents(web_contents)
diff --git a/chrome/browser/extensions/extension_keybinding_registry.h b/chrome/browser/extensions/extension_keybinding_registry.h
index 00a91f6..aa7068c8 100644
--- a/chrome/browser/extensions/extension_keybinding_registry.h
+++ b/chrome/browser/extensions/extension_keybinding_registry.h
@@ -21,9 +21,10 @@
 
 static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
 
+class TabListInterface;
+
 namespace content {
 class BrowserContext;
-class WebContents;
 }
 
 namespace ui {
@@ -46,19 +47,13 @@
     PLATFORM_APPS_ONLY
   };
 
-  class Delegate {
-   public:
-    virtual ~Delegate() = default;
-
-    // Returns the currently active WebContents, or nullptr if there is none.
-    virtual content::WebContents* GetWebContentsForExtension() = 0;
-  };
-
   // If `extension_filter` is not ALL_EXTENSIONS, only keybindings by
   // by extensions that match the filter will be registered.
+  // `tab_list_interface` is the one for the connected browser window
+  // instance. It must outlive this instance.
   ExtensionKeybindingRegistry(content::BrowserContext* context,
-                              ExtensionFilter extension_filter,
-                              std::unique_ptr<Delegate> delegate);
+                              TabListInterface* tab_list_interface,
+                              ExtensionFilter extension_filter);
 
   ExtensionKeybindingRegistry(const ExtensionKeybindingRegistry&) = delete;
   ExtensionKeybindingRegistry& operator=(const ExtensionKeybindingRegistry&) =
@@ -176,11 +171,11 @@
 
   raw_ptr<content::BrowserContext> browser_context_;
 
+  const raw_ptr<TabListInterface> tab_list_interface_;
+
   // What extensions to register keybindings for.
   ExtensionFilter extension_filter_;
 
-  std::unique_ptr<Delegate> delegate_;
-
   // Maps an accelerator to a list of string pairs (extension id, command name)
   // for commands that have been registered. This keeps track of the targets for
   // the keybinding event (which named command to call in which extension). On
diff --git a/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc b/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc
index 6807b43d4..d26f431 100644
--- a/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc
+++ b/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc
@@ -21,49 +21,14 @@
 #include "ui/events/platform_event.h"
 
 namespace extensions {
-namespace {
-
-class ExtensionKeybindingRegistryDelegateAndroid
-    : public ExtensionKeybindingRegistry::Delegate {
- public:
-  explicit ExtensionKeybindingRegistryDelegateAndroid(
-      content::BrowserContext* context)
-      : context_(context) {}
-
-  ExtensionKeybindingRegistryDelegateAndroid(
-      const ExtensionKeybindingRegistryDelegateAndroid& other) = delete;
-  ExtensionKeybindingRegistryDelegateAndroid& operator=(
-      const ExtensionKeybindingRegistryDelegateAndroid& other) = delete;
-
-  ~ExtensionKeybindingRegistryDelegateAndroid() override = default;
-
-  content::WebContents* GetWebContentsForExtension() override {
-    for (const TabModel* model : TabModelList::models()) {
-      if (model->GetProfile() != context_) {
-        continue;
-      }
-      if (model->IsActiveModel()) {
-        return model->GetActiveWebContents();
-      }
-    }
-
-    return nullptr;
-  }
-
- private:
-  const raw_ptr<content::BrowserContext> context_;
-};
-
-}  // namespace
 
 ExtensionKeybindingRegistryAndroid::ExtensionKeybindingRegistryAndroid(
     content::BrowserContext* context,
+    TabListInterface* tab_list_interface,
     ExtensionsToolbarViewModel* toolbar_view_model)
-    : ExtensionKeybindingRegistry(
-          context,
-          ExtensionFilter::ALL_EXTENSIONS,
-          std::make_unique<ExtensionKeybindingRegistryDelegateAndroid>(
-              context)),
+    : ExtensionKeybindingRegistry(context,
+                                  tab_list_interface,
+                                  ExtensionFilter::ALL_EXTENSIONS),
       toolbar_view_model_(toolbar_view_model) {
   Init();
 }
diff --git a/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.h b/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.h
index f1a41e44..7fa61ee6 100644
--- a/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.h
+++ b/chrome/browser/ui/android/extensions/extension_keybinding_registry_android.h
@@ -9,6 +9,8 @@
 #include "chrome/browser/ui/extensions/extensions_toolbar_view_model.h"
 #include "ui/base/accelerators/accelerator.h"
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential activeTab permission misrouting in Desktop-Android multi-instance scenarios

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: In experimental Desktop-Android builds, extension keyboard shortcuts resolve the active tab using a global list, ignoring window boundaries. This may allow an extension to gain host access to a sensitive site in a background window when the user interacts with the extension in a separate foreground window.

Affected files:

  • chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc
  • chrome/browser/ui/android/extensions/extensions_toolbar_android.cc

Estimated timestamp from git blame: 2026-03-05

Summary

In the experimental Desktop-Android implementation of Chrome (enabled via the GN argument is_desktop_android = true), extension keyboard shortcuts (commands) may incorrectly resolve the target WebContents by iterating through a process-global TabModelList. In multi-instance or split-screen scenarios, this logic fails to account for multiple windows each having its own ‘active’ tab model. This potentially allows an extension to be granted the activeTab permission for a sensitive site open in a background window, even when the user intended to trigger the shortcut on a different foreground window.

Root Cause Analysis

In chrome/browser/ui/android/extensions/extensions_toolbar_android.cc, the ExtensionsToolbarAndroid instance is window-scoped, but it initializes its keybinding registry delegate without a reference to its parent window:

keybinding_registry_(std::make_unique<ExtensionKeybindingRegistryAndroid>(
    browser_->GetProfile(),
    toolbar_view_model_.get())),

The registry’s delegate, ExtensionKeybindingRegistryDelegateAndroid, implements GetWebContentsForExtension in chrome/browser/ui/android/extensions/extension_keybinding_registry_android.cc by searching through all available tab models in the process:

content::WebContents* GetWebContentsForExtension() override {
  for (const TabModel* model : TabModelList::models()) {
    if (model->GetProfile() != context_) {
      continue;
    }
    if (model->IsActiveModel()) {
      return model->GetActiveWebContents();
    }
  }
  return nullptr;
}

TabModelList::models() is a process-global singleton containing TabModel instances from all open windows. The TabModel::IsActiveModel() property indicates whether a model is currently selected (e.g., regular vs. incognito) within its respective window’s TabModelSelector.

In a multi-window environment where two regular-profile windows are open, both regular TabModel instances will return true for IsActiveModel(). Because the delegate iterates through the global list in insertion order, it will deterministically return the WebContents from the window that was opened first, regardless of which window actually received the keyboard event.

Security Impact

The activeTab permission model ensures that an extension only receives host access to the specific tab the user is actively targeting via a deliberate interaction. This logic flaw breaks that security boundary by misrouting the permission grant to a background window. A malicious extension with a configured keyboard shortcut and activeTab permission could gain cross-origin scripting access and read sensitive data from a high-value site in a background window when the user intended to invoke the shortcut on a benign site in the foreground.

Potential Reproduction Steps

Note: These steps are based on source code analysis of the experimental Desktop-Android feature.

  1. Build Chrome for Android with the GN argument is_desktop_android = true.
  2. Install an extension that requests the activeTab permission and defines a keyboard command (e.g., Ctrl+Shift+Y) in its manifest.
  3. Open Window A and navigate its active tab to a sensitive site (e.g., https://bank.example).
  4. Using Android multi-instance features (e.g., split-screen), open Window B and navigate its active tab to a benign site (e.g., https://news.example).
  5. Focus Window B and press the extension’s keyboard shortcut on a hardware keyboard.
  6. The extension’s command listener may receive the tab object for the site in Window A instead of the intended site in Window B, effectively granting the extension unauthorized access to Window A.

Suggested Fix

The ExtensionKeybindingRegistryDelegateAndroid should be scoped to its specific window. Instead of searching the global TabModelList, the delegate should be initialized with a reference to the BrowserWindowInterface (available in ExtensionsToolbarAndroid) and use that reference to resolve the active WebContents within the correct window context.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


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