Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in WebAppInstalls
DescriptionInappropriate implementation in WebAppInstalls
ComponentWebAppInstalls
Bug ClassLogic Error
Tracker513743129
Fix commitbd577c2f32d3 (chromium/src) +214/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
modified

Files Changed

  • components/webapps/browser/android/BUILD.gn
  • components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
  • components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtilsTest.java
From bd577c2f32d371594b0d234b157c4b4911e98eb1 Mon Sep 17 00:00:00 2001
From: Dan Murphy <dmurph@chromium.org>
Date: Wed, 20 May 2026 13:58:15 -0700
Subject: [PATCH] [webapps] Restrict shortcut fallback to default launcher

Chrome for Android fell back to sending an implicit broadcast with
action com.android.launcher.action.INSTALL_SHORTCUT when ShortcutManager
was not supported. This implicit broadcast could be intercepted by
malicious apps, leaking the WebApp launch MAC (sensitive HMAC-SHA256)
and URL.

Instead of removing the fallback entirely (which broke add to homescreen
functionality on some devices/distributions), this CL re-implements the
fallback by explicitly targeting the default launcher package.

We query the package manager for the default home activity, and if found
(and it's not the system resolver), we set it as the target package for
the INSTALL_SHORTCUT broadcast.

We also update isAddToHomeIntentSupported to verify if the default
launcher actually has a receiver for this broadcast.

TAG=agy
CONV=baf5d000-8241-49f3-90fd-21c834bccdaa

Bug: 513743129, b:514440631
Change-Id: I76d5df87f2d1e00d8d9ed7cf31dde1a4bd6053e3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7858490
Reviewed-by: Glenn Hartmann <hartmanng@chromium.org>
Auto-Submit: Daniel Murphy <dmurph@chromium.org>
Commit-Queue: Daniel Murphy <dmurph@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1633816}
---

diff --git a/components/webapps/browser/android/BUILD.gn b/components/webapps/browser/android/BUILD.gn
index 64dfdb9d..1ef7676 100644
--- a/components/webapps/browser/android/BUILD.gn
+++ b/components/webapps/browser/android/BUILD.gn
@@ -205,6 +205,7 @@
     "java/src/org/chromium/components/webapps/AddToHomescreenDialogViewTest.java",
     "java/src/org/chromium/components/webapps/AddToHomescreenMediatorTest.java",
     "java/src/org/chromium/components/webapps/AddToHomescreenViewBinderTest.java",
+    "java/src/org/chromium/components/webapps/WebappsUtilsTest.java",
     "java/src/org/chromium/components/webapps/pwa_restore_ui/PwaRestoreBottomSheetContentTest.java",
     "java/src/org/chromium/components/webapps/pwa_restore_ui/PwaRestoreBottomSheetCoordinatorTest.java",
     "java/src/org/chromium/components/webapps/pwa_universal_install/PwaUniversalInstallBottomSheetContentTest.java",
@@ -227,6 +228,7 @@
     "//content/public/android:content_java",
     "//content/public/test/android:content_java_test_support",
     "//third_party/androidx:androidx_core_core_java",
+    "//third_party/androidx:androidx_test_core_java",
     "//third_party/androidx:androidx_test_runner_java",
     "//third_party/jni_zero:jni_zero_java",
     "//third_party/junit",
diff --git a/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java b/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
index af7e1172..d7a64961 100644
--- a/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
+++ b/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
@@ -4,7 +4,6 @@
 
 package org.chromium.components.webapps;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -84,7 +83,13 @@
             return;
         }
 
+        String defaultLauncher = getDefaultLauncherPackageName();
+        if (defaultLauncher == null) {
+            Log.w(TAG, "ShortcutManager is not supported and no default launcher found to target.");
+            return;
+        }
         Intent intent = createAddToHomeIntent(title, icon, shortcutIntent);
+        intent.setPackage(defaultLauncher);
         ContextUtils.getApplicationContext().sendBroadcast(intent);
         showAddedToHomescreenToast(title);
     }
@@ -155,16 +160,37 @@
      *
      * @return if a shortcut can be added to the home screen under the current profile.
      */
-    @SuppressLint("WrongConstant")
     public static boolean isAddToHomeIntentSupported() {
         if (isRequestPinShortcutSupported()) return true;
+
+        String defaultLauncher = getDefaultLauncherPackageName();
+        if (defaultLauncher == null) return false;
+
         PackageManager pm = ContextUtils.getApplicationContext().getPackageManager();
         Intent i = new Intent(INSTALL_SHORTCUT);
-        List<ResolveInfo> receivers =
-                pm.queryBroadcastReceivers(i, PackageManager.GET_INTENT_FILTERS);
+        i.setPackage(defaultLauncher);
+        List<ResolveInfo> receivers = pm.queryBroadcastReceivers(i, 0);
         return !receivers.isEmpty();
     }
 
+    private static @Nullable String getDefaultLauncherPackageName() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_HOME);
+        PackageManager pm = ContextUtils.getApplicationContext().getPackageManager();
+        ResolveInfo resolveInfo = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        if (resolveInfo == null || resolveInfo.activityInfo == null) {
+            return null;
+        }
+        String packageName = resolveInfo.activityInfo.packageName;
+        // If the resolveInfo is the system resolver (e.g., if there are multiple launchers
+        // and the user hasn't selected a default), we treat it as no default launcher.
+        if ("android".equals(packageName)
+                || "com.android.internal.app.ResolverActivity".equals(packageName)) {
+            return null;
+        }
+        return packageName;
+    }
+
     /** Prepares whether Android O's ShortcutManager.requestPinShortcut() is supported. */
     @WorkerThread
     public static void prepareIsRequestPinShortcutSupported() {
diff --git a/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtilsTest.java b/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtilsTest.java
new file mode 100644
index 0000000..e207449
--- /dev/null
+++ b/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtilsTest.java
@@ -0,0 +1,182 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.components.webapps;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.Bitmap;
+import android.os.Build;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.shadows.ShadowShortcutManager;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Feature;
+
+import java.util.List;
+
+/** Tests WebappsUtils. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = Build.VERSION_CODES.Q)
+public class WebappsUtilsTest {
+    private Context mContext;
+    private ShortcutManager mShortcutManager;
+    private ShadowShortcutManager mShadowShortcutManager;
+    private ShadowPackageManager mShadowPackageManager;
+    private ShadowApplication mShadowApplication;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        ContextUtils.initApplicationContextForTests(mContext);
+        mShortcutManager = mContext.getSystemService(ShortcutManager.class);
+        mShadowShortcutManager = shadowOf(mShortcutManager);
+        mShadowPackageManager = shadowOf(mContext.getPackageManager());
+        mShadowApplication = shadowOf((Application) mContext);
+        WebappsUtils.setAddToHomeIntentSupportedForTesting(null); // Reset
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Webapp"})
+    public void testIsAddToHomeIntentSupported_ShortcutManagerSupported() {
+        mShadowShortcutManager.setIsRequestPinShortcutSupported(true);
+        assertTrue(WebappsUtils.isAddToHomeIntentSupported());
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"Webapp"})
+    public void testIsAddToHomeIntentSupported_ShortcutManagerNotSupported() {
+        mShadowShortcutManager.setIsRequestPinShortcutSupported(false);
+        assertFalse(WebappsUtils.isAddToHomeIntentSupported());
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Leak of WebApp Launch MACs via Implicit Broadcast Fallback on Android

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 Android’s fallback mechanism for adding webapp shortcuts uses an implicit broadcast that leaks sensitive authentication MACs to malicious apps. These MACs can be used to spoof trusted webapp launches and bypass intent security checks. This bypass potentially allows an attacker to trigger malicious image decoding within the high-privileged browser process.

Affected files:

  • components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
  • chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
  • chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLauncherActivity.java
  • chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.java
  • chrome/browser/android/shortcut_helper.cc
  • chrome/browser/webapps/webapps_client_android.cc
  • components/webapps/browser/android/add_to_homescreen_installer.cc
  • chrome/browser/android/webapk/webapk_install_service.cc

Estimated timestamp from git blame: Unknown (Google3 checkout)

Potential Security Issue

An investigation into Chrome’s webapp shortcut creation on Android has revealed a potential vulnerability where sensitive authentication secrets (HMAC-SHA256 MACs) can be leaked via an implicit broadcast. This leak occurs when Chrome falls back to a deprecated shortcut installation method on devices where the modern ShortcutManager API is not supported by the default launcher (common on Android Go, Android TV, or certain third-party launchers).

Technical Details

Chrome uses a secret HMAC-SHA256 MAC to verify that intents to launch webapps (via WebappLauncherActivity) originate from a trusted source (the launcher). This MAC is computed using a secret key stored in Chrome’s private data directory.

When adding a shortcut on affected devices, the fallback code in WebappsUtils.addShortcutToHomescreen sends an implicit broadcast with the action com.android.launcher.action.INSTALL_SHORTCUT.

File: components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
Code Path:

80:     public static void addShortcutToHomescreen(
81:             String id, String title, Bitmap icon, boolean isIconAdaptive, Intent shortcutIntent) {
82:         if (isRequestPinShortcutSupported()) {
83:             addShortcutWithShortcutManager(id, title, icon, isIconAdaptive, shortcutIntent);
84:             return;
85:         }
86: 
87:         Intent intent = createAddToHomeIntent(title, icon, shortcutIntent);
88:         ContextUtils.getApplicationContext().sendBroadcast(intent); // Potential leak occurs here
89:         showAddedToHomescreenToast(title);
90:     }

The shortcutIntent passed into this broadcast contains the sensitive EXTRA_MAC extra. Because the broadcast is implicit and lacks any receiver package or permission constraints, any application that registers a receiver for this action at runtime (via Context.registerReceiver()) can intercept the intent and extract the URL and MAC.

Potential Impact

  1. Phishing and UI Spoofing: An attacker possessing a stolen MAC can launch WebappLauncherActivity for the intercepted URL with spoofed intent extras such as a malicious name or icon. This allows the attacker to present a site in a fullscreen standalone window that mimics a trusted application, with no URL bar for the user to verify the origin.
  2. Browser Process RCE: When WebappLauncherActivity receives an intent with a valid MAC, it treats the intent data as trusted. This trust is inherited by WebappActivity, which then decodes the attacker-provided EXTRA_ICON string using BitmapFactory.decodeByteArray directly in the browser process. This bypasses Chrome’s standard security practice of decoding untrusted images in a sandboxed utility process, potentially allowing an attacker to exploit vulnerabilities in OS-level image codecs for Remote Code Execution (RCE) in the browser process.

File: chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappIcon.java
Potential Vulnerable Decoding:

126:     private @Nullable Bitmap generateBitmap() {
127:         if (mEncoded != null && mIsTrusted) {
128:             return BitmapHelper.decodeBitmapFromString(mEncoded);
...

Suggested Potential Steps to Reproduce

  1. Install a malicious app on an Android device that registers a receiver for com.android.launcher.action.INSTALL_SHORTCUT at runtime.
  2. Ensure the device is using a launcher that does not support the Pin Shortcut API.
  3. In Chrome, use the “Add to Home screen” menu option on a website.
  4. Observe the malicious app intercepting the broadcast and extracting the EXTRA_MAC and EXTRA_URL from the intent extras.
  5. Craft a new intent to WebappLauncherActivity with the stolen MAC and URL, but substitute a malicious Base64 icon string in EXTRA_ICON and a spoofed name.
  6. Confirm that Chrome launches the webapp with the spoofed identity and decodes the icon in the browser process.

The INSTALL_SHORTCUT fallback should be removed or made explicit (by targeting the default launcher) on Android 8.0 (O) and above. Additionally, the security model should be revised to ensure that icon data from external intents is always processed in a sandboxed utility process, regardless of whether a valid signature is provided.

Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a


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