Low chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactConfused deputy in Mobile
DescriptionConfused deputy in Mobile
ComponentMobile
Bug ClassAuthorization Error
Tracker513423334
Fix commit12e9eb9b62c2 (chromium/src) +54/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator.mm
modified
TEST_F
ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm
modified
for
ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm
modified

Files Changed

  • ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm
  • ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator.mm
From 12e9eb9b62c28bbf4dfdca947baae2bf30cf4cff Mon Sep 17 00:00:00 2001
From: Louis Romero <[email protected]>
Date: Thu, 10 Sep 2026 04:42:55 -0700
Subject: [PATCH] Fix navigation trust escalation in Assistant AIM

When a main-frame navigation to a third-party domain occurs within the
Assistant AIM sheet, AssistantAIMMediator cancels the navigation and
replays the target URL into a new browser tab. Previously, this load
was dispatched without scheme validation and with the default
is_renderer_initiated set to false. This allowed renderer-initiated
navigations to be laundered into trusted browser-initiated loads,
bypassing security checks for privileged pages such as about: or
chrome:// WebUI URLs.

This change validates that target URLs use an HTTP or HTTPS scheme
before replaying, and explicitly propagates the renderer context by
setting is_renderer_initiated to true and the transition type to
PAGE_TRANSITION_LINK.

Fixed: 513423334
Change-Id: Ice7d3ad572c5103e230705b3d596125543744f20
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8378851
Auto-Submit: Louis Romero <[email protected]>
Reviewed-by: Ameur Hosni <[email protected]>
Commit-Queue: Louis Romero <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1695343}
---
diff --git a/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator.mm b/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator.mm
index 8d458432..52608bd7 100644
--- a/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator.mm
+++ b/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator.mm
@@ -48,6 +48,7 @@
 #import "net/base/apple/url_conversions.h"
 #import "third_party/lens_server_proto/aim_communication.pb.h"
 #import "ui/base/l10n/l10n_util.h"
+#import "ui/base/page_transition_types.h"
 #import "url/gurl.h"
 
 @interface AssistantAIMMediator () <CRWWebFramesManagerObserver,
@@ -242,10 +243,11 @@
   // the main browser instead.
   if (requestInfo.target_frame_is_main) {
     decisionHandler(web::WebStatePolicyDecider::PolicyDecision::Cancel());
-    // Filter out about:blank initialization navigations to prevent spawning
-    // empty tabs in the main browser upon loading the Assistant AIM sheet.
-    if (URL.is_valid() && !URL.IsAboutBlank()) {
+    // Only replay valid HTTP or HTTPS URLs in the main browser.
+    if (URL.is_valid() && URL.SchemeIsHTTPOrHTTPS()) {
       UrlLoadParams params = UrlLoadParams::InNewTab(URL);
+      params.web_params.is_renderer_initiated = true;
+      params.web_params.transition_type = ui::PAGE_TRANSITION_LINK;
       _urlLoader->Load(params);
       [_containerHandler
           animateAssistantContainerToDetent:AssistantContainerDetent::kMinimized
diff --git a/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm b/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm
index faa54ca6b..e20df3b 100644
--- a/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm
+++ b/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm
@@ -4,9 +4,11 @@
 
 #import "ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator.h"
 
+#import "base/functional/callback_helpers.h"
 #import "base/memory/raw_ptr.h"
 #import "base/strings/sys_string_conversions.h"
 #import "base/test/scoped_feature_list.h"
+#import "base/test/test_future.h"
 #import "components/contextual_tasks/public/features.h"
 #import "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/assistant/coordinator/assistant_container_commands.h"
@@ -242,6 +244,10 @@
   EXPECT_TRUE(blocked_decision.ShouldCancelNavigation());
   EXPECT_EQ(url_loader_->load_new_tab_call_count, 1);
   EXPECT_EQ(url_loader_->last_params.web_params.url, third_party_url);
+  EXPECT_TRUE(url_loader_->last_params.web_params.is_renderer_initiated);
+  EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
+      url_loader_->last_params.web_params.transition_type,
+      ui::PAGE_TRANSITION_LINK));
 }
 
 // Tests that the navigation policy decider intercepts and cancels
@@ -271,6 +277,49 @@
   EXPECT_TRUE(blocked_decision.ShouldCancelNavigation());
   EXPECT_EQ(url_loader_->load_new_tab_call_count, 1);
   EXPECT_EQ(url_loader_->last_params.web_params.url, third_party_url);
+  EXPECT_TRUE(url_loader_->last_params.web_params.is_renderer_initiated);
+  EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
+      url_loader_->last_params.web_params.transition_type,
+      ui::PAGE_TRANSITION_LINK));
+}
+
+// Tests that the navigation policy decider cancels navigation to non-HTTP/HTTPS
+// URLs (such as about: or chrome://) without opening a new tab.
+TEST_F(AssistantAIMMediatorTest, CancelsNonHttpOrHttpsURLWithoutOpeningNewTab) {
+  id<CRWWebStatePolicyDecider> policy_decider =
+      static_cast<id<CRWWebStatePolicyDecider>>(mediator_);
+
+  [[mock_container_handler_ reject]
+      animateAssistantContainerToDetent:AssistantContainerDetent::kMinimized
+                               duration:kSheetDetentAnimationDuration
+                                  curve:UIViewAnimationCurveEaseInOut];
+
+  const GURL test_urls[] = {
+      GURL("about:version"),
+      GURL("chrome://flags"),
+      GURL("javascript:alert(1)"),
+      GURL("file:///etc/passwd"),
+  };
+
+  for (const GURL& test_url : test_urls) {
+    base::test::TestFuture<web::WebStatePolicyDecider::PolicyDecision> future;
+
+    [policy_decider
+        shouldAllowRequest:[NSURLRequest
+                               requestWithURL:net::NSURLWithGURL(test_url)]
+               requestInfo:web::WebStatePolicyDecider::RequestInfo(
+                               ui::PageTransition::PAGE_TRANSITION_LINK,
+                               /*target_frame_is_main=*/true,
+                               /*target_frame_is_cross_origin=*/true,
+                               /*target_window_is_cross_origin=*/false,
+                               /*is_user_initiated=*/true,
+                               /*user_tapped_recently=*/true)
+           decisionHandler:base::CallbackToBlock(future.GetCallback())];
+    EXPECT_TRUE(future.Get().ShouldCancelNavigation());
+    EXPECT_EQ(url_loader_->load_new_tab_call_count, 0);
+  }
+
+  [mock_container_handler_ verify];
 }
 
 // Tests that the navigation policy decider allows authorized Google
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm b/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm
index faa54ca6b..e20df3b 100644
--- a/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm
+++ b/ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm
@@ -4,9 +4,11 @@
 
 #import "ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator.h"
 
