Low watch CVSS 4.3 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect reference resolution in CustomTabs
DescriptionIncorrect reference resolution in CustomTabs
ComponentCustomTabs
Bug ClassLogic 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.
Tracker534556413
Fix commit2ba952e0154b (chromium/src) +10/-2
CWECWE-706
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-07-14 (per vendor advisory)
Fix landed2026-07-16 (public git)
Disclosed2026-08-25
Patch gap40 days — fix public in source before the advisory

Background

Custom Tabs (CCT)
an Android surface that lets an external app host a Chrome-rendered tab in its own task, exposing an “Open in browser” action that hands the current URL to a full browser.
Implicit `Intent` resolution
Android matches an implicit Intent against installed activities’ intent filters, and an activity matches only if it declares every category the Intent carries.
`Intent.CATEGORY_BROWSABLE`
the category a component must declare to be considered a safe target for web URLs launched from untrusted contexts; browsers declare it, most non-browser handlers do not.
`FLAG_ACTIVITY_NEW_TASK` / `EXTRA_FROM_OPEN_IN_BROWSER`
the flag and extra set on the “Open in browser” Intent so it launches in a new task and is recognized downstream as originating from this action.

Root Cause Analysis

When a Custom Tab user chose “Open in browser,” the code built an implicit Intent(Intent.ACTION_VIEW, Uri.parse(url)) and dispatched it (via detachAndStartReparenting or startActivity) without adding Intent.CATEGORY_BROWSABLE. Because the Intent carried only ACTION_VIEW plus a URL, Android’s resolution could match components that register for the scheme but are not browsable-safe web handlers, so the URL reference could be resolved to an unintended activity rather than an actual browser. This is the “incorrect reference resolution” logic error: the invariant that a web URL surfaced from an untrusted CCT context should only be handed to browsable (browser-grade) targets was not enforced.

The fix adds intent.addCategory(Intent.CATEGORY_BROWSABLE), so resolution is constrained to activities whose intent filters declare CATEGORY_BROWSABLE, matching the categorization the OS uses for normal web-link handling. The accompanying tests capture the dispatched Intent and assert intent.hasCategory(Intent.CATEGORY_BROWSABLE) on both the reparenting and startActivity paths.

Key insight
The core mistake was launching an ACTION_VIEW URL Intent from the CCT “Open in browser” entry point without the CATEGORY_BROWSABLE category, letting non-browsable components become eligible resolution targets; the fix adds that category so the Intent is only resolvable by proper browser handlers.

Attack Path

  1. Host a Custom Tab A malicious or complicit app launches a Custom Tab pointing at a URL under attacker influence and the user reaches the “Open in browser” action.
  2. Trigger the flawed dispatch Invoking openCurrentUrlInBrowser() builds Intent(ACTION_VIEW, url) with FLAG_ACTIVITY_NEW_TASK and EXTRA_FROM_OPEN_IN_BROWSER but no CATEGORY_BROWSABLE, then dispatches it.
  3. Capture via non-browsable filter An installed component that registers for the URL’s scheme without declaring CATEGORY_BROWSABLE can be matched during resolution, so the URL reference is resolved to that component instead of a browser (inferred).
  4. Consequence The resolved component receives the URL (and the EXTRA_FROM_OPEN_IN_BROWSER trust marker), diverging from the intended “open in a real browser” behavior (inferred).

Impact Assessment

An attacker can cause a URL that the user expects to be handed to a browser to instead be resolved to an unintended, non-browsable component, on Android, from the Custom Tabs “Open in browser” flow. The precondition is that a component registering for the target scheme without CATEGORY_BROWSABLE is present and reachable; the advisory classifies this as a logic error (incorrect reference resolution in CustomTabs) and does not report in-the-wild exploitation. Any escalation beyond misdirected intent resolution is (inferred).

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

Audit Directions

  • Implicit `ACTION_VIEW` URL intents
    Audit every site where Chrome constructs an implicit ACTION_VIEW Intent from a web URL and dispatches it, checking that CATEGORY_BROWSABLE is set so non-browsable components cannot intercept.
  • Entry-point parity
    When one entry point (e.g., an earlier CL like crrev.com/c/8076861) is hardened, grep for sibling entry points building the same kind of Intent (here CustomTabActivityNavigationController) that were missed.
  • Trust-marker extras on under-constrained intents
    Flag intents carrying trust markers such as EXTRA_FROM_OPEN_IN_BROWSER whose target set is not tightly restricted, since a mis-resolved target inherits that implied trust.
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