CVE-2026-9950
Overview
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-formedGURL; ajavascript:URL is structurally valid.SchemeIsHTTPOrHTTPS()is the actual scheme allow-list check.- `{searchTerms}` substitution / UXSS
- A registered
TemplateURLkeyword expands{searchTerms}at query time. If the stored URL keeps ajavascript: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.
javascript: URL passed is_valid() and was persisted as a search engine whose keyword, once used, ran attacker script in any origin.Attack Path
- Renderer compromise
Attacker gains code execution in the iOS WebKit renderer (
com.apple.WebKit.WebContent) (precondition per advisory; not shown by the diff — (inferred)). - Forge the IPC
Send a crafted
SearchEngineMessagedirectly to the browser’skIsolatedWorldhandler, bypassing thesearch_engine.tscontent script that would otherwise sanitize the URL. - Supply a javascript: payload
Use the
searchableUrlcommand with e.g.javascript:alert(document.domain)//{searchTerms}. - Pass validation
SearchEngineJavaScriptFeature::ScriptMessageReceivedforwards toAddTemplateURLBySearchableURL, which accepts the URL because the old code only checkedis_valid(); keyword registration proceeds (the advisory notesjavascript:URLs also satisfy the empty-host autogenerated-keyword check — (inferred), not in the diff). - Persist the engine
The malicious
TemplateURLis stored in the user’s local search-engine database. - UXSS execution
The user later types the auto-generated keyword plus a query;
{searchTerms}is substituted while thejavascript:scheme is preserved, and selecting the suggestion runs the script in the active tab’s origin — universal cross-site scripting.
Impact Assessment
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 URLsAudit all
TemplateURL/search-engine and omnibox entry points (iOS and desktop) that accept renderer- or page-supplied URLs and gate only onis_valid(); require an explicit scheme allow-list before persistence or navigation. - Sibling-method parityCompare
AddTemplateURLByOSDD,GenerateKeywordFromNavigationItem, and other methods insearch_engine_tab_helper.mm; this bug was a parity gap, so confirm every registration path enforces the same scheme policy. - Renderer-forgeable messagesTreat
SearchEngineJavaScriptFeature/kIsolatedWorldmessages as attacker-controlled (content-script sanitization is bypassable from a compromised renderer) and re-validate all fields browser-side. - `{searchTerms}` scheme preservationReview query-time substitution (
SearchProvider::Start, omnibox load paths) for any case where a stored dangerous scheme survives into navigation (javascript:,data:,chrome:).
Patch
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;
}
Original Bug Report
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.
- Renderer Compromise: An attacker exploits a vulnerability in the iOS WebKit renderer process (
com.apple.WebKit.WebContent). - Forged IPC Message: From the compromised renderer, the attacker sends a forged
SearchEngineMessageIPC directly to the browser process via internal WebKit messaging. This bypasses the nativesearch_engine.tscontent script (which would normally sanitize the URL into a non-executable format) and targets thekIsolatedWorldmessage handler. - Malicious Payload: The message uses the
searchableUrlcommand with a payload like{"command": "searchableUrl", "url": "javascript:alert(document.domain)//{searchTerms}"}. - Browser Processing:
SearchEngineJavaScriptFeature::ScriptMessageReceivedextracts the URL and passes it toSearchEngineTabHelper. Upon the next navigation completion,AddTemplateURLBySearchableURLaccepts the URL becausesearchable_url.is_valid()returnstrue. - Database Registration:
TemplateURLService::CanAddAutogeneratedKeywordpermits the addition becausejavascript:URLs lack a host component (url.GetHost().empty()evaluates totrue). The search engine is then persisted in the user’s local database. - User Execution: The user later types the auto-generated keyword followed by a space and a query into the Omnibox.
- UXSS Trigger:
SearchProvider::Startresolves the match, directly substituting{searchTerms}while preserving thejavascript:scheme. When the suggestion is selected,LocationBarCoordinator::loadGURLFromLocationBarrecognizes the scheme and explicitly callsLoadJavaScriptURL(...), executing the attacker’s script in the context of the active tab.
Recommended Fix
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