High CVSS 3.1 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
3.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in iOS
DescriptionInsufficient validation of untrusted input in iOS
ComponentChromium
Sub-componentiOS
Bug ClassInput Validation
Tracker503862359
Fix commitc1bb5bb34b47 (chromium/src) +1/-1
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-04-17 (per vendor advisory)
Fix landed2026-04-24 (public git)
Disclosed2026-05-27
Patch gap33 days — fix public in source before the advisory

Background

`AddTemplateURLBySearchableURL(const GURL&)`
The entry point that registers an auto-generated search engine from a renderer-supplied “searchable URL”. It gated acceptance only on searchable_url.is_valid().
`GURL::is_valid()` vs `SchemeIsHTTPOrHTTPS()`
is_valid() only means the URL parsed into a structurally well-formed GURL; a javascript: URL is structurally valid. SchemeIsHTTPOrHTTPS() is the actual scheme allow-list check.
`{searchTerms}` substitution / UXSS
A registered TemplateURL keyword expands {searchTerms} at query time. If the stored URL keeps a javascript: scheme, selecting the omnibox suggestion runs the script in the active tab’s origin — universal cross-site scripting.

Root Cause Analysis

The violated invariant is that a renderer-influenced URL persisted as a navigable search engine must be restricted to safe network schemes (HTTP/HTTPS). AddTemplateURLBySearchableURL checked only if (!searchable_url.is_valid()) return;, which validates structure but not scheme. A compromised renderer that forges a SearchEngineMessage (bypassing the search_engine.ts content-script sanitization) can supply a javascript: URL; it is is_valid(), so the helper registers it as an auto-generated TemplateURL. Later {searchTerms} substitution preserves the javascript: scheme, and when the user invokes the keyword the omnibox load path executes the payload in the active tab’s context — UXSS across arbitrary origins.

The patch tightens the guard to if (!searchable_url.is_valid() || !searchable_url.SchemeIsHTTPOrHTTPS()) return; in search_engine_tab_helper.mm, rejecting any non-HTTP(S) scheme (including javascript:) before the URL can become a search engine. The commit message confirms this was an oversight: sibling methods in the same file (AddTemplateURLByOSDD, GenerateKeywordFromNavigationItem) already restrict schemes to HTTP/HTTPS, and this one did not. The advisory labels the bug “Insufficient validation of untrusted input” (CWE-20); the diff substantiates exactly that — a missing scheme allow-list on renderer-supplied input — with the security consequence being UXSS (a cross-origin script-execution / sandbox-escape outcome). This is a pure logic/validation fix with no memory-safety sink; the memory-safety vocabulary does not apply.

Key insight
The code validated that the renderer-supplied search URL was well-formed but never that its scheme was HTTP/HTTPS, so a forged javascript: URL passed is_valid() and was persisted as a search engine whose keyword, once used, ran attacker script in any origin.

Attack Path

  1. Renderer compromise Attacker gains code execution in the iOS WebKit renderer (com.apple.WebKit.WebContent) (precondition per advisory; not shown by the diff — (inferred)).
  2. Forge the IPC Send a crafted SearchEngineMessage directly to the browser’s kIsolatedWorld handler, bypassing the search_engine.ts content script that would otherwise sanitize the URL.
  3. Supply a javascript: payload Use the searchableUrl command with e.g. javascript:alert(document.domain)//{searchTerms}.
  4. Pass validation SearchEngineJavaScriptFeature::ScriptMessageReceived forwards to AddTemplateURLBySearchableURL, which accepts the URL because the old code only checked is_valid(); keyword registration proceeds (the advisory notes javascript: URLs also satisfy the empty-host autogenerated-keyword check — (inferred), not in the diff).
  5. Persist the engine The malicious TemplateURL is stored in the user’s local search-engine database.
  6. UXSS execution The user later types the auto-generated keyword plus a query; {searchTerms} is substituted while the javascript: scheme is preserved, and selecting the suggestion runs the script in the active tab’s origin — universal cross-site scripting.

Impact Assessment

The defect is in iOS browser-process search-engine registration, with the untrusted input originating from a (compromised) renderer; exploitation also depends on a user later invoking the auto-generated keyword (UI:R and multi-step, consistent with the advisory’s AC:H / confidentiality-only CVSS). Confirmed by the diff: a missing scheme allow-list allowing a javascript: URL to be persisted as a search engine. The gained primitive is UXSS — script execution in arbitrary web origins from the compromised renderer, a confidentiality/integrity breach of the web security model. It is not memory corruption or RCE; the renderer-compromise and omnibox-execution steps are preconditions asserted by the advisory, and the empty-host keyword-acceptance detail is inferred and not in the diff.

Files Changed

  • ios/chrome/browser/search_engines/model/search_engine_tab_helper.mm

Audit Directions

  • Scheme checks on persisted/navigable URLs
    Audit all TemplateURL/search-engine and omnibox entry points (iOS and desktop) that accept renderer- or page-supplied URLs and gate only on is_valid(); require an explicit scheme allow-list before persistence or navigation.
  • Sibling-method parity
    Compare AddTemplateURLByOSDD, GenerateKeywordFromNavigationItem, and other methods in search_engine_tab_helper.mm; this bug was a parity gap, so confirm every registration path enforces the same scheme policy.
  • Renderer-forgeable messages
    Treat SearchEngineJavaScriptFeature / kIsolatedWorld messages as attacker-controlled (content-script sanitization is bypassable from a compromised renderer) and re-validate all fields browser-side.
  • `{searchTerms}` scheme preservation
    Review query-time substitution (SearchProvider::Start, omnibox load paths) for any case where a stored dangerous scheme survives into navigation (javascript:, data:, chrome:).
