Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect reference resolution in CustomTabs
DescriptionIncorrect reference resolution in CustomTabs
ComponentCustomTabs
Bug ClassLogic Error
Tracker534556413
Fix commit2ba952e0154b (chromium/src) +10/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java
  • chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationControllerTest.java
From 2ba952e0154b8e9d6d7b1e755f82b576255a541e Mon Sep 17 00:00:00 2001
From: Sinan Sahin <sinansahin@google.com>
Date: Thu, 16 Jul 2026 10:30:52 -0700
Subject: [PATCH] Add CATEGORY_BROWSABLE to CCT 'Open in browser' intent

Similar to https://crrev.com/c/8076861, but this time the entry point is
CustomTabActivityNavigationController.

Bug: 534556413
Change-Id: I68c36ea42eb02402fbabf3114936bcfe7fb8d8fe
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8103622
Commit-Queue: Sinan Sahin <sinansahin@google.com>
Reviewed-by: Michael Thiessen <mthiesse@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1663278}
---

diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java
index ef741b7..657e50e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java
@@ -366,6 +366,7 @@
         assertUrlNotNullForOpenInBrowser(url, tab);
 
         Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+        intent.addCategory(Intent.CATEGORY_BROWSABLE);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.putExtra(IntentHandler.EXTRA_FROM_OPEN_IN_BROWSER, true);
         ResolveInfo resolveInfo = DefaultBrowserInfo.getDefaultWebBrowserInfo();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationControllerTest.java
index 362d2c26..298180f9 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationControllerTest.java
@@ -22,6 +22,7 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Build;
@@ -248,7 +249,10 @@
         ExternalNavigationDelegateImpl.setWillChromeHandleIntentHookForTesting(intent -> true);
         mNavigationController.openCurrentUrlInBrowser();
         verify(env.activity, never()).startActivity(any());
-        verify(mTabController).detachAndStartReparenting(any(), any(), any());
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mTabController).detachAndStartReparenting(intentCaptor.capture(), any(), any());
+        Intent intent = intentCaptor.getValue();
+        assertTrue(intent.hasCategory(Intent.CATEGORY_BROWSABLE));
     }
 
     @Test
@@ -284,7 +288,10 @@
         ExternalNavigationDelegateImpl.setWillChromeHandleIntentHookForTesting(intent -> false);
         mNavigationController.openCurrentUrlInBrowser();
         verify(mTabController, never()).detachAndStartReparenting(any(), any(), any());
-        verify(env.activity).startActivity(any(), any());
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(env.activity).startActivity(intentCaptor.capture(), any());
+        Intent intent = intentCaptor.getValue();
+        assertTrue(intent.hasCategory(Intent.CATEGORY_BROWSABLE));
         verify(mFinishHandler).onFinish(FinishReason.OPEN_IN_BROWSER, true);
     }
 
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential CATEGORY_BROWSABLE bypass when opening Chrome Custom Tab URLs in browser

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: When a user selects ‘Open in browser’ in a Chrome Custom Tab (CCT), Chrome dispatches an implicit ACTION_VIEW intent with the active URL but fails to set Intent.CATEGORY_BROWSABLE. Under specific system conditions where no default browser has been selected, this intent can resolve to and launch non-browsable activities of other installed apps. This allows a web origin to potentially bypass the CATEGORY_BROWSABLE security boundary and deliver arbitrary deep-links to internal, non-web-reachable activities.

Affected files:

  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java

Estimated timestamp from git blame: 2016-01-14

Description

There is a potential security boundary bypass in Chrome Custom Tabs (CCT) on Android. When a user selects “Open in browser” from the CCT overflow menu or custom toolbar buttons, Chrome dispatches an implicit ACTION_VIEW intent containing the current tab’s URL. However, this intent does not contain the Intent.CATEGORY_BROWSABLE category.

Under standard Android intent resolution rules, an implicit intent with no categories can match any activity whose intent-filter contains <category android:name="android.intent.category.DEFAULT" />, regardless of whether it lacks CATEGORY_BROWSABLE. App developers omit CATEGORY_BROWSABLE specifically to prevent web-to-app launches and secure their internal deep-link handlers. By dispatching a web-derived URL in an intent without CATEGORY_BROWSABLE, Chrome can accidentally route arbitrary attacker-controlled web URLs to these internal, non-web-reachable activities.

Root Cause and Analysis

In chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationController.java, the openCurrentUrlInBrowser() method prepares the intent:

// Line 368-370
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(IntentHandler.EXTRA_FROM_OPEN_IN_BROWSER, true);

Note that no category is added to the intent.

If multiple browsers are installed and no default is set on the device, DefaultBrowserInfo.getDefaultWebBrowserInfo() returns the system’s ResolverActivity (package name "android"). The package check on line 373-376 restricts the package to "android", fails to resolve the https scheme for that package, and clears the package restriction:

ResolveInfo resolveInfo = DefaultBrowserInfo.getDefaultWebBrowserInfo();
if (resolveInfo != null) {
    intent.setPackage(resolveInfo.activityInfo.packageName);
    // crbug.com/40801270
    if (intent.resolveActivity(mActivity.getPackageManager()) == null) {
        intent.setPackage(null);
    }
}

As a result, the intent is dispatched as a fully implicit intent with setPackage(null). Since the intent target is external and willChromeHandleIntent returns false, execution falls through to the final fallback block:

// Line 425-427
} else if (PackageManagerUtils.canResolveActivity(intent)) {
    mActivity.startActivity(intent, startActivityOptions);
    finish(FinishReason.OPEN_IN_BROWSER);
}

The intent is then dispatched globally without CATEGORY_BROWSABLE, matching and potentially opening non-browsable activities.

This behavior is an asymmetric contrast to the similar menu context-path in chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java which correctly enforces the security restriction:

// Line 565-566
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url.getSpec()));
intent.addCategory(Intent.CATEGORY_BROWSABLE); // Enforced category

Potential Trigger Steps

Note: The following are suggested/potential steps to demonstrate the issue, as our tooling agent does not currently have the capability to execute code or run interactive proofs.

  1. Prepare an Android device with at least two web browsers installed, and with no default browser chosen in system settings.
  2. Install a test application containing an activity that handles https://victim-host.example and defines the CATEGORY_DEFAULT category but explicitly omits CATEGORY_BROWSABLE.
  3. Launch a Chrome Custom Tab to https://attacker.test/ from any third-party app.
  4. Within the CCT, navigate to https://victim-host.example/sensitive_action?command=evil.
  5. Tap the CCT menu or toolbar option to “Open in browser”.
  6. Observe that the non-browsable activity is either presented in the chooser or launched directly, bypassing the CATEGORY_BROWSABLE restriction.

Suggested Fix

Explicitly sanitize the intent or add Intent.CATEGORY_BROWSABLE in CustomTabActivityNavigationController.openCurrentUrlInBrowser() prior to checking resolution and dispatching, mirroring the fix implemented in TabContextMenuItemDelegate:

Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

Evaluated with Chrome root at commit: b5b015ea5f690560237d1f0cff1405844cd12b8d


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