Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in Chrome for iOS
DescriptionIncorrect security UI in Chrome for iOS
ComponentChrome for iOS
Bug ClassLogic Error
Tracker516836297
Fix commitb5e92a2937e9 (chromium/src) +23/-0
CISA KEVNot listed
CreditedAzza Tegar Naufal Ataullah
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
TEST_F
ios/web/web_state/ui/crw_web_controller_unittest.mm
modified

Files Changed

  • ios/web/web_state/ui/crw_web_controller_unittest.mm
  • ios/web/web_state/ui/crw_wk_ui_handler.mm
From b5e92a2937e988674e052b9060eed854256a5b5a Mon Sep 17 00:00:00 2001
From: Mike Dougherty <michaeldo@chromium.org>
Date: Mon, 15 Jun 2026 10:52:22 -0700
Subject: [PATCH] Prevent JS dialogs from non-visible WebStates

WebStates must not be permitted to display dialogs when they are no
longer the primary/visible WebState. Doing so would block the shared
WebKit WebProcess thread and impact other WebStates.

Fixed: 516836297
Change-Id: Ie470e00257a54292e8ae4c4fd8e236a01ed62368
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7934914
Reviewed-by: Gauthier Ambard <gambard@chromium.org>
Commit-Queue: Mike Dougherty <michaeldo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1646934}
---

diff --git a/ios/web/web_state/ui/crw_web_controller_unittest.mm b/ios/web/web_state/ui/crw_web_controller_unittest.mm
index 0823d10a..25dd689e2 100644
--- a/ios/web/web_state/ui/crw_web_controller_unittest.mm
+++ b/ios/web/web_state/ui/crw_web_controller_unittest.mm
@@ -466,6 +466,7 @@
   JavaScriptDialogPresenterTest() : page_url_("https://chromium.test/") {}
   void SetUp() override {
     WebTestWithWebState::SetUp();
+    web_state()->WasShown();
     LoadHtml(@"<html><body></body></html>", page_url_);
     web_state()->SetDelegate(&web_state_delegate_);
   }
@@ -605,6 +606,24 @@
   ASSERT_TRUE(requested_prompt_dialogs().empty());
 }
 
+// Tests that window.alert, window.confirm and window.prompt dialogs are not
+// shown if the WebState is not visible.
+TEST_F(JavaScriptDialogPresenterTest, InvisibleWebState) {
+  ASSERT_FALSE(JSDialogPresenterHasDialogs());
+
+  web_state()->WasHidden();
+  ASSERT_FALSE(web_state()->IsVisible());
+
+  ExecuteJavaScript(@"alert('test')");
+  ASSERT_TRUE(requested_alert_dialogs().empty());
+
+  EXPECT_NSEQ(@NO, ExecuteJavaScript(@"confirm('test')"));
+  ASSERT_TRUE(requested_confirm_dialogs().empty());
+
+  EXPECT_NSEQ([NSNull null], ExecuteJavaScript(@"prompt('Yes?', 'No')"));
+  ASSERT_TRUE(requested_prompt_dialogs().empty());
+}
+
 // Test fixture for testing visible security state.
 typedef WebTestWithWebState CRWWebStateSecurityStateTest;
 
diff --git a/ios/web/web_state/ui/crw_wk_ui_handler.mm b/ios/web/web_state/ui/crw_wk_ui_handler.mm
index 91d433b..728ac61e 100644
--- a/ios/web/web_state/ui/crw_wk_ui_handler.mm
+++ b/ios/web/web_state/ui/crw_wk_ui_handler.mm
@@ -366,6 +366,10 @@
     return NO;
   }
 
