Chrome · Mobile
CVE-2026-106427
Authorization Error in Mobile
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator.mm |
modified | |
TEST_Fios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm |
modified | |
forios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mm |
modified |
Files Changed
ios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator_unittest.mmios/chrome/browser/cobrowse/coordinator/assistant_aim_mediator.mm
Patch
From 12e9eb9b62c28bbf4dfdca947baae2bf30cf4cff Mon Sep 17 00:00:00 2001
From: Louis Romero <lpromero@google.com>
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 <lpromero@google.com>
Reviewed-by: Ameur Hosni <ameurhosni@google.com>
Commit-Queue: Louis Romero <lpromero@google.com>
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.
References
On This Page