Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Omnibox
DescriptionInsufficient validation of untrusted input in Omnibox
ComponentOmnibox
Bug ClassLogic Error
Tracker505242189
Fix commit430798f7081f (chromium/src) +105/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
switch
components/omnibox/browser/actions/omnibox_action_in_suggest.cc
modified
OmniboxActionInSuggest
components/omnibox/browser/actions/omnibox_action_in_suggest.h
modified
if
components/omnibox/browser/base_search_provider.cc
modified
TEST_F
components/omnibox/browser/base_search_provider_unittest.cc
modified
for
components/omnibox/browser/base_search_provider_unittest.cc
modified

Files Changed

  • components/omnibox/browser/actions/omnibox_action_in_suggest.cc
  • components/omnibox/browser/actions/omnibox_action_in_suggest.h
  • components/omnibox/browser/base_search_provider.cc
  • components/omnibox/browser/base_search_provider_unittest.cc
From 430798f7081f91b50b4a9061a0e32b27a5b660c0 Mon Sep 17 00:00:00 2001
From: Ameur Hosni <ameurhosni@google.com>
Date: Mon, 01 Jun 2026 03:08:09 -0700
Subject: [PATCH] [Omnibox] Add validation for action URI schemes

This CL aims to add validation for the actionURIs provided by the
templateAction when creating an actionInSuggest.

Bug: 505242189
Change-Id: I4f91e272086567ef8c47e664b94f3ad86224959e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7867718
Commit-Queue: Ameur Hosni <ameurhosni@google.com>
Auto-Submit: Ameur Hosni <ameurhosni@google.com>
Reviewed-by: Tomasz Wiszkowski <ender@google.com>
Cr-Commit-Position: refs/heads/main@{#1639261}
---

diff --git a/components/omnibox/browser/actions/omnibox_action_in_suggest.cc b/components/omnibox/browser/actions/omnibox_action_in_suggest.cc
index 2204bfb..1550559 100644
--- a/components/omnibox/browser/actions/omnibox_action_in_suggest.cc
+++ b/components/omnibox/browser/actions/omnibox_action_in_suggest.cc
@@ -10,6 +10,7 @@
 #include "components/strings/grit/components_strings.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "url/gurl.h"
+#include "url/url_constants.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/jni_android.h"
@@ -131,8 +132,43 @@
   return action.action_type() ==
          omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CHROME_AIM;
 }
+
+// Validates that the scheme of the provided action URI matches the expected
+// scheme of its action type.
+bool IsValidActionURIForType(
+    const std::string& action_uri_str,
+    omnibox::SuggestTemplateInfo_TemplateAction_ActionType action_type) {
+  GURL action_url(action_uri_str);
+  if (!action_url.is_valid()) {
+    return false;
+  }
+
+  switch (action_type) {
+    case omnibox::SuggestTemplateInfo_TemplateAction_ActionType_CALL:
+      return action_url.SchemeIs(url::kTelScheme);
+    case omnibox::SuggestTemplateInfo_TemplateAction_ActionType_DIRECTIONS:
+    case omnibox::SuggestTemplateInfo_TemplateAction_ActionType_REVIEWS:
+      return action_url.SchemeIsHTTPOrHTTPS();
+    default:
+      return true;
+  }
+}
 }  // namespace
 
+// static
+scoped_refptr<OmniboxActionInSuggest> OmniboxActionInSuggest::Create(
+    omnibox::SuggestTemplateInfo::TemplateAction template_action,
+    std::optional<TemplateURLRef::SearchTermsArgs> search_terms_args) {
+  if (!template_action.action_uri().empty() &&
+      !IsValidActionURIForType(template_action.action_uri(),
+                               template_action.action_type())) {
+    return nullptr;
+  }
+
+  return base::MakeRefCounted<OmniboxActionInSuggest>(
+      std::move(template_action), std::move(search_terms_args));
+}
+
 OmniboxActionInSuggest::OmniboxActionInSuggest(
     omnibox::SuggestTemplateInfo::TemplateAction template_action,
     std::optional<TemplateURLRef::SearchTermsArgs> search_terms_args)