+  if (!self.webStateImpl->IsVisible()) {
+    return NO;
+  }
+
   if (isMainFrame && url::Origin::Create(self.webStateImpl->GetVisibleURL()) !=
                          url::Origin::Create(requestURL)) {
     // Dialog was requested by web page's main frame, but visible URL has
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ios/web/web_state/ui/crw_web_controller_unittest.mm b/ios/web/web_state/ui/crw_web_controller_unittest.mm
index 0823d10a..25dd689e2 100644
--- a/ios/web/web_state/ui/crw_web_controller_unittest.mm
+++ b/ios/web/web_state/ui/crw_web_controller_unittest.mm
@@ -466,6 +466,7 @@
   JavaScriptDialogPresenterTest() : page_url_("https://chromium.test/") {}
   void SetUp() override {
     WebTestWithWebState::SetUp();
+    web_state()->WasShown();
     LoadHtml(@"<html><body></body></html>", page_url_);
     web_state()->SetDelegate(&web_state_delegate_);
   }
@@ -605,6 +606,24 @@
   ASSERT_TRUE(requested_prompt_dialogs().empty());
 }
 
+// Tests that window.alert, window.confirm and window.prompt dialogs are not
+// shown if the WebState is not visible.
+TEST_F(JavaScriptDialogPresenterTest, InvisibleWebState) {
+  ASSERT_FALSE(JSDialogPresenterHasDialogs());
+
+  web_state()->WasHidden();
+  ASSERT_FALSE(web_state()->IsVisible());
+
+  ExecuteJavaScript(@"alert('test')");
+  ASSERT_TRUE(requested_alert_dialogs().empty());
+
+  EXPECT_NSEQ(@NO, ExecuteJavaScript(@"confirm('test')"));
+  ASSERT_TRUE(requested_confirm_dialogs().empty());
+
+  EXPECT_NSEQ([NSNull null], ExecuteJavaScript(@"prompt('Yes?', 'No')"));
+  ASSERT_TRUE(requested_prompt_dialogs().empty());
+}
+
 // Test fixture for testing visible security state.
 typedef WebTestWithWebState CRWWebStateSecurityStateTest;
Loading diff…

Original Bug Report

reported by az...@gmail.com

Chrome iOS: Persistent Address Bar Origin Spoofing via JavaScript Thread Blocking

Steps to reproduce the problem

  1. Open: https://justluckidzz.github.io/clickme/zero.html
  2. Tap CLICK HERE
  3. Observe: Address bar changes to apple.com, attacker controlled content remains rendered, redirect becomes stuck/persistent & swipe refresh and browser UI interactions still keep apple.com visible while attacker content remains displayed

Problem Description

Broken Behavior:

Chrome iOS contains a persistent address bar spoofing vulnerability caused by a UI/render desynchronization during asynchronous navigation. The issue occurs when attacker-controlled HTML is injected into a newly opened about:blank tab using document.write(), followed by asynchronous navigation to a trusted origin (https://apple.com) while a synchronous modal dialog (prompt()) blocks the navigation/render pipeline.

Trigger logic:

setTimeout(() => {
    newTab.location.href = "https://apple.com";
}, 1);

setTimeout(function () {
    prompt("ROOT CAUSE");
}, 500);

Chrome iOS updates the address bar and security UI to display apple.com before navigation fully commits. The renderer then becomes stuck displaying attacker-controlled HTML content while the browser UI continues displaying the trusted origin.

The spoofed state remains persistent even after:

  • Swipe refresh
  • Browser UI interaction
  • Navigation gestures

Expected Behavior:

The address bar must strictly synchronize with the currently rendered DOM. If the main thread is blocked before a navigation commits, the address bar should reflect the true origin of the visible content, or the navigation should complete without freezing the UI state.

Reproduced on:

  • Chrome iOS Version: 149.0.7827.26 (Stable) | Also confirmed on Beta, Dev, and Canary latest version.
  • iOS 26.6 Beta Developer

Summary

Chrome iOS: Persistent Address Bar Origin Spoofing via JavaScript Thread Blocking

Custom Questions

Type of crash:

na

Crash state:

na

Reporter credit:

Azza Tegar Naufal Ataullah

Additional Data

Category: Security
Chrome Channel: Stable
Regression: N/A \

View on issue tracker