Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in Mobile
DescriptionUI misrepresentation in Mobile
ComponentMobile
Sub-componentiOS
Bug ClassSpoofing
Tracker518039724
Fix commit8b46a82f5193 (chromium/src) +52/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
TEST_F
ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager_unittest.mm
modified

Files Changed

  • ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm
  • ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager_unittest.mm
From 8b46a82f5193ad2c19fa98e988dae9f58d940b15 Mon Sep 17 00:00:00 2001
From: Stepan Khapugin <stkhapugin@google.com>
Date: Tue, 08 Sep 2026 13:19:29 -0700
Subject: [PATCH] [iOS][Lens] Check host in IsLensOverlaySRP.

Check in lens srp helpers that the host is google.

Fixed: 518039724
Change-Id: I4615345fb54eed3c412933a22018321006e7d647
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8372182
Reviewed-by: Charles Yang <cmyang@google.com>
Commit-Queue: Stepan Khapugin <stkhapugin@chromium.org>
Commit-Queue: Charles Yang <cmyang@google.com>
Auto-Submit: Stepan Khapugin <stkhapugin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1694003}
---
diff --git a/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm b/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm
index 68e8df8..14def4a 100644
--- a/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm
+++ b/ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm
@@ -27,6 +27,9 @@
 }
 
 bool IsLensOverlaySRP(const GURL& url) {
+  if (!IsGoogleHostURL(url)) {
+    return false;
+  }
   std::string search_term;
   bool hasSearchTerms = net::GetValueForKeyInQuery(url, "q", &search_term);
   std::string lens_surface;
@@ -41,6 +44,9 @@
 }
 
 bool IsLensMultimodalSRP(const GURL& url) {
+  if (!IsGoogleHostURL(url)) {
+    return false;
+  }
   std::string search_term;
   bool has_search_terms = net::GetValueForKeyInQuery(url, "q", &search_term);
   std::string lens_surface;
diff --git a/ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager_unittest.mm b/ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager_unittest.mm
index 322a9723..f37d8c8 100644
--- a/ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager_unittest.mm
+++ b/ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager_unittest.mm
@@ -16,6 +16,7 @@
 #import "components/variations/variations_ids_provider.h"
 #import "ios/chrome/browser/lens_overlay/coordinator/fake_chrome_lens_overlay_result.h"
 #import "ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_mutator.h"
+#import "ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.h"
 #import "ios/chrome/common/NSString+Chromium.h"
 #import "ios/web/public/test/fakes/fake_navigation_context.h"
 #import "ios/web/public/test/fakes/fake_navigation_manager.h"
@@ -388,3 +389,48 @@
   GoBackExpectingURLReload(URL2, /*expect_can_go_back=*/YES);
   EXPECT_TRUE([latest_loaded_omnibox_text_ isEqualToString:text1]);
 }
+
+// Tests that navigation to a non-Google domain does not update the omnibox
+// steady-state text.
+TEST_F(LensOverlayNavigationManagerTest,
+       NonGoogleHostURLDoesNotUpdateOmniboxText) {
+  id<ChromeLensOverlayResult> result1 = GenerateResult(/*identifier=*/1);
+  SimulateLensDidGenerateResult(result1, /*expect_load=*/YES,
+                                /*expect_can_go_back=*/NO);
+
+  NSString* sample_query = @"sample search label";
+  GURL non_google_url = GURL("https://example.com/search");
+  non_google_url = net::AppendOrReplaceQueryParameter(
+      non_google_url, "q", sample_query.cr_UTF8String);
+  non_google_url = net::AppendOrReplaceQueryParameter(
+      non_google_url, lens::kLensSurfaceQueryParameter, "4");
+
+  // Mutator should not receive onSRPLoadWithOmniboxText: for non-Google hosts.
+  [[mock_mutator_ reject] onSRPLoadWithOmniboxText:[OCMArg any]
+                                      isMultimodal:NO];
+  [[mock_mutator_ reject] onSRPLoadWithOmniboxText:[OCMArg any]
+                                      isMultimodal:YES];
+  SimulateWebNavigation(non_google_url, /*expect_can_go_back=*/YES);
+  EXPECT_OCMOCK_VERIFY(mock_mutator_);
+
+  // Direct verification of URL utils.
+  EXPECT_FALSE(lens::IsLensOverlaySRP(non_google_url));
+  EXPECT_FALSE(lens::IsLensMultimodalSRP(non_google_url));
+}
+
+// Tests that IsLensOverlaySRP and IsLensMultimodalSRP require a Google host.
+TEST_F(LensOverlayNavigationManagerTest, GoogleHostURLValidation) {
+  EXPECT_TRUE(lens::IsLensOverlaySRP(
+      GURL("https://www.google.com/search?q=test&lns_surface=4")));
+  EXPECT_TRUE(lens::IsLensOverlaySRP(
+      GURL("https://www.google.co.uk/search?q=test&lns_surface=4")));
+  EXPECT_FALSE(lens::IsLensOverlaySRP(
+      GURL("https://example.com/search?q=test&lns_surface=4")));
+  EXPECT_FALSE(lens::IsLensOverlaySRP(
+      GURL("https://google.com.example.com/search?q=test&lns_surface=4")));
+
+  EXPECT_TRUE(lens::IsLensMultimodalSRP(GURL(
+      "https://www.google.com/search?q=test&lns_surface=4&vsrid=123&udm=24")));
+  EXPECT_FALSE(lens::IsLensMultimodalSRP(GURL(
+      "https://example.com/search?q=test&lns_surface=4&vsrid=123&udm=24")));
+}
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager_unittest.mm b/ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager_unittest.mm
index 322a9723..f37d8c8 100644
--- a/ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager_unittest.mm
+++ b/ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager_unittest.mm
@@ -16,6 +16,7 @@
 #import "components/variations/variations_ids_provider.h"
 #import "ios/chrome/browser/lens_overlay/coordinator/fake_chrome_lens_overlay_result.h"
 #import "ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_mutator.h"
+#import "ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.h"
 #import "ios/chrome/common/NSString+Chromium.h"
 #import "ios/web/public/test/fakes/fake_navigation_context.h"
 #import "ios/web/public/test/fakes/fake_navigation_manager.h"
@@ -388,3 +389,48 @@
   GoBackExpectingURLReload(URL2, /*expect_can_go_back=*/YES);
   EXPECT_TRUE([latest_loaded_omnibox_text_ isEqualToString:text1]);
 }
+
+// Tests that navigation to a non-Google domain does not update the omnibox
+// steady-state text.
+TEST_F(LensOverlayNavigationManagerTest,
+       NonGoogleHostURLDoesNotUpdateOmniboxText) {
+  id<ChromeLensOverlayResult> result1 = GenerateResult(/*identifier=*/1);
+  SimulateLensDidGenerateResult(result1, /*expect_load=*/YES,
+                                /*expect_can_go_back=*/NO);
+
+  NSString* sample_query = @"sample search label";
+  GURL non_google_url = GURL("https://example.com/search");
+  non_google_url = net::AppendOrReplaceQueryParameter(
+      non_google_url, "q", sample_query.cr_UTF8String);
+  non_google_url = net::AppendOrReplaceQueryParameter(
+      non_google_url, lens::kLensSurfaceQueryParameter, "4");
+
+  // Mutator should not receive onSRPLoadWithOmniboxText: for non-Google hosts.
+  [[mock_mutator_ reject] onSRPLoadWithOmniboxText:[OCMArg any]
+                                      isMultimodal:NO];
+  [[mock_mutator_ reject] onSRPLoadWithOmniboxText:[OCMArg any]
+                                      isMultimodal:YES];
+  SimulateWebNavigation(non_google_url, /*expect_can_go_back=*/YES);
+  EXPECT_OCMOCK_VERIFY(mock_mutator_);
+
+  // Direct verification of URL utils.
+  EXPECT_FALSE(lens::IsLensOverlaySRP(non_google_url));
+  EXPECT_FALSE(lens::IsLensMultimodalSRP(non_google_url));
+}
+
+// Tests that IsLensOverlaySRP and IsLensMultimodalSRP require a Google host.
+TEST_F(LensOverlayNavigationManagerTest, GoogleHostURLValidation) {
+  EXPECT_TRUE(lens::IsLensOverlaySRP(
+      GURL("https://www.google.com/search?q=test&lns_surface=4")));
+  EXPECT_TRUE(lens::IsLensOverlaySRP(
+      GURL("https://www.google.co.uk/search?q=test&lns_surface=4")));
+  EXPECT_FALSE(lens::IsLensOverlaySRP(
+      GURL("https://example.com/search?q=test&lns_surface=4")));
+  EXPECT_FALSE(lens::IsLensOverlaySRP(
+      GURL("https://google.com.example.com/search?q=test&lns_surface=4")));
+
+  EXPECT_TRUE(lens::IsLensMultimodalSRP(GURL(
+      "https://www.google.com/search?q=test&lns_surface=4&vsrid=123&udm=24")));
+  EXPECT_FALSE(lens::IsLensMultimodalSRP(GURL(
+      "https://example.com/search?q=test&lns_surface=4&vsrid=123&udm=24")));
+}
Loading diff…

Original Bug Report

reported by vm...@google.com

Omnibox text spoofing in iOS Chrome Lens results sheet via Google AMP viewer links

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential address bar and origin-indicator spoofing vulnerability exists in iOS Chrome’s Lens results bottom sheet UI. Due to missing host and path validation in IsLensOverlaySRP, an attacker can leverage a Google AMP viewer link with the lns_surface=4 parameter to bypass domain restrictions while loading their own phishing page. This allows the attacker to fully control the native omnibox steady-state text using the q query parameter of the navigated URL.

Affected files:

  • ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm
  • ios/chrome/browser/lens_overlay/model/lens_overlay_navigation_manager.mm
  • ios/chrome/browser/lens_overlay/coordinator/lens_result_page_mediator.mm
  • ios/chrome/browser/lens_overlay/coordinator/lens_overlay_mediator.mm
  • ios/chrome/browser/lens_overlay/coordinator/lens_omnibox_client.mm

Estimated timestamp from git blame: 2025-01-10

Description

A potential address-bar/origin-indicator spoofing vulnerability exists in the iOS Chrome Lens results panel. When a user navigates inside the results panel, the policy decider IsValidURLToOpenInResultsPage permits navigation to any URL on an allowed Google host that contains the lns_surface=4 parameter. An attacker can exploit this by utilizing a Google AMP viewer link (e.g., https://www.google.com/amp/s/attacker.com/phish) to bypass the host restriction and load an attacker-controlled page.

Once the navigation begins, LensOverlayNavigationManager::DidStartNavigation evaluates the destination URL using lens::IsLensOverlaySRP. However, IsLensOverlaySRP only checks query parameters (such as q and lns_surface=4) and performs no host or path validation. As a result, the navigation manager incorrectly identifies the AMP URL as a Search Results Page (SRP) and extracts the q query parameter to override the native omnibox steady-state text via LensOmniboxClient::SetOmniboxSteadyStateText. The native omnibox then displays the attacker-controlled text (e.g., "chase.com — Sign in") while the web view loads the attacker’s phishing content inside the AMP viewer.

Potential Steps to Reproduce

  1. On iOS Chrome, invoke the Lens overlay and wait for the results bottom sheet to load.
  2. Trigger a main-frame navigation within the panel’s WebState to a crafted Google AMP URL, such as: https://www.google.com/amp/s/attacker.com/phish?q=chase.com%20%E2%80%94%20Sign%20in&lns_surface=4
  3. Observe that the navigation is permitted because www.google.com is a valid Google domain under IsGoogleHostURL (which permits the www.google prefix under DISALLOW_SUBDOMAIN checks) and contains lns_surface=4.
  4. Observe that IsLensOverlaySRP evaluates the URL as a valid SRP because it contains query keys q and lns_surface=4.
  5. Observe that the native omnibox text is rewritten to display chase.com — Sign in while the page viewport displays the attacker’s phishing page.

Note: These are potential steps analyzed from the source code, as our tooling environment does not currently have the capability to execute code or run dynamic proofs of concept.

Suggested Fix

Update IsLensOverlaySRP and IsLensMultimodalSRP in ios/chrome/browser/lens_overlay/model/lens_overlay_url_utils.mm to ensure they perform host validation using IsGoogleHostURL(url) before parsing and trusting query parameters:

bool IsLensOverlaySRP(const GURL& url) {
  if (!IsGoogleHostURL(url)) {
    return false;
  }
  std::string search_term;
  bool hasSearchTerms = net::GetValueForKeyInQuery(url, "q", &search_term);
  std::string lens_surface;
  bool hasLensSurface = net::GetValueForKeyInQuery(
      url, lens::kLensSurfaceQueryParameter, &lens_surface);
  std::string request_id;
  bool hasLensParam = net::GetValueForKeyInQuery(
      url, lens::kLensRequestQueryParameter, &request_id);

  return hasSearchTerms && hasLensSurface && !hasLensParam &&
         lens_surface == "4";
}

Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040


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.

View on issue tracker