+#import "base/functional/callback_helpers.h"
 #import "base/memory/raw_ptr.h"
 #import "base/strings/sys_string_conversions.h"
 #import "base/test/scoped_feature_list.h"
+#import "base/test/test_future.h"
 #import "components/contextual_tasks/public/features.h"
 #import "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/assistant/coordinator/assistant_container_commands.h"
@@ -242,6 +244,10 @@
   EXPECT_TRUE(blocked_decision.ShouldCancelNavigation());
   EXPECT_EQ(url_loader_->load_new_tab_call_count, 1);
   EXPECT_EQ(url_loader_->last_params.web_params.url, third_party_url);
+  EXPECT_TRUE(url_loader_->last_params.web_params.is_renderer_initiated);
+  EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
+      url_loader_->last_params.web_params.transition_type,
+      ui::PAGE_TRANSITION_LINK));
 }
 
 // Tests that the navigation policy decider intercepts and cancels
@@ -271,6 +277,49 @@
   EXPECT_TRUE(blocked_decision.ShouldCancelNavigation());
   EXPECT_EQ(url_loader_->load_new_tab_call_count, 1);
   EXPECT_EQ(url_loader_->last_params.web_params.url, third_party_url);
+  EXPECT_TRUE(url_loader_->last_params.web_params.is_renderer_initiated);
+  EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs(
+      url_loader_->last_params.web_params.transition_type,
+      ui::PAGE_TRANSITION_LINK));
+}
+
+// Tests that the navigation policy decider cancels navigation to non-HTTP/HTTPS
+// URLs (such as about: or chrome://) without opening a new tab.
+TEST_F(AssistantAIMMediatorTest, CancelsNonHttpOrHttpsURLWithoutOpeningNewTab) {
+  id<CRWWebStatePolicyDecider> policy_decider =
+      static_cast<id<CRWWebStatePolicyDecider>>(mediator_);
+
+  [[mock_container_handler_ reject]
+      animateAssistantContainerToDetent:AssistantContainerDetent::kMinimized
+                               duration:kSheetDetentAnimationDuration
+                                  curve:UIViewAnimationCurveEaseInOut];
+
+  const GURL test_urls[] = {
+      GURL("about:version"),
+      GURL("chrome://flags"),
+      GURL("javascript:alert(1)"),
+      GURL("file:///etc/passwd"),
+  };
+
+  for (const GURL& test_url : test_urls) {
+    base::test::TestFuture<web::WebStatePolicyDecider::PolicyDecision> future;
+
+    [policy_decider
+        shouldAllowRequest:[NSURLRequest
+                               requestWithURL:net::NSURLWithGURL(test_url)]
+               requestInfo:web::WebStatePolicyDecider::RequestInfo(
+                               ui::PageTransition::PAGE_TRANSITION_LINK,
+                               /*target_frame_is_main=*/true,
+                               /*target_frame_is_cross_origin=*/true,
+                               /*target_window_is_cross_origin=*/false,
+                               /*is_user_initiated=*/true,
+                               /*user_tapped_recently=*/true)
+           decisionHandler:base::CallbackToBlock(future.GetCallback())];
+    EXPECT_TRUE(future.Get().ShouldCancelNavigation());
+    EXPECT_EQ(url_loader_->load_new_tab_call_count, 0);
+  }
+
+  [mock_container_handler_ verify];
 }
 
 // Tests that the navigation policy decider allows authorized Google
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.