Medium watch CVSS 4.4 chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
4.4
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Mobile
DescriptionIncorrect authorization in Mobile
ComponentMobile
Sub-componentUI>Browser>Mobile
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker553141660
Fix commiteb8268508062 (chromium/src) +0/-10
CWECWE-863
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-08-26 (per vendor advisory)
Fix landed2026-08-31 (public git)
Disclosed2026-09-22
Patch gap22 days — fix public in source before the advisory

Background

`IntentHandler`
The Chrome-for-Android class that inspects incoming Intent objects and decides what URL to load and with what context.
`extractUrlFromIntent`
A helper in IntentHandler that walks a chain of extractors to recover a target URL from an incoming Intent.
`CUSTOM_TAB_SCHEME`
A "customtab" URI scheme constant, once used by a document-mode prototype to smuggle a target URL as the Intent data URI’s query string.
`getUrlForCustomTab`
The extractor that returned data.getQuery() when an Intent’s data URI carried the customtab scheme, bypassing normal URL parsing.

Root Cause Analysis

The extractUrlFromIntent chain in IntentHandler.java included a call to getUrlForCustomTab, which trusted any Intent whose data URI used the customtab scheme and returned that URI’s raw query component as the URL to load. This code was leftover from a document-mode prototype, was unused by Chrome, and was not advertised in the AndroidManifest, yet it remained reachable because any app could craft an explicit Intent at Chrome’s launcher activity with a customtab: data URI. Because the query string was returned directly rather than being parsed and validated like an ordinary data URI, the path constituted an incorrect-authorization / logic error where an unintended, less-scrutinized channel could influence which URL Chrome navigated to.

The fix removes the getUrlForCustomTab call from extractUrlFromIntent, deletes the method itself, and drops the CUSTOM_TAB_SCHEME constant from UrlConstants.java, eliminating the scheme as a recognized input entirely. With the extractor gone, customtab: intents fall through to the remaining well-vetted extractors, closing the unauthorized path.

Key insight
The single mistake was leaving a dead prototype code path that unconditionally honored the customtab scheme and returned its unparsed query as a URL; the fix removes that extractor and its scheme constant so the input is no longer recognized at all.

Attack Path

  1. Craft a custom-tab intent A malicious local app builds an explicit Intent targeting Chrome’s launcher activity with a data URI using the customtab scheme and an attacker-chosen query string.
  2. Reach the extractor Chrome’s IntentHandler calls extractUrlFromIntent, which invokes getUrlForCustomTab and returns data.getQuery() without the validation applied to ordinary URLs.
  3. Influence navigation The returned query is treated as the target URL, letting the caller steer Chrome toward an attacker-controlled destination through an unintended channel. (inferred)
  4. Incorrect authorization outcome The load proceeds via a path that was never meant to be exposed, matching the advisory’s “Incorrect authorization in Mobile” classification. (inferred)

Impact Assessment

An attacker with a local app capable of sending explicit intents to Chrome’s launcher activity could influence the URL Chrome resolves through the unadvertised customtab scheme, an incorrect-authorization logic error in the mobile browser process. The precondition is the ability to deliver a crafted explicit Intent to Chrome on Android; the advisory states no exploited-in-the-wild status, and any escalation beyond influencing URL extraction is not demonstrated by the patch (inferred).

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
  • components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlConstants.java

Audit Directions

  • Dead prototype code paths
    Audit IntentHandler and other entry points for extractors and schemes kept alive from abandoned prototypes that remain reachable via explicit intents despite not being advertised in the AndroidManifest.
  • Unparsed URI components
    Flag any code that returns Uri.getQuery(), getFragment(), or similar raw components as a navigable URL without the validation applied to normal URLs.
  • Scheme-gated trust
    Review all UrlConstants scheme comparisons that grant special handling based solely on data.getScheme(), confirming each has a legitimate, manifest-backed caller and proper authorization checks.
From eb82685080622cf8f9cca0197f30e6606b921185 Mon Sep 17 00:00:00 2001
From: Michael Thiessen <[email protected]>
Date: Mon, 31 Aug 2026 09:15:14 -0700
Subject: [PATCH] Reland "Remove CustomTabs scheme from Intent Handling"

