Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactPolicy bypass in Extensions
DescriptionPolicy bypass in Extensions
ComponentExtensions
Bug ClassLogic Error
Tracker523637452
Fix commitfb54f5d902ed (chromium/src) +40/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
chrome/browser/extensions/api/tabs/tabs_api.cc
modified
IN_PROC_BROWSER_TEST_F
chrome/browser/extensions/api/tabs/tabs_test.cc
modified

Files Changed

  • chrome/browser/extensions/api/tabs/tabs_api.cc
  • chrome/browser/extensions/api/tabs/tabs_test.cc
From fb54f5d902ed184faed71d78e799b5d968a35c06 Mon Sep 17 00:00:00 2001
From: Bhaskar Sharma <[email protected]>
Date: Wed, 24 Jun 2026 10:46:16 -0700
Subject: [PATCH] Block chrome.tabs.duplicate for Isolated Web Apps.

Isolated Web App URLs ('isolated-app:' scheme) are not allowed to be
opened via chrome.tabs.duplicate.
Extensions must use chrome.windows.create to open Isolated Web Apps.

Fixed: 523637452
Change-Id: I47b01d3826dc63d1eb00e35376685dacad52b211
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7986105
Reviewed-by: Reilly Grant <[email protected]>
Reviewed-by: Vlad Krot <[email protected]>
Reviewed-by: Andrew Rayskiy <[email protected]>
Commit-Queue: Bhaskar Sharma <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1651828}
---

diff --git a/chrome/browser/extensions/api/tabs/tabs_api.cc b/chrome/browser/extensions/api/tabs/tabs_api.cc
index be91971..ed88aa88 100644
--- a/chrome/browser/extensions/api/tabs/tabs_api.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_api.cc
@@ -149,6 +149,8 @@
 constexpr char kTabsUpdateIwaUrlNotAllowedError[] =
     "Cannot navigate to a URL with the 'isolated-app:' scheme via tabs.update. "
     "Use windows.create instead.";
+constexpr char kCannotDuplicateIwaTabError[] =
+    "The tab of an Isolated Web App cannot be duplicated.";
 #endif
 
 #if BUILDFLAG(IS_ANDROID)
@@ -2372,6 +2374,13 @@
     return RespondNow(Error(ExtensionTabUtil::kTabStripNotEditableError));
   }
 
