Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Navigation
DescriptionInsufficient validation of untrusted input in Navigation
ComponentNavigation
Bug ClassLogic Error
Tracker502173136
Fix commit05bf5a2b510c (chromium/src) +0/-63
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroidImpl.java
modified
for
chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroidImpl.java
modified

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroidImpl.java
  • chrome/browser/android/tab_web_contents_delegate_android.h
  • chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
From 05bf5a2b510c9ba1e4463d5a240dd08fbfe3baa9 Mon Sep 17 00:00:00 2001
From: Fabio Muratori <muratori@google.com>
Date: Fri, 17 Apr 2026 12:11:03 -0700
Subject: [PATCH] Removed unused methods TabWebContentsDelegateAndroid#openInAppOrChromeFromCct

Method was only used by previous CL to force open a new tab from CCT
when middle click navigation is processed.

Handling this logic here is very problematic because we are skipping all
the policies enforced by ExternalNavigationHandler.

This is the root cause of security vulnerability "Potential silent
external app launch from CCT via OpenURL with NEW_BACKGROUND_TAB".

To prevent further mis-use we should directly remove the method in
TabWebContentsDelegateAndroid.

Bug: 502173136

Remove stale handling of middle click navigations

This is an old attempt at modifyling the middle click navigation flow
and outcome.

Flag was never progressed and this slice is basically deadcode.

On top of this, it has been reported by AI tools that the flag could
enable a vulnerability "Potential silent external app launch from CCT
via OpenURL with NEW_BACKGROUND_TAB"

Bug: 502173136
Change-Id: Ibc931608281e768d93b1654e26d7314a6d0357d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7770415
Commit-Queue: Fabio Muratori <muratori@google.com>
Reviewed-by: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1616749}
---

diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroidImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroidImpl.java
index f330da6..27c14cf2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroidImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroidImpl.java
@@ -4,18 +4,12 @@
 
 package org.chromium.chrome.browser.tab;
 
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
-import android.net.Uri;
 import android.os.Handler;
 import android.view.KeyEvent;
 
@@ -28,7 +22,6 @@
 import org.chromium.base.Callback;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.ObserverList.RewindableIterator;
-import org.chromium.base.PackageManagerUtils;
 import org.chromium.base.lifetime.Destroyable;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
@@ -38,10 +31,8 @@
 import org.chromium.chrome.browser.app.serial.SerialNotificationService;
 import org.chromium.chrome.browser.app.usb.UsbNotificationService;
 import org.chromium.chrome.browser.bluetooth.BluetoothNotificationManager;
-import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.gesturenav.NativePageBitmapCapturer;
 import org.chromium.chrome.browser.media.MediaCaptureNotificationServiceImpl;
-import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.policy.PolicyAuditor;
 import org.chromium.chrome.browser.policy.PolicyAuditorJni;
 import org.chromium.chrome.browser.serial.SerialNotificationManager;
@@ -188,55 +179,6 @@
         mDelegate.setContentsBounds(source, bounds);
     }
 
-    @CalledByNative
-    @Override
-    protected boolean openInAppOrChromeFromCct(GURL gurl) {
-        Intent intent =
-                new Intent(Intent.ACTION_VIEW, Uri.parse(gurl.getSpec()))
-                        .addCategory(Intent.CATEGORY_BROWSABLE);
-
-        ResolveInfo defaultActivity =
-                PackageManagerUtils.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
-
-        if (defaultActivity != null) {
-            // Check if the default activity is a chooser
-            List<ResolveInfo> handlers =
-                    PackageManagerUtils.queryIntentActivities(
-                            intent, PackageManager.GET_RESOLVED_FILTER);
-            for (ResolveInfo handler : handlers) {
-                String packageName = handler.activityInfo.packageName;
-                String activityName = handler.activityInfo.name;
-                if (packageName.equals(defaultActivity.activityInfo.packageName)
-                        && activityName.equals(defaultActivity.activityInfo.name)) {
-                    intent.setClassName(packageName, activityName);
-                    break;
-                }
-            }
-        }
-
-        // Fallback to Chrome if no supporting app was found
-        if (intent.getComponent() == null) {
-            intent.setClass(ContextUtils.getApplicationContext(), ChromeLauncherActivity.class);
-        }
-
-        Context context = mTab.getContext();
-
-        int flags = Intent.FLAG_ACTIVITY_NEW_TASK;
-        // If we're in in multi window it's fine to open multiple instances
-        if (context instanceof Activity
-                && MultiWindowUtils.getInstance().isInMultiWindowMode((Activity) context)) {
-            flags |= Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-        }
-
-        intent.setFlags(flags);
-        try {
-            context.startActivity(intent);
-            return true;
-        } catch (RuntimeException e) {
-            return false;
-        }
-    }
-
     // WebContentsDelegateAndroid
 
     @Override
diff --git a/chrome/browser/android/tab_web_contents_delegate_android.h b/chrome/browser/android/tab_web_contents_delegate_android.h
index 3b66b56..5c4658e 100644
--- a/chrome/browser/android/tab_web_contents_delegate_android.h
+++ b/chrome/browser/android/tab_web_contents_delegate_android.h
@@ -146,7 +146,6 @@
   bool IsInstalledWebappDelegateGeolocation() const;
   bool IsModalContextMenu() const;
   bool IsDynamicSafeAreaInsetsEnabled() const;
-  bool OpenInAppOrChromeFromCct(GURL url);
 
   void DraggableRegionsChanged(
       const std::vector<blink::mojom::DraggableRegionPtr>& regions,
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
index 5593e452c..d26b473 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
@@ -134,10 +134,6 @@
         return false;
     }
 
-    protected boolean openInAppOrChromeFromCct(GURL gurl) {
-        return false;
-    }
-
     /** Called when WebContents reports a change to the non-draggable regions in header content. */
     protected void nonDraggableRegionsChanged(List<Rect> regions) {}
 
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential silent external app launch from CCT via OpenURL with NEW_BACKGROUND_TAB

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 without the Chrome Security team.

Overview: When the kNavigationCaptureRefactorAndroid feature is enabled, a compromised renderer in a Chrome Custom Tab can trigger silent external app launches. This occurs by requesting a navigation with the NEW_BACKGROUND_TAB disposition, which bypasses the popup blocker and standard external navigation security checks.

Affected files:

  • chrome/browser/android/tab_web_contents_delegate_android.cc
  • chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroidImpl.java

Estimated timestamp from git blame: 2025-05-07

Background

When external_intents::kNavigationCaptureRefactorAndroid is enabled, a logic flaw in TabWebContentsDelegateAndroid::OpenURLFromTab allows for unauthorized external application launches from Chrome Custom Tabs (CCT).

In chrome/browser/android/tab_web_contents_delegate_android.cc, OpenURLFromTab contains a short-circuit branch for CCTs requesting a NEW_BACKGROUND_TAB disposition:

  if (base::FeatureList::IsEnabled(
          external_intents::kNavigationCaptureRefactorAndroid)) {
    if (IsCustomTab() &&
        disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB) {
      if (OpenInAppOrChromeFromCct(params.url)) {
        // Navigation handled, stop here. Otherwise proceed normally.
        return nullptr;
      }
    }
  }

Vulnerability Details

This early return completely bypasses the standard popup blocking logic (blocked_content::ConsiderForPopupBlocking) and the subsequent routing to TabModelList::HandlePopupNavigation. As a result, the navigation never reaches ExternalNavigationHandler (ENH).

The Java sink, TabWebContentsDelegateAndroidImpl.openInAppOrChromeFromCct, directly constructs an ACTION_VIEW intent with CATEGORY_BROWSABLE and calls context.startActivity(intent).

Because ENH is bypassed, several critical security safeguards are missing:

  1. Popup Blocker Bypass: The app launch occurs immediately, without requiring a user gesture or displaying a popup warning.
  2. Incognito Bypass: If the CCT is in Incognito mode, the standard “Leave Incognito?” warning dialog is skipped, breaking the Incognito security boundary.
  3. Intent Sanitization: Standard intent sanitization (e.g., stripping dangerous extras) performed by ENH is omitted.

Chromium’s IPC validation in content/browser/renderer_host/ipc_utils.cc (IsValidRendererDisposition) explicitly permits the NEW_BACKGROUND_TAB disposition from renderers. Therefore, a compromised renderer can spoof this disposition to trigger the vulnerability.

Potential Attack Steps

Note: These steps are based on static analysis; a working proof-of-concept has not been executed.

  1. Ensure the flag chrome://flags/#navigation-capture-refactor-android is enabled on an Android device.
  2. The user opens a Chrome Custom Tab (e.g., via a host application).
  3. An attacker compromises the Chrome renderer process associated with the CCT.
  4. From the compromised renderer, the attacker sends a blink::mojom::FrameHost::OpenURL Mojo IPC message to the Browser process with:
    • url: A target external application URI (e.g., tel:+15551234567, market://details?id=com.malicious.app, or a verified App Link).
    • disposition: WindowOpenDisposition::NEW_BACKGROUND_TAB.
    • user_gesture: false (or true).
  5. The browser process validates the IPC, accepts the disposition, and routes the request to TabWebContentsDelegateAndroid::OpenURLFromTab.
  6. The CCT check passes, and OpenInAppOrChromeFromCct launches the target app silently without any user interaction or security prompts.

Suggested Fix

Refactor TabWebContentsDelegateAndroid::OpenURLFromTab to ensure that even when kNavigationCaptureRefactorAndroid is enabled, background tab navigations from CCTs are subjected to blocked_content::ConsiderForPopupBlocking (to verify user gestures) and ideally routed through ExternalNavigationHandler to enforce Incognito checks and intent sanitization before calling startActivity.

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


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.

View on issue tracker