(This change was incorrectly reverted and not responsible for
HistoryTabHelperTest#testAppHistory failing)

This code was added for a document mode prototype:
https://codereview.chromium.org/1838483003

It's unused in Chrome, and our AndroidManifest does not advertise
support for it. I really doubt we need a kill switch for this as there's
no legitimate reason for this to be used and would require explicit
intents aimed at our launcher activity.

Bug: 553141660
Change-Id: I1ad486c18f6dc5936d7ff8f31db116314a0edf88
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8311085
Reviewed-by: Yaron Friedman <[email protected]>
Reviewed-by: Jinsuk Kim <[email protected]>
Bot-Commit: [email protected] <[email protected]>
Commit-Queue: Michael Thiessen <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1689141}
---
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
index c37dd549..00464284 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
@@ -1299,7 +1299,6 @@
     private static @Nullable String extractUrlFromIntent(@Nullable Intent intent) {
         if (intent == null) return null;
         String url = getUrlFromVoiceSearchResult(intent);
-        if (url == null) url = getUrlForCustomTab(intent);
         if (url == null) url = getUrlForWebapp(intent);
         if (url == null) url = getUrlFromShareIntent(intent);
         if (url == null) url = getUrlForHandoff(intent);
@@ -1372,14 +1371,6 @@
         return TemplateUrlServiceFactory.getForProfile(profile).getUrlForSearchQuery(text);
     }
 