From c1bb5bb34b479e31a052389d8b532a1984963d91 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
Date: Fri, 24 Apr 2026 10:28:44 -0700
Subject: [PATCH] [iOS] Add scheme validation for auto-generated search engines

SearchEngineTabHelper::AddTemplateURLBySearchableURL lacked scheme
validation, allowing malicious renderers to register javascript: URLs as
search engines. This could lead to UXSS.

This appears to be an oversight, as other methods in the same file, such
as AddTemplateURLByOSDD and GenerateKeywordFromNavigationItem, already
correctly restrict schemes to HTTP/HTTPS.

This CL adds a check to ensure the URL scheme is HTTP or HTTPS in
AddTemplateURLBySearchableURL.

Fixed: 503862359
Change-Id: I54f6b4fa558d17d92b38dad41c7416eb481ee18e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7781599
Reviewed-by: Jérôme Lebel <jlebel@chromium.org>
Commit-Queue: Andrew Paseltiner <apaseltiner@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1620308}
---

diff --git a/ios/chrome/browser/search_engines/model/search_engine_tab_helper.mm b/ios/chrome/browser/search_engines/model/search_engine_tab_helper.mm
index cbda250..23db603 100644
--- a/ios/chrome/browser/search_engines/model/search_engine_tab_helper.mm
+++ b/ios/chrome/browser/search_engines/model/search_engine_tab_helper.mm
@@ -180,7 +180,7 @@
 // https://cs.chromium.org/chromium/src/chrome/browser/ui/search_engines/search_engine_tab_helper.cc
 void SearchEngineTabHelper::AddTemplateURLBySearchableURL(
     const GURL& searchable_url) {
-  if (!searchable_url.is_valid()) {
+  if (!searchable_url.is_valid() || !searchable_url.SchemeIsHTTPOrHTTPS()) {
     return;
   }
 
Loading diff…

Original Bug Report

reported by li...@chromium.org

Potential UXSS via Missing Scheme Validation in iOS SearchEngineTabHelper

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 SearchEngineTabHelper on iOS lacks scheme validation when adding auto-generated search engines. A compromised renderer can register a malicious javascript: URL as a search engine. If a user subsequently searches using the generated keyword, the JavaScript payload executes in the context of the active tab, leading to Universal Cross-Site Scripting (UXSS).

Affected files:

  • ios/chrome/browser/search_engines/model/search_engine_tab_helper.mm

Estimated timestamp from git blame: 2018-11-07

Description

A potential missing scheme validation vulnerability exists in SearchEngineTabHelper::AddTemplateURLBySearchableURL on iOS. This function registers auto-generated search engines based on IPC messages from the renderer. While it checks if the provided searchable_url is valid using is_valid(), it fails to ensure the scheme is restricted to safe protocols like HTTP or HTTPS. Because javascript: URLs are considered structurally valid by the GURL parser, they can bypass this check.

If a compromised renderer process directly sends a forged SearchEngineMessage bypassing the normal JS content script sanitization, it can inject a javascript: URL into the TemplateURL service. Once registered, if the user types the associated keyword into the Omnibox and selects the suggestion, the javascript: payload is executed in the context of the currently active tab. This provides a sandbox escape leading to Universal Cross-Site Scripting (UXSS) across arbitrary web origins.

Potential Attack Steps

Note: These are suggested steps based on static code analysis. Our tooling agent cannot execute code to dynamically verify a working proof-of-concept.

  1. Renderer Compromise: An attacker exploits a vulnerability in the iOS WebKit renderer process (com.apple.WebKit.WebContent).
  2. Forged IPC Message: From the compromised renderer, the attacker sends a forged SearchEngineMessage IPC directly to the browser process via internal WebKit messaging. This bypasses the native search_engine.ts content script (which would normally sanitize the URL into a non-executable format) and targets the kIsolatedWorld message handler.
  3. Malicious Payload: The message uses the searchableUrl command with a payload like {"command": "searchableUrl", "url": "javascript:alert(document.domain)//{searchTerms}"}.
  4. Browser Processing: SearchEngineJavaScriptFeature::ScriptMessageReceived extracts the URL and passes it to SearchEngineTabHelper. Upon the next navigation completion, AddTemplateURLBySearchableURL accepts the URL because searchable_url.is_valid() returns true.
  5. Database Registration: TemplateURLService::CanAddAutogeneratedKeyword permits the addition because javascript: URLs lack a host component (url.GetHost().empty() evaluates to true). The search engine is then persisted in the user’s local database.
  6. User Execution: The user later types the auto-generated keyword followed by a space and a query into the Omnibox.
  7. UXSS Trigger: SearchProvider::Start resolves the match, directly substituting {searchTerms} while preserving the javascript: scheme. When the suggestion is selected, LocationBarCoordinator::loadGURLFromLocationBar recognizes the scheme and explicitly calls LoadJavaScriptURL(...), executing the attacker’s script in the context of the active tab.

In ios/chrome/browser/search_engines/model/search_engine_tab_helper.mm, update AddTemplateURLBySearchableURL to explicitly require HTTP or HTTPS schemes:

  if (!searchable_url.is_valid() || !searchable_url.SchemeIsHTTPOrHTTPS()) {
    return;
  }

Evaluated with Chrome root at commit: c1eba8ce379f5d218a2948fafbb5dd72cfa30529


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.

Raised in root component due to access or custom field issues on 1974931

View on issue tracker