diff --git a/components/omnibox/browser/actions/omnibox_action_in_suggest.h b/components/omnibox/browser/actions/omnibox_action_in_suggest.h
index 307b4d9..3e4274b 100644
--- a/components/omnibox/browser/actions/omnibox_action_in_suggest.h
+++ b/components/omnibox/browser/actions/omnibox_action_in_suggest.h
@@ -16,6 +16,12 @@
 
 class OmniboxActionInSuggest : public OmniboxAction {
  public:
+  // Validates the template_action and returns a new OmniboxActionInSuggest if
+  // valid.
+  static scoped_refptr<OmniboxActionInSuggest> Create(
+      omnibox::SuggestTemplateInfo::TemplateAction template_action,
+      std::optional<TemplateURLRef::SearchTermsArgs> search_terms_args);
+
   OmniboxActionInSuggest(
       omnibox::SuggestTemplateInfo::TemplateAction template_action,
       std::optional<TemplateURLRef::SearchTermsArgs> search_terms_args);
diff --git a/components/omnibox/browser/base_search_provider.cc b/components/omnibox/browser/base_search_provider.cc
index 0f748f5..eba94e96 100644
--- a/components/omnibox/browser/base_search_provider.cc
+++ b/components/omnibox/browser/base_search_provider.cc
@@ -239,8 +239,11 @@
         suggest_template_info->action_suggestions_size() > 0) {
       for (const omnibox::SuggestTemplateInfo_TemplateAction& action :
            suggest_template_info->action_suggestions()) {
-        match.actions.emplace_back(CreateActionInSuggest(
-            action, search_url, *match.search_terms_args, search_terms_data));
+        auto suggest_action = CreateActionInSuggest(
+            action, search_url, *match.search_terms_args, search_terms_data);
+        if (suggest_action) {
+          match.actions.emplace_back(std::move(suggest_action));
+        }
       }
     } else {
       // TODO(crbug.com/417745802): Remove once actions are migrated from
@@ -255,9 +258,12 @@
                 action_info.action_type()));
         *template_action.mutable_search_parameters() =
             action_info.search_parameters();
-        match.actions.emplace_back(
+        auto suggest_action =
             CreateActionInSuggest(template_action, search_url,
-                                  *match.search_terms_args, search_terms_data));
+                                  *match.search_terms_args, search_terms_data);
+        if (suggest_action) {
+          match.actions.emplace_back(std::move(suggest_action));
+        }
       }
     }
   }
@@ -283,8 +289,8 @@
         CreateQueryParamStringFromMap(template_action.search_parameters());
   }
 
-  return base::MakeRefCounted<OmniboxActionInSuggest>(
-      std::move(template_action), std::move(action_search_terms_args));
+  return OmniboxActionInSuggest::Create(std::move(template_action),
+                                        std::move(action_search_terms_args));
 }
 
 // static
diff --git a/components/omnibox/browser/base_search_provider_unittest.cc b/components/omnibox/browser/base_search_provider_unittest.cc
index 338df34..431df02a 100644
--- a/components/omnibox/browser/base_search_provider_unittest.cc
+++ b/components/omnibox/browser/base_search_provider_unittest.cc
@@ -718,8 +718,59 @@
         << "while evaluating case `" << test_case.test_name << '`';
   }
 }