-    private static @Nullable String getUrlForCustomTab(@Nullable Intent intent) {
-        if (intent == null || intent.getData() == null) return null;
-        Uri data = intent.getData();
-        return TextUtils.equals(data.getScheme(), UrlConstants.CUSTOM_TAB_SCHEME)
-                ? data.getQuery()
-                : null;
-    }
-
     private static @Nullable String getUrlForWebapp(@Nullable Intent intent) {
         if (intent == null || intent.getData() == null) return null;
         Uri data = intent.getData();
diff --git a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlConstants.java b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlConstants.java
index 8dfeceb..0db9d99 100644
--- a/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlConstants.java
+++ b/components/embedder_support/android/java/src/org/chromium/components/embedder_support/util/UrlConstants.java
@@ -21,7 +21,6 @@
     public static final String CHROME_EXTENSION_SCHEME = "chrome-extension";
     public static final String CHROME_NATIVE_SCHEME = "chrome-native";
     public static final String CONTENT_SCHEME = "content";
-    public static final String CUSTOM_TAB_SCHEME = "customtab";
     public static final String DATA_SCHEME = "data";
     public static final String DEVTOOLS_SCHEME = "devtools";
     public static final String DISTILLER_SCHEME = "chrome-distiller";
Loading diff…

Original Bug Report

reported by [email protected]

Potential intent security check bypass loading internal URLs via CCT data URI rewrite desync

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 URL extraction desync exists between LaunchIntentDispatcher and CustomTabIntentDataProvider when launching Custom Tabs. When LaunchIntentDispatcher validates an incoming intent, scheme-gated extractors return a benign URL, but the intent’s data URI is subsequently rewritten while preserving unverified extras such as EXTRA_HANDOFF_URL. The resulting Custom Tab initializes without re-validating the intent, falling through to EXTRA_HANDOFF_URL and potentially allowing untrusted Android apps to navigate to privileged internal schemes like chrome://.

Affected files:

  • chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
  • chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabIntentHandler.java

Estimated timestamp from git blame: 2026-04-16

Summary

When launching a Custom Tab via LaunchIntentDispatcher, security validation via IntentHandler.shouldIgnoreIntent() is performed on the original incoming Intent. When createCustomTabActivityIntent() prepares the launch intent for CustomTabActivity, it rewrites the intent’s data URI to the extracted benign URL while copying all intent extras verbatim. Because URL extraction in IntentHandler.extractUrlFromIntent() relies on a prioritized chain where earlier extractors (getUrlForCustomTab and getUrlForWebapp) are gated on specific schemes (customtab:// or webapp://), rewriting the data scheme to https:// causes CustomTabIntentDataProvider to fall through to getUrlForHandoff() (EXTRA_HANDOFF_URL), which is ungated and unverified. CustomTabActivity initial tab creation does not re-run shouldIgnoreIntent(), leading to a potential security check bypass.

Affected Files

  • chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
  • chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabIntentHandler.java

Root Cause Analysis

  1. Validation on Original Intent (LaunchIntentDispatcher.java:285-292): launchCustomTabActivity() validates the untrusted incoming intent:

    if (IntentHandler.shouldIgnoreIntent(mIntent, mActivity, isCustomTab)) {
        return false;
    }
    

    IntentHandler.extractUrlFromIntent(intent) (IntentHandler.java:1299-1310) evaluates getUrlForCustomTab(intent) before getUrlForHandoff(intent):

    private static @Nullable String extractUrlFromIntent(@Nullable Intent intent) {
        if (intent == null) return null;
        String url = getUrlFromVoiceSearchResult(intent);
        if (url == null) url = getUrlForCustomTab(intent);    // [Extractor 2]
        if (url == null) url = getUrlForWebapp(intent);       // [Extractor 3]
        if (url == null) url = getUrlFromShareIntent(intent); // [Extractor 4]
        if (url == null) url = getUrlForHandoff(intent);      // [Extractor 5]
        if (url == null) url = intent.getDataString();
        ...
    }
    

    For data = "customtab://x?https://benign.example", Extractor 2 (IntentHandler.java:1375-1381) matches the scheme and returns "https://benign.example". Because this URL is safe, IntentHandler.shouldIgnoreIntent() returns false.

  2. Data Rewrite & Extra Propagation (LaunchIntentDispatcher.java:197-210): createCustomTabActivityIntent() creates a copy of the intent carrying over all extras (including EXTRA_HANDOFF_URL), but rewrites the data URI:

    String uriString = IntentHandler.getUrlFromIntent(intent); // "https://benign.example"
    Uri uri = Uri.parse(uriString);
    Intent newIntent = new Intent(intent);                     // Copies all extras
    if (ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_DONT_OVERRIDE_INTENT_MIME_TYPE)) {
        newIntent.setDataAndType(uri, intent.getType());       // Data rewritten to https://...
    } else {
        newIntent.setAction(Intent.ACTION_VIEW);
        newIntent.setData(uri);
    }
    
  3. Extractor Fallthrough in CustomTabActivity (CustomTabIntentDataProvider.java:1133-1148): When CustomTabActivity receives the launch intent, resolveUrlToLoad() calls IntentHandler.getUrlFromIntent():

    • Extractor 2 (getUrlForCustomTab) checks data.getScheme() == "customtab" $\rightarrow$ false.
    • Extractor 3 (getUrlForWebapp) checks data.getScheme() == "webapp" $\rightarrow$ false.
    • Extractor 4 (getUrlFromShareIntent) checks action == ACTION_SEND $\rightarrow$ false.
    • Extractor 5 (getUrlForHandoff, IntentHandler.java:1391-1394) checks EXTRA_HANDOFF_URL with no scheme check or sender verification $\rightarrow$ returns attacker-controlled URL (e.g. "chrome://flags").
  4. Unvalidated Navigation (CustomTabIntentHandler.java:74-84 / DefaultCustomTabIntentHandlingStrategy.java:89-95): CustomTabIntentHandler.handleInitialIntent() executes without invoking IntentHandler.shouldIgnoreIntent(), navigating directly to the URL returned by getUrlToLoad() via tab.loadUrl().

Potential Reproduction Steps

(Note: These are suggested steps identified via static source code analysis; automated tooling does not execute code.)

  1. From an installed Android application, construct an explicit Intent:
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setClassName("com.android.chrome", "com.google.android.apps.chrome.IntentDispatcher");
    intent.setData(Uri.parse("customtab://x?https://benign.example"));
    intent.putExtra("org.chromium.chrome.browser.handoff_url", "chrome://flags");
    Bundle bundle = new Bundle();
    bundle.putBinder("android.support.customtabs.extra.SESSION", null);
    intent.putExtras(bundle);
    
  2. Call context.startActivity(intent).
  3. ChromeLauncherActivity dispatches to LaunchIntentDispatcher.launchCustomTabActivity().
  4. IntentHandler.shouldIgnoreIntent() verifies "https://benign.example" and allows the launch.
  5. createCustomTabActivityIntent() rewrites the data URI to https://benign.example, preserving EXTRA_HANDOFF_URL.
  6. CustomTabActivity extracts the URL to load, falling through to getUrlForHandoff(), which yields "chrome://flags".
  7. CustomTabActivityNavigationController.navigate() loads "chrome://flags" into the Custom Tab.

Suggested Fix

  1. Strip IntentHandler.EXTRA_HANDOFF_URL (and other unverified extras not intended for external consumption) in LaunchIntentDispatcher.launchCustomTabActivity() or createCustomTabActivityIntent() before launching CustomTabActivity.
  2. Ensure getUrlForHandoff() validates that the sender is trustworthy (IntentHandler.wasIntentSenderChrome()) before extracting EXTRA_HANDOFF_URL.
  3. Alternatively, re-verify the resolved URL in CustomTabIntentHandler.handleInitialIntent() or CustomTabIntentDataProvider using IntentHandler.shouldIgnoreIntent() or ExternalIntentUrlChecker.isUnsafeExternalIntentUrl().

Evaluated with Chrome root at commit: c6718ebbf2c677c6f63691e28c7801c563f6b19c


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