CVE-2026-17882
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/extensions/api/tabs/tabs_api.cc |
modified | |
IN_PROC_BROWSER_TEST_Fchrome/browser/extensions/api/tabs/tabs_test.cc |
modified |
Files Changed
chrome/browser/extensions/api/tabs/tabs_api.ccchrome/browser/extensions/api/tabs/tabs_test.cc
Patch
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
Regression Test / PoC
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
Original Bug Report
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.ccchrome/browser/extensions/api/tabs/tabs_api.ccchrome/browser/ui/window_feature_controller/window_feature_controller.ccchrome/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:
- Origin Spoofing: In
WindowFeatureController::AppPopupBrowserSupportsWindowFeature, the visibility of the location bar (kFeatureLocationBar) requires a validapp_controller_. Because it is null, the function returns false. The popup window displays the content without any origin indicator (Omnibox), enabling high-fidelity spoofing. - IWA Isolation Bypass: The missing
AppBrowserControllercauses validation logic in other extension APIs to fail. Inchrome/browser/extensions/api/tabs/tabs_api.cc,WindowsCreateFunction::ValidateTabchecks if a tab belongs to an IWA before allowing it to be moved. It relies onAppBrowserController::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 standardTYPE_NORMALbrowser window usingchrome.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
- Install a Chrome extension with the
tabspermission. - Open an Isolated Web App and trigger a popup window (e.g., via
window.open('...', '_blank', 'popup=yes')). - Using the extension, call
chrome.tabs.duplicate(popupTabId)to duplicate the IWA popup. - Observe that the new window contains the IWA content but lacks a location bar/Omnibox, allowing arbitrary content spoofing.
- From the extension, call
chrome.tabs.move(duplicatedTabId, {windowId: standardWindowId}). - 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.