+#if !BUILDFLAG(IS_ANDROID)
+  if (web_contents->GetLastCommittedURL().SchemeIs(
+          webapps::kIsolatedAppScheme)) {
+    return RespondNow(Error(kCannotDuplicateIwaTabError));
+  }
+#endif
+
   TabListInterface* tab_list = TabListInterface::From(browser);
   if (!tab_list) {
     return RespondNow(Error(tabs_constants::kCannotDuplicateTab,
diff --git a/chrome/browser/extensions/api/tabs/tabs_test.cc b/chrome/browser/extensions/api/tabs/tabs_test.cc
index 53ac97a..2e7f55a 100644
--- a/chrome/browser/extensions/api/tabs/tabs_test.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_test.cc
@@ -1587,6 +1587,7 @@
 using ExtensionApiTabsIwaMoveTest = ExtensionIwaTestBase;
 
 using ExtensionApiTabsIwaNavigateTest = ExtensionIwaTestBase;
+using ExtensionApiTabsIwaDuplicateTest = ExtensionIwaTestBase;
 
 // `tabs.create` does not support `isolated-app:` URLs, even when targeting an
 // existing IWA window. `windows.create` is the supported entry point and
@@ -1706,6 +1707,35 @@
   EXPECT_EQ(error, "The tab of an Isolated Web App cannot be moved.");
 }
 
+// Tests that duplicating an IWA tab via the chrome.tabs.duplicate Extension API
+// is disallowed.
+IN_PROC_BROWSER_TEST_F(ExtensionApiTabsIwaDuplicateTest,
+                       DuplicateTabDisallowed) {
+  web_app::IsolatedWebAppUrlInfo url_info = InstallAndTrustBundle();
+  BrowserWindowInterface* iwa_browser = OpenIwa(url_info);
+  ASSERT_TRUE(iwa_browser);
+
+  TabListInterface* iwa_tab_list = TabListInterface::From(iwa_browser);
+  ASSERT_EQ(iwa_tab_list->GetTabCount(), 1);
+  auto* iwa_web_contents = iwa_tab_list->GetActiveTab()->GetContents();
+  content::WaitForLoadStop(iwa_web_contents);
+  int iwa_tab_id = ExtensionTabUtil::GetTabId(iwa_web_contents);
+
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("ExtensionApiTabsIwaDuplicateTest")
+          .AddAPIPermission("tabs")
+          .Build();
+
+  auto function = base::MakeRefCounted<TabsDuplicateFunction>();
+  function->set_extension(extension);
+
+  std::string args = base::StringPrintf("[%d]", iwa_tab_id);
+  std::string error = api_test_utils::RunFunctionAndReturnError(
+      function.get(), args, profile());
+
+  EXPECT_EQ(error, "The tab of an Isolated Web App cannot be duplicated.");
+}
+
 IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DuplicateTab) {
   content::OpenURLParams params(GURL(url::kAboutBlankURL), content::Referrer(),
                                 WindowOpenDisposition::NEW_FOREGROUND_TAB,
@@ -5507,6 +5537,7 @@
   // Wait for the JS test to catch the event and send "success".
   ASSERT_TRUE(success_listener.WaitUntilSatisfied());
 }
+
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace extensions
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/extensions/api/tabs/tabs_test.cc b/chrome/browser/extensions/api/tabs/tabs_test.cc
index 53ac97a..2e7f55a 100644
--- a/chrome/browser/extensions/api/tabs/tabs_test.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_test.cc
@@ -1587,6 +1587,7 @@
 using ExtensionApiTabsIwaMoveTest = ExtensionIwaTestBase;
 
 using ExtensionApiTabsIwaNavigateTest = ExtensionIwaTestBase;
+using ExtensionApiTabsIwaDuplicateTest = ExtensionIwaTestBase;
 
 // `tabs.create` does not support `isolated-app:` URLs, even when targeting an
 // existing IWA window. `windows.create` is the supported entry point and
@@ -1706,6 +1707,35 @@
   EXPECT_EQ(error, "The tab of an Isolated Web App cannot be moved.");
 }
 
+// Tests that duplicating an IWA tab via the chrome.tabs.duplicate Extension API
+// is disallowed.
+IN_PROC_BROWSER_TEST_F(ExtensionApiTabsIwaDuplicateTest,
+                       DuplicateTabDisallowed) {
+  web_app::IsolatedWebAppUrlInfo url_info = InstallAndTrustBundle();
+  BrowserWindowInterface* iwa_browser = OpenIwa(url_info);
+  ASSERT_TRUE(iwa_browser);
+
+  TabListInterface* iwa_tab_list = TabListInterface::From(iwa_browser);
+  ASSERT_EQ(iwa_tab_list->GetTabCount(), 1);
+  auto* iwa_web_contents = iwa_tab_list->GetActiveTab()->GetContents();
+  content::WaitForLoadStop(iwa_web_contents);
+  int iwa_tab_id = ExtensionTabUtil::GetTabId(iwa_web_contents);
+
+  scoped_refptr<const Extension> extension =
+      ExtensionBuilder("ExtensionApiTabsIwaDuplicateTest")
+          .AddAPIPermission("tabs")
+          .Build();
+
+  auto function = base::MakeRefCounted<TabsDuplicateFunction>();
+  function->set_extension(extension);
+
+  std::string args = base::StringPrintf("[%d]", iwa_tab_id);
+  std::string error = api_test_utils::RunFunctionAndReturnError(
+      function.get(), args, profile());
+
+  EXPECT_EQ(error, "The tab of an Isolated Web App cannot be duplicated.");
+}
+
 IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DuplicateTab) {
   content::OpenURLParams params(GURL(url::kAboutBlankURL), content::Referrer(),
                                 WindowOpenDisposition::NEW_FOREGROUND_TAB,
@@ -5507,6 +5537,7 @@
   // Wait for the JS test to catch the event and send "success".
   ASSERT_TRUE(success_listener.WaitUntilSatisfied());
 }
+
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace extensions
Loading diff…

Original Bug Report

reported by [email protected]

IWA isolation bypass and origin spoofing via chrome.tabs.duplicate on popup windows

Flapjack, 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: A logic flaw in the chrome.tabs.duplicate extension API allows a malicious extension to duplicate an Isolated Web App (IWA) popup window, producing a corrupted window missing its AppBrowserController. This missing controller causes the location bar to be hidden (enabling high-fidelity origin spoofing) and bypasses security checks that prevent moving IWA tabs into standard browser windows.

Affected files:

  • chrome/browser/ui/browser_commands.cc
  • chrome/browser/extensions/api/tabs/tabs_api.cc
  • chrome/browser/ui/window_feature_controller/window_feature_controller.cc
  • chrome/browser/ui/navigator/browser_navigator.cc

Estimated timestamp from git blame: Unknown (Google3 checkout)

Description

A vulnerability exists in the implementation of the chrome.tabs.duplicate extension API that allows a malicious extension with the tabs permission to bypass security boundaries for Isolated Web Apps (IWAs). When duplicating a tab from an IWA popup window (type TYPE_APP_POPUP), the resulting window is incorrectly initialized, stripping it of its AppBrowserController.

When chrome.tabs.duplicate is called on a TYPE_APP_POPUP window, execution eventually routes to DuplicateTabAt in chrome/browser/ui/browser_commands.cc. Because a popup window does not support a tab strip, the code falls back to calling CreateAndShowNewWindowWithContents.

Inside CreateAndShowNewWindowWithContents, there is a DCHECK(original_browser->GetType() != BrowserWindowInterface::TYPE_APP_POPUP); which is compiled out in release builds. When handling the popup in production, the logic falls through to an else block because original_browser->GetType() is not TYPE_APP.

new_browser = Browser::Create(Browser::CreateParams(
    original_browser->GetType(), original_browser->GetProfile(), true));

This constructor creates a Browser::CreateParams object but fails to copy the app_name from the original browser. Consequently, when BrowserWindowFeatures::Init runs for the new window, web_app::MaybeCreateAppBrowserController fails to identify the application (since the app_name is empty) and returns nullptr. The window is thus created without an AppBrowserController.

This missing controller has two severe consequences:

  1. Origin Spoofing: In WindowFeatureController::AppPopupBrowserSupportsWindowFeature, the visibility of the location bar (kFeatureLocationBar) requires a valid app_controller_. Because it is null, the function returns false. The popup window displays the content without any origin indicator (Omnibox), enabling high-fidelity spoofing.
  2. IWA Isolation Bypass: The missing AppBrowserController causes validation logic in other extension APIs to fail. In chrome/browser/extensions/api/tabs/tabs_api.cc, WindowsCreateFunction::ValidateTab checks if a tab belongs to an IWA before allowing it to be moved. It relies on AppBrowserController::From(source_browser) to identify IWA windows. Since the duplicated window lacks this controller, the check is bypassed. The extension can now move the privileged IWA tab into a standard TYPE_NORMAL browser window using chrome.tabs.move.

Navigating this incorrectly placed tab causes a CHECK crash in BrowserNavigator, leading to a denial of service.

(Note: These are potential steps, as our tooling agent does not run code directly.)

Potential Steps to Reproduce

  1. Install a Chrome extension with the tabs permission.
  2. Open an Isolated Web App and trigger a popup window (e.g., via window.open('...', '_blank', 'popup=yes')).
  3. Using the extension, call chrome.tabs.duplicate(popupTabId) to duplicate the IWA popup.
  4. Observe that the new window contains the IWA content but lacks a location bar/Omnibox, allowing arbitrary content spoofing.
  5. From the extension, call chrome.tabs.move(duplicatedTabId, {windowId: standardWindowId}).
  6. The IWA tab is successfully moved to a normal window, bypassing the isolation boundary.

Suggested Fix

Update CreateAndShowNewWindowWithContents in chrome/browser/ui/browser_commands.cc to explicitly handle TYPE_APP_POPUP windows by extracting the app_name from the original browser and using the correct Browser::CreateParams factory method (e.g., Browser::CreateParams::CreateForAppPopup).

Evaluated with Chrome root at commit: 65b3256311f3ab6fb9870eaa522de7e6dd2663bb


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