+TEST_F(BaseSearchProviderTest, CreateActionInSuggest_SchemeValidation) {
+  using TemplateAction = omnibox::SuggestTemplateInfo::TemplateAction;
 
+  struct {
+    const char* test_name;
+    TemplateAction::ActionType action_type;
+    const char* action_url;
+    bool expect_valid;
+  } test_cases[]{
+      {"CALL: tel scheme is valid", TemplateAction::CALL, "tel:123456", true},
+      {"CALL: HTTP scheme is invalid", TemplateAction::CALL,
+       "http://example.com", false},
+      {"CALL: HTTPS scheme is invalid", TemplateAction::CALL,
+       "https://example.com", false},
+      {"CALL: chrome scheme is invalid", TemplateAction::CALL,
+       "chrome://settings", false},
+      {"DIRECTIONS: HTTP scheme is valid", TemplateAction::DIRECTIONS,
+       "http://example.com", true},
+      {"DIRECTIONS: HTTPS scheme is valid", TemplateAction::DIRECTIONS,
+       "https://example.com", true},
+      {"DIRECTIONS: tel scheme is invalid", TemplateAction::DIRECTIONS,
+       "tel:123456", false},
+      {"DIRECTIONS: chrome scheme is invalid", TemplateAction::DIRECTIONS,
+       "chrome://settings", false},
+      {"REVIEWS: HTTP scheme is valid", TemplateAction::REVIEWS,
+       "http://example.com", true},
+      {"REVIEWS: HTTPS scheme is valid", TemplateAction::REVIEWS,
+       "https://example.com", true},
+      {"REVIEWS: tel scheme is invalid", TemplateAction::REVIEWS, "tel:123456",
+       false},
+      {"REVIEWS: chrome scheme is invalid", TemplateAction::REVIEWS,
+       "chrome://settings", false},
+  };
 
+  for (const auto& test_case : test_cases) {
+    TemplateAction template_action;
+    template_action.set_action_type(test_case.action_type);
+    template_action.set_action_uri(test_case.action_url);
+
+    TemplateURLRef::SearchTermsArgs search_terms_args;
+    SearchTermsData search_terms_data;
+    TemplateURLData template_url_data;
+    template_url_data.SetURL("https://www.google.com");
+    auto template_url = std::make_unique<TemplateURL>(template_url_data);
+
+    auto action = BaseSearchProvider::CreateActionInSuggest(
+        std::move(template_action), template_url->url_ref(), search_terms_args,
+        search_terms_data);
+
+    EXPECT_EQ(action != nullptr, test_case.expect_valid)
+        << "while evaluating case `" << test_case.test_name << "`";
+  }
+}
 
 TEST_F(BaseSearchProviderTest, SuggestTemplateInfoPopulatesMatch) {
   TemplateURLData data;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/omnibox/browser/base_search_provider_unittest.cc b/components/omnibox/browser/base_search_provider_unittest.cc
index 338df34..431df02a 100644
--- a/components/omnibox/browser/base_search_provider_unittest.cc
+++ b/components/omnibox/browser/base_search_provider_unittest.cc
@@ -718,8 +718,59 @@
         << "while evaluating case `" << test_case.test_name << '`';
   }
 }
+TEST_F(BaseSearchProviderTest, CreateActionInSuggest_SchemeValidation) {
+  using TemplateAction = omnibox::SuggestTemplateInfo::TemplateAction;
 
+  struct {
+    const char* test_name;
+    TemplateAction::ActionType action_type;
+    const char* action_url;
+    bool expect_valid;
+  } test_cases[]{
+      {"CALL: tel scheme is valid", TemplateAction::CALL, "tel:123456", true},
+      {"CALL: HTTP scheme is invalid", TemplateAction::CALL,
+       "http://example.com", false},
+      {"CALL: HTTPS scheme is invalid", TemplateAction::CALL,
+       "https://example.com", false},
+      {"CALL: chrome scheme is invalid", TemplateAction::CALL,
+       "chrome://settings", false},
+      {"DIRECTIONS: HTTP scheme is valid", TemplateAction::DIRECTIONS,
+       "http://example.com", true},
+      {"DIRECTIONS: HTTPS scheme is valid", TemplateAction::DIRECTIONS,
+       "https://example.com", true},
+      {"DIRECTIONS: tel scheme is invalid", TemplateAction::DIRECTIONS,
+       "tel:123456", false},
+      {"DIRECTIONS: chrome scheme is invalid", TemplateAction::DIRECTIONS,
+       "chrome://settings", false},
+      {"REVIEWS: HTTP scheme is valid", TemplateAction::REVIEWS,
+       "http://example.com", true},
+      {"REVIEWS: HTTPS scheme is valid", TemplateAction::REVIEWS,
+       "https://example.com", true},
+      {"REVIEWS: tel scheme is invalid", TemplateAction::REVIEWS, "tel:123456",
+       false},
+      {"REVIEWS: chrome scheme is invalid", TemplateAction::REVIEWS,
+       "chrome://settings", false},
+  };
 
+  for (const auto& test_case : test_cases) {
+    TemplateAction template_action;
+    template_action.set_action_type(test_case.action_type);
+    template_action.set_action_uri(test_case.action_url);
+
+    TemplateURLRef::SearchTermsArgs search_terms_args;
+    SearchTermsData search_terms_data;
+    TemplateURLData template_url_data;
+    template_url_data.SetURL("https://www.google.com");
+    auto template_url = std::make_unique<TemplateURL>(template_url_data);
+
+    auto action = BaseSearchProvider::CreateActionInSuggest(
+        std::move(template_action), template_url->url_ref(), search_terms_args,
+        search_terms_data);
+
+    EXPECT_EQ(action != nullptr, test_case.expect_valid)
+        << "while evaluating case `" << test_case.test_name << "`";
+  }
+}
 
 TEST_F(BaseSearchProviderTest, SuggestTemplateInfoPopulatesMatch) {
   TemplateURLData data;
Loading diff…

Original Bug Report

reported by li...@chromium.org

Potential privilege escalation to chrome:// via untrusted Suggest Action URIs on iOS

Flapjack, 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. Please see go/chrome-ai-generated-security-bugs-faq for more information.

Overview: The iOS Omnibox improperly treats ‘Suggest Action’ URIs from search providers as trusted browser-initiated navigations. This assigns them a privileged transition type, bypassing security checks and allowing an attacker-controlled search provider to force navigations to sensitive chrome:// pages.

Affected files:

  • ios/chrome/browser/omnibox/coordinator/popup/omnibox_popup_mediator.mm
  • ios/chrome/browser/url_loading/model/scene_url_loading_service.mm
  • ios/web/navigation/crw_wk_navigation_handler.mm

Estimated timestamp from git blame: 2026-02-20

Description

A potential vulnerability in the iOS Omnibox allows a malicious or compromised search provider to trigger navigations to privileged chrome:// URLs. This occurs because ‘Suggest Action’ URIs (such as fallback actions like ‘Reviews’ or ‘Directions’ associated with a suggestion chip) are not validated for safe schemes during parsing and are subsequently treated as trusted browser-initiated navigations on iOS.

When a user taps a Suggest Action chip (e.g., omnibox::SuggestTemplateInfo_TemplateAction_ActionType_REVIEWS), the tap is handled by -[OmniboxPopupMediator openNewTabWithSuggestAction:]. This method creates a navigation command using the commandWithURLFromChrome: initializer:

- (void)openNewTabWithSuggestAction:(SuggestAction*)suggestAction {
  DCHECK(self.sceneHandler);
  OpenNewTabCommand* command =
      [OpenNewTabCommand commandWithURLFromChrome:suggestAction.actionURI
                                      inIncognito:NO];
  [self.sceneHandler openURLInNewTab:command];
}

Using commandWithURLFromChrome: inherently flags the command as trusted (_fromChrome = YES). Consequently, SceneUrlLoadingService::LoadUrlInNewTab unconditionally assigns the transition type ui::PAGE_TRANSITION_TYPED to the resulting navigation load parameters.

In the iOS Web layer, -[CRWWKNavigationHandler shouldAllowAppSpecificURLNavigationAction:transition:] relies on this transition type to determine if a navigation to an app-specific (privileged) URL is permitted. Because ui::PAGE_TRANSITION_TYPED is explicitly allowed (intended for user-typed URLs or bookmarks), the security check is bypassed, allowing the navigation to proceed to the internal chrome:// page.

Potential Exploitation Scenario

Note: These are suggested steps based on static analysis. We do not currently have a working proof of concept.

  1. An attacker compromises the user’s default search provider or performs a Man-in-the-Middle (MITM) attack on a custom HTTP search provider.
  2. The attacker injects a malicious JSON response containing a google:suggesttemplate or google:entityinfo field with base64-encoded protobuf data.
  3. Within the encoded protobuf, the attacker specifies a privileged URI (e.g., chrome://settings) in the action_uri field of an action suggestion (e.g., a ‘Reviews’ action).
  4. The SearchSuggestionParser decodes this proto and instantiates an OmniboxActionInSuggest object without validating the scheme of the action_uri.
  5. The user interacts with the omnibox and taps the suggestion chip associated with the malicious action.
  6. Chrome opens a new tab and navigates to the privileged chrome:// page, bypassing normal security boundaries.

Impact

This issue allows an untrusted source (the search provider) to bypass the security boundary protecting internal chrome:// pages on iOS. While iOS chrome:// pages have a limited attack surface and generally do not allow state manipulation via URL parameters, this primitive leads to UI spoofing and could potentially be combined with other flaws.

Suggested Fix

  1. Input Validation: Add strict scheme validation (e.g., requiring HTTP/HTTPS) for the action_uri field when parsing the protobuf in SearchSuggestionParser::ParseSuggestResults or when instantiating OmniboxActionInSuggest in BaseSearchProvider::CreateActionInSuggest.
  2. Appropriate Trust Level: Re-evaluate the use of [OpenNewTabCommand commandWithURLFromChrome:] in -[OmniboxPopupMediator openNewTabWithSuggestAction:]. Since Suggest Actions originate from an external network response, they should likely not be treated with the same elevated trust as native browser UI clicks.

Evaluated with Chrome root at commit: 4a3e9db74111a3c6c4b3acfd70050a05077cf27a


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.

View on issue tracker