Medium chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactConfused deputy in Contextual Tasks
DescriptionConfused deputy in Contextual Tasks
ComponentContextual Tasks
Sub-componentContextual Tasks
Bug ClassAuthorization Error
Tracker501914204
Fix commit046a4f465be8 (chromium/src) +89/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Files Changed

  • chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc
  • chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc
From 046a4f465be8cb0725317ec8424f394c97e55dc8 Mon Sep 17 00:00:00 2001
From: Duncan Mercer <mercerd@google.com>
Date: Mon, 14 Sep 2026 18:51:37 -0700
Subject: [PATCH] [Contextual Tasks] Restrict guest link schemes

Links originating from the embedded <webview> guest can be
re-dispatched via OnThreadLinkClicked or OpenUrl in a new tab without
carrying over the guest navigation context. For non-web schemes, this
can bypass navigation throttles and web_accessible_resources checks.

- Add an early return in ContextualTasksUiService::HandleNavigationImpl
  to reject non-HTTP(S) and non-about:blank URLs from the embedded
  guest before dispatching to thread link / OpenUrl helpers.
- Add unit tests verifying that chrome-extension:// URLs from the
  embedded guest are not intercepted, with and without
  kAimTriggeredThreadLinks.

Fixed: b:501914204
Change-Id: If880ef3c43b779b5bc341c4aa5320c9f59a2d609
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8402735
Commit-Queue: Duncan Mercer <mercerd@google.com>
Reviewed-by: Juan Mojica <juanmojica@google.com>
Cr-Commit-Position: refs/heads/main@{#1697728}
---
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc b/chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc
index 6a66bcd5..4406827b 100644
--- a/chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc
+++ b/chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc
@@ -2132,6 +2132,21 @@
       }
     }
 
+    // Link navigation helpers below (OnThreadLinkClicked / OpenUrl) issue
+    // a fresh load in a new tab without carrying over the guest renderer's
+    // navigation context (initiator origin, renderer-initiated flag).
+    // Restrict them to HTTP(S) and about:blank (used for window tracking).
+    // All other schemes (e.g. chrome-extension://, chrome://, file://,
+    // data:, javascript:) should not be re-dispatched by the browser and are
+    // instead left to the standard navigation flow and navigation throttles.
+    if (!url_params.url.SchemeIsHTTPOrHTTPS() &&
+        !url_params.url.IsAboutBlank()) {
+      OMNIBOX_LOG("nav_trace")
+          << "ContextualTasks navigation trace: HandleNavigationImpl "
+             "returning false, non-web scheme from embedded page";
+      return false;
+    }
+
     // On mobile phones without window tracking, link navigations that request
     // new window creation cannot create separate windows. Intercept them here
     // and route to `OnThreadLinkClicked` for bottom-sheet handling, whereas
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc b/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc
index 55b4eff..834554d 100644
--- a/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc
+++ b/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc
@@ -1563,6 +1563,80 @@
   run_loop.Run();
 }
 
+TEST_F(ContextualTasksUiServiceTest,
+       Navigation_NonWebScheme_FromEmbeddedGuest_NotIntercepted) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  // Test both with kAimTriggeredThreadLinks enabled and disabled.
+  scoped_feature_list.InitAndEnableFeature(kAimTriggeredThreadLinks);
+
+  GURL extension_url("chrome-extension://someextensionid/secret.html");
+  GURL host_web_content_url(chrome::kChromeUIContextualTasksURL);
+
+  ON_CALL(*aim_eligibility_service_, IsAimUrl(_, _))
+      .WillByDefault(Return(false));
+  ON_CALL(*aim_eligibility_service_, IsAimHost(_, _))
+      .WillByDefault(Return(false));
+
+  auto web_contents = content::WebContentsTester::CreateTestWebContents(
+      profile_.get(), content::SiteInstance::Create(profile_.get()));
+  content::WebContentsTester::For(web_contents.get())
+      ->SetLastCommittedURL(host_web_content_url);
+  tabs::MockTabInterface tab;
+  ON_CALL(tab, GetContents).WillByDefault(Return(web_contents.get()));
+
+  EXPECT_CALL(*service_for_nav_, OnThreadLinkClicked(_, _, _, _, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OpenUrl(_, _, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OnNonThreadNavigationInTab(_, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OnNavigationToAiPageIntercepted(_, _, _))
+      .Times(0);
+
+  // When viewed in a tab with a non-web scheme, HandleNavigationImpl should
+  // return false and avoid dispatching any thread link or OpenUrl helpers.
+  EXPECT_FALSE(service_for_nav_->HandleNavigationImpl(
+      CreateOpenUrlParams(extension_url, true), web_contents.get(), &tab,
+      /*is_from_embedded_page=*/true,
+      /*from_can_create_window=*/false, /*is_same_site_or_from_ui=*/true, false,
+      std::nullopt, std::nullopt, blink::mojom::WindowFeatures()));
+
+  // Same check when viewed in side panel (null tab).
+  EXPECT_FALSE(service_for_nav_->HandleNavigationImpl(
+      CreateOpenUrlParams(extension_url, true), web_contents.get(), nullptr,
+      /*is_from_embedded_page=*/true,
+      /*from_can_create_window=*/false, /*is_same_site_or_from_ui=*/true, false,
+      std::nullopt, std::nullopt, blink::mojom::WindowFeatures()));
+}
+
+TEST_F(ContextualTasksUiServiceTest,
+       Navigation_NonWebScheme_AimTriggeredThreadLinksDisabled_NotIntercepted) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(kAimTriggeredThreadLinks);
+
+  GURL extension_url("chrome-extension://someextensionid/secret.html");
+  GURL host_web_content_url(chrome::kChromeUIContextualTasksURL);
+
+  ON_CALL(*aim_eligibility_service_, IsAimUrl(_, _))
+      .WillByDefault(Return(false));
+  ON_CALL(*aim_eligibility_service_, IsAimHost(_, _))
+      .WillByDefault(Return(false));
+
+  auto web_contents = content::WebContentsTester::CreateTestWebContents(
+      profile_.get(), content::SiteInstance::Create(profile_.get()));
+  content::WebContentsTester::For(web_contents.get())
+      ->SetLastCommittedURL(host_web_content_url);
+
+  EXPECT_CALL(*service_for_nav_, OnThreadLinkClicked(_, _, _, _, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OpenUrl(_, _, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OnNonThreadNavigationInTab(_, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OnNavigationToAiPageIntercepted(_, _, _))
+      .Times(0);
+
+  EXPECT_FALSE(service_for_nav_->HandleNavigationImpl(
+      CreateOpenUrlParams(extension_url, true), web_contents.get(), nullptr,
+      /*is_from_embedded_page=*/true,
+      /*from_can_create_window=*/false, /*is_same_site_or_from_ui=*/true, false,
+      std::nullopt, std::nullopt, blink::mojom::WindowFeatures()));
+}
+
 // If the search results page is navigated to while viewing the UI in the side
 // panel (e.g. no tab tied to the WebContents), ensure the correct event is
 // fired.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc b/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc
index 55b4eff..834554d 100644
--- a/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc
+++ b/chrome/browser/contextual_tasks/contextual_tasks_ui_service_unittest.cc
@@ -1563,6 +1563,80 @@
   run_loop.Run();
 }
 
+TEST_F(ContextualTasksUiServiceTest,
+       Navigation_NonWebScheme_FromEmbeddedGuest_NotIntercepted) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  // Test both with kAimTriggeredThreadLinks enabled and disabled.
+  scoped_feature_list.InitAndEnableFeature(kAimTriggeredThreadLinks);
+
+  GURL extension_url("chrome-extension://someextensionid/secret.html");
+  GURL host_web_content_url(chrome::kChromeUIContextualTasksURL);
+
+  ON_CALL(*aim_eligibility_service_, IsAimUrl(_, _))
+      .WillByDefault(Return(false));
+  ON_CALL(*aim_eligibility_service_, IsAimHost(_, _))
+      .WillByDefault(Return(false));
+
+  auto web_contents = content::WebContentsTester::CreateTestWebContents(
+      profile_.get(), content::SiteInstance::Create(profile_.get()));
+  content::WebContentsTester::For(web_contents.get())
+      ->SetLastCommittedURL(host_web_content_url);
+  tabs::MockTabInterface tab;
+  ON_CALL(tab, GetContents).WillByDefault(Return(web_contents.get()));
+
+  EXPECT_CALL(*service_for_nav_, OnThreadLinkClicked(_, _, _, _, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OpenUrl(_, _, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OnNonThreadNavigationInTab(_, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OnNavigationToAiPageIntercepted(_, _, _))
+      .Times(0);
+
+  // When viewed in a tab with a non-web scheme, HandleNavigationImpl should
+  // return false and avoid dispatching any thread link or OpenUrl helpers.
+  EXPECT_FALSE(service_for_nav_->HandleNavigationImpl(
+      CreateOpenUrlParams(extension_url, true), web_contents.get(), &tab,
+      /*is_from_embedded_page=*/true,
+      /*from_can_create_window=*/false, /*is_same_site_or_from_ui=*/true, false,
+      std::nullopt, std::nullopt, blink::mojom::WindowFeatures()));
+
+  // Same check when viewed in side panel (null tab).
+  EXPECT_FALSE(service_for_nav_->HandleNavigationImpl(
+      CreateOpenUrlParams(extension_url, true), web_contents.get(), nullptr,
+      /*is_from_embedded_page=*/true,
+      /*from_can_create_window=*/false, /*is_same_site_or_from_ui=*/true, false,
+      std::nullopt, std::nullopt, blink::mojom::WindowFeatures()));
+}
+
+TEST_F(ContextualTasksUiServiceTest,
+       Navigation_NonWebScheme_AimTriggeredThreadLinksDisabled_NotIntercepted) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(kAimTriggeredThreadLinks);
+
+  GURL extension_url("chrome-extension://someextensionid/secret.html");
+  GURL host_web_content_url(chrome::kChromeUIContextualTasksURL);
+
+  ON_CALL(*aim_eligibility_service_, IsAimUrl(_, _))
+      .WillByDefault(Return(false));
+  ON_CALL(*aim_eligibility_service_, IsAimHost(_, _))
+      .WillByDefault(Return(false));
+
+  auto web_contents = content::WebContentsTester::CreateTestWebContents(
+      profile_.get(), content::SiteInstance::Create(profile_.get()));
+  content::WebContentsTester::For(web_contents.get())
+      ->SetLastCommittedURL(host_web_content_url);
+
+  EXPECT_CALL(*service_for_nav_, OnThreadLinkClicked(_, _, _, _, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OpenUrl(_, _, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OnNonThreadNavigationInTab(_, _)).Times(0);
+  EXPECT_CALL(*service_for_nav_, OnNavigationToAiPageIntercepted(_, _, _))
+      .Times(0);
+
+  EXPECT_FALSE(service_for_nav_->HandleNavigationImpl(
+      CreateOpenUrlParams(extension_url, true), web_contents.get(), nullptr,
+      /*is_from_embedded_page=*/true,
+      /*from_can_create_window=*/false, /*is_same_site_or_from_ui=*/true, false,
+      std::nullopt, std::nullopt, blink::mojom::WindowFeatures()));
+}
+
 // If the search results page is navigated to while viewing the UI in the side
 // panel (e.g. no tab tied to the WebContents), ensure the correct event is
 // fired.
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential extension web_accessible_resources bypass via Contextual Tasks

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: A compromised guest renderer within the Contextual Tasks WebUI can bypass extension security policies by initiating a new window request. The Contextual Tasks service intercepts the request and re-issues it as a browser-initiated navigation without an initiator origin. This launders the navigation, allowing it to bypass web_accessible_resources checks and load restricted extension pages.

Affected files:

  • chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc
  • chrome/browser/chrome_content_browser_client.cc
  • extensions/browser/extension_navigation_throttle.cc
  • content/browser/renderer_host/render_frame_host_impl.cc

Estimated timestamp from git blame: 2026-04-08

Summary

A potential vulnerability exists in the Contextual Tasks feature where a compromised guest renderer can bypass web_accessible_resources restrictions for installed Chrome extensions. When the guest renderer requests a new window, the ContextualTasksUiService intercepts the request and manually triggers the navigation using a newly instantiated content::NavigationController::LoadURLParams. Because it does not propagate the original initiator_origin or set is_renderer_initiated, the browser treats the request as a trusted, browser-initiated navigation, bypassing extension security boundaries.

Note: The following steps and analysis are suggested/potential scenarios based on source code analysis, as our tooling agent does not currently run active exploit code to verify them dynamically.

Potential Attack Scenario

  1. An attacker compromises the sandboxed guest renderer used by the Contextual Tasks feature (e.g., via an XSS payload in the web content it loads).
  2. The attacker executes JavaScript to open a new window pointing to a sensitive, non-web-accessible internal page of an installed extension: window.open("chrome-extension://<target_id>/secret_page.html").
  3. The browser process receives the mojom::Frame::CreateNewWindow IPC.
  4. The chrome-extension:// URL passes the initial FilterURL check because the scheme is registered as a WebSafeIsolated scheme, making it generally requestable.
  5. The Contextual Tasks service intercepts the request, aborts the standard window creation, and opens the URL in a new tab as a browser-initiated navigation.
  6. The new tab successfully loads the internal extension page, bypassing the extension’s manifest restrictions and exposing its privileged context to the attacker.

Root Cause Analysis

The vulnerability stems from a loss of navigation context during request interception.

  1. Interception: In chrome/browser/chrome_content_browser_client.cc:4425 (ChromeContentBrowserClient::CanCreateWindow), the Contextual Tasks feature intercepts the window.open call. It passes is_from_embedded_page = true and is_to_new_tab = true to contextual_tasks_ui_service->HandleNavigation().
  2. Forced Link Handling: Inside ContextualTasksUiService::HandleNavigationImpl, the combination of is_from_embedded_page and is_to_new_tab forces the is_allowed_host check to evaluate to false (line 925). Consequently, the code falls through to line 979, posting a task to OnThreadLinkClicked and returning true to abort standard window creation.
  3. Context Loss (Laundering): In OnThreadLinkClicked (chrome/browser/contextual_tasks/contextual_tasks_ui_service.cc:474), a new tab is created and navigated using: new_contents->GetController().LoadURLWithParams(content::NavigationController::LoadURLParams(url)); By default, the LoadURLParams constructor initializes is_renderer_initiated to false and initiator_origin to std::nullopt.
  4. Policy Bypass: As the navigation starts, ExtensionNavigationThrottle::WillStartOrRedirectRequest (extensions/browser/extension_navigation_throttle.cc:356) checks if there is an initiator_origin. Seeing nullopt, it treats the navigation as an “Automatically trusted” browser-initiated navigation (like a user typing in the omnibox) and returns content::NavigationThrottle::PROCEED. This fully bypasses the web_accessible_resources checks.

Suggested Fix

When constructing LoadURLParams in ContextualTasksUiService::OnThreadLinkClicked (or the methods calling it), the original navigation context must be preserved.

The HandleNavigation method should accept the initiator_origin from the source_contents, and this origin should be explicitly assigned to LoadURLParams::initiator_origin. Additionally, LoadURLParams::is_renderer_initiated should be set to true when the original action was renderer-initiated. Alternatively, HandleNavigationImpl should explicitly reject unhandled or sensitive schemes (like chrome-extension://) before treating them as thread link clicks.

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.

Raised in root component due to access or custom field issues on 1516765

View on issue tracker