CVE-2026-95302
Overview
Background
- WebAPK
- An Android APK that wraps a Progressive Web App and is granted elevated, browser-trusted privileges (such as running without browser UI) once Chrome confirms it is a legitimate, Chrome-minted package.
- `WebApkValidator`
- The client-side class in
components/webapk/android/libs/client/.../WebApkValidator.javathat decides whether an installed package should be treated as a trusted WebAPK, normally by checking its signing key or comment signature. - Signature-based validation
- The intended trust check (
verifyV1WebApk/verifyCommentSignedWebApk) that ties WebAPK trust to a cryptographic key rather than to attacker-controllable metadata like package name or URL. - Allowlist bypass
- A special-case code path that grants trust based on matching a hardcoded identifier instead of performing the cryptographic check.
Root Cause Analysis
WebApkValidator contained a legacy special case, verifyMapsLite, that returned a trusted ValidationResult.MAPS_LITE verdict whenever a package’s name equaled the hardcoded MAPSLITE_PACKAGE_NAME ("com.google.android.apps.mapslite") and its START_URL / SCOPE metadata began with MAPSLITE_URL_PREFIX ("https://www.google.com/maps"). Unlike the verifyV1WebApk and verifyCommentSignedWebApk paths, this check verified no signature, so any package that adopted that package name and metadata prefixes was accepted as a valid WebAPK — violating the invariant that WebAPK trust must derive from a Chrome-controlled signing key. Because the dispatch method returned before reaching the signature-based checks, an attacker-installed package could satisfy the allowlist and receive WebAPK authorization it was never entitled to.
The fix removes verifyMapsLite, the MAPS_LITE enum value, the switch case that handled it, and the associated constants and deprecation string, so every package must now pass a cryptographic verification path.
The fix works because trust decisions are again gated solely on signature checks that attacker-supplied metadata cannot forge.
Attack Path
- Craft a lookalike package
An attacker builds an APK whose package name is set to
com.google.android.apps.mapslite, since the allowlist keyed trust off this exact string. (inferred) - Set matching metadata
The attacker populates the manifest metadata so
START_URLandSCOPEboth begin withMAPSLITE_URL_PREFIX("https://www.google.com/maps"), the only other conditionsverifyMapsLiteenforced. - Install and be validated
Once installed, Chrome’s
WebApkValidatorreachesverifyMapsLite, which returnstrueand short-circuits to theValidationResult.MAPS_LITEverdict before any signature check runs. - Obtain WebAPK authorization The package is treated as a legitimate WebAPK, gaining the elevated, browser-trusted handling normally reserved for Chrome-signed WebAPKs. (inferred)
Impact Assessment
com.google.android.apps.mapslite identity with matching START_URL/SCOPE metadata; the advisory classifies this as incorrect authorization in WebAPKs and states no exploited-in-the-wild status. Any further escalation of the WebAPK privileges into broader capability is not demonstrated by the patch. (inferred)Changed Functions
| Function | Change | Notes |
|---|---|---|
switchcomponents/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java |
modified | |
ifcomponents/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java |
modified |
Files Changed
components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidatorTest.javacomponents/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.javacomponents/browser_ui/strings/android/browser_ui_strings_grd/IDS_WEBAPK_MAPSGO_DEPRECATION_WARNING.png.sha1components/browser_ui/strings/android/browser_ui_strings.grd
Audit Directions
- Non-cryptographic allowlistsSearch validation and authorization code for trust decisions keyed on hardcoded package names, URLs, or metadata prefixes rather than signatures, especially early-return special cases that precede the real check.
- Legacy/deprecated special casesReview remaining WebAPK and app-trust paths for other deprecated exceptions (like the removed Maps Lite case) that may still grant privileges without full verification.
- Verdict dispatch orderingAudit switch/dispatch methods that return a trusted result before invoking
verifyV1WebApk/verifyCommentSignedWebApk, ensuring no branch short-circuits the signature-based validation.
Patch
From 9f55f22780c010abc558718a6528957ad907c034 Mon Sep 17 00:00:00 2001 From: Yaron Friedman <[email protected]> Date: Thu, 20 Aug 2026 17:18:18 -0700 Subject: [PATCH] Remove Maps Lite allowlist from WebApkValidator Remove the deprecated Maps Lite allowlist and associated deprecation warning string and unit tests. Fixed: 502242455 TAG=agy CONV=3a08567f-d60b-44f5-a7dc-378d70a721f1 Change-Id: If328144efd28dbe8053beec0a872f0437bf9174a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8269601 Reviewed-by: Daniel Murphy <[email protected]> Commit-Queue: Yaron Friedman <[email protected]> Cr-Commit-Position: refs/heads/main@{#1683570} --- diff --git a/components/browser_ui/strings/android/browser_ui_strings.grd b/components/browser_ui/strings/android/browser_ui_strings.grd index 9f789aa0..ec8bcce 100644 --- a/components/browser_ui/strings/android/browser_ui_strings.grd +++ b/components/browser_ui/strings/android/browser_ui_strings.grd @@ -929,9 +929,6 @@ </message> <!-- WebApk outofdate warning string --> - <message name="IDS_WEBAPK_MAPSGO_DEPRECATION_WARNING" desc="The toast message when user trying to launch the deprecated Maps Go WebAPK."> - '<ph name="APP_NAME">%1$s<ex>Maps Go</ex></ph>' is out of date. Please update the app. - </message> <message name="IDS_WEBAPK_DEPRECATION_WARNING" desc="The toast message when user trying to launch the deprecated WebAPKs."> '<ph name="APP_NAME">%1$s<ex>Maps Go</ex></ph>' is out of date. Please re-install the app. </message> diff --git a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_WEBAPK_MAPSGO_DEPRECATION_WARNING.png.sha1 b/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_WEBAPK_MAPSGO_DEPRECATION_WARNING.png.sha1 deleted file mode 100644 index 3ae1237..0000000 --- a/components/browser_ui/strings/android/browser_ui_strings_grd/IDS_WEBAPK_MAPSGO_DEPRECATION_WARNING.png.sha1 +++ /dev/null @@ -1 +0,0 @@ -a6da12e61f0b81f118d2cb0747b852228aeafc73 \ No newline at end of file diff --git a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java index 2b50f4e3..3993884 100644 --- a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java +++ b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java @@ -6,7 +6,6 @@ import static org.chromium.build.NullUtil.assumeNonNull; import static org.chromium.components.webapk.lib.common.WebApkConstants.WEBAPK_PACKAGE_PREFIX; -import static org.chromium.components.webapk.lib.common.WebApkMetaDataKeys.SCOPE; import static org.chromium.components.webapk.lib.common.WebApkMetaDataKeys.START_URL; import static org.chromium.components.webapk.lib.common.WebApkMetaDataKeys.WEB_MANIFEST_URL; @@ -55,9 +54,6 @@ public class WebApkValidator { private static final String TAG = "WebApkValidator"; private static final String KEY_FACTORY = "EC"; // aka "ECDSA" - private static final String MAPSLITE_PACKAGE_NAME = "com.google.android.apps.mapslite"; - private static final String MAPSLITE_URL_PREFIX = - "https://www.google.com/maps"; // Matches scope. private static final boolean DEBUG = false; private static byte @Nullable [] sExpectedSignature; @@ -68,14 +64,12 @@ @IntDef({ ValidationResult.FAILURE, ValidationResult.V1_WEB_APK, - ValidationResult.MAPS_LITE, ValidationResult.COMMENT_SIGNED }) @Retention(RetentionPolicy.SOURCE) private @interface ValidationResult { int FAILURE = 0; int V1_WEB_APK = 1; - int MAPS_LITE = 2; int COMMENT_SIGNED = 3; } @@ -184,11 +178,6 @@ switch (result) { case ValidationResult.FAILURE: continue; - case ValidationResult.MAPS_LITE: - String name = info.loadLabel(context.getPackageManager()).toString(); - showDeprecationWarning( - context, name, R.string.webapk_mapsgo_deprecation_warning); - return false; case ValidationResult.V1_WEB_APK: int shellApkVersion = IntentUtils.safeGetInt( @@ -336,13 +325,6 @@ if (verifyV1WebApk(packageInfo, webappPackageName)) { return ValidationResult.V1_WEB_APK; } - if (verifyMapsLite(packageInfo, webappPackageName)) { - if (DEBUG) { - Log.d(TAG, "Matches Maps Lite"); - } - - return ValidationResult.MAPS_LITE; - } if (verifyCommentSignedWebApk(packageInfo)) { return ValidationResult.COMMENT_SIGNED; } @@ -428,27 +410,6 @@ return false; } - private static boolean verifyMapsLite(PackageInfo packageInfo, String webappPackageName) { - if (hasSplits(packageInfo) || !webappPackageName.equals(MAPSLITE_PACKAGE_NAME)) { - return false; - } - String startUrl = packageInfo.applicationInfo.metaData.getString(START_URL); - if (startUrl == null || !startUrl.startsWith(MAPSLITE_URL_PREFIX)) { - if (DEBUG) { - Log.d(TAG, "mapslite invalid startUrl prefix"); - } - return false; - } - String scope = packageInfo.applicationInfo.metaData.getString(SCOPE); - if (scope == null || !scope.equals(MAPSLITE_URL_PREFIX)) { - if (DEBUG) { - Log.d(TAG, "mapslite invalid scope prefix"); - } - return false; - } - return true; - } - /** Verify that the comment signed webapk matches the public key. */ private static boolean verifyCommentSignedWebApk(PackageInfo packageInfo) { if (hasSplits(packageInfo)) { diff --git a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidatorTest.java b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidatorTest.java index 075259c..eb82c5b 100644 --- a/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidatorTest.java +++ b/components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidatorTest.java @@ -51,8 +51,6 @@ private static final String URL_WITHOUT_WEBAPK = "https://www.other.com"; private static final String TEST_DATA_DIR = "webapks/"; private static final String TEST_STARTURL = "https://non-empty.com/starturl"; - private static final String MAPSLITE_PACKAGE_NAME = "com.google.android.apps.mapslite"; - private static final String MAPSLITE_EXAMPLE_STARTURL = "https://www.google.com/maps"; private static final String MANIFEST_URL = "https://www.foo.com/manifest.json"; private static final int SHELL_VERSION = 100; @@ -322,69 +320,6 @@ } /** - * Tests {@link WebApkValidator.isValidWebApk} returns true if the package name is maps lite and - * the start url matches the correct prefix. - */ - @Test - public void testIsValidWebApkForMapsLite() { - mPackageManager.addPackage( - newPackageInfoWithBrowserSignature( - MAPSLITE_PACKAGE_NAME, - new Signature(SIGNATURE_1), - MAPSLITE_EXAMPLE_STARTURL, - null)); - mPackageManager.addPackage( - newPackageInfoWithBrowserSignature( - MAPSLITE_PACKAGE_NAME + ".other", - new Signature(SIGNATURE_1), - MAPSLITE_EXAMPLE_STARTURL, - null)); - - assertTrue( - WebApkValidator.isValidWebApk( - RuntimeEnvironment.application, MAPSLITE_PACKAGE_NAME)); - assertFalse( - WebApkValidator.isValidWebApk( - RuntimeEnvironment.application, MAPSLITE_PACKAGE_NAME + ".other")); - assertFalse( - WebApkValidator.isValidWebApk( - RuntimeEnvironment.application, MAPSLITE_PACKAGE_NAME + ".notfound")); - } - - /** Tests {@link WebApkValidator.canWebApkHandleUrl} returns false and shows a toast. */ - @Test - public void testMapsLiteWebApkShowsWarning() { - // Invalid MapsLite WebAPK is not verified and does not show toast. - addWebApkResolveInfoWithPackageName( - MAPSLITE_EXAMPLE_STARTURL, MAPSLITE_PACKAGE_NAME + ".other", SIGNATURE_1); - assertFalse( - WebApkValidator.canWebApkHandleUrl( - RuntimeEnvironment.application, - MAPSLITE_PACKAGE_NAME + ".other", - MAPSLITE_EXAMPLE_STARTURL, - 0)); - assertNull(ShadowToast.getLatestToast()); - - // Valid MapsLite WebAPK returns false as "not handled" and shows a toast. - addWebApkResolveInfoWithPackageName( - MAPSLITE_EXAMPLE_STARTURL, MAPSLITE_PACKAGE_NAME, SIGNATURE_1); - assertFalse( - WebApkValidator.canWebApkHandleUrl( - RuntimeEnvironment.application, - MAPSLITE_PACKAGE_NAME, - MAPSLITE_EXAMPLE_STARTURL, - 0)); - assertNotNull(ShadowToast.getLatestToast()); - // assertTextFromLatestToast(R.string.copied); - TextView textView = (TextView) ShadowToast.getLatestToast().getView(); - String actualText = textView == null ? "" : textView.getText().toString(); - assertEquals( - ContextUtils.getApplicationContext() - .getString(R.string.webapk_mapsgo_deprecation_warning, ""), - actualText); - } - - /** * Tests {@link WebApkValidator.canWebApkHandleUrl} returns false and shows a toast when the * shell version is out-of-date (older than the min_version). */ @@ -420,19 +355,6 @@ } /** - * Tests {@link WebApkValidator.isValidWebApk} returns false when the startUrl is not correct. - */ - @Test - public void testIsNotValidWebApkForMapsLiteBadStartUrl() { - mPackageManager.addPackage( - newPackageInfoWithBrowserSignature( - MAPSLITE_PACKAGE_NAME, new Signature(SIGNATURE_1), TEST_STARTURL, null)); - assertFalse( - WebApkValidator.isValidWebApk( - RuntimeEnvironment.application, MAPSLITE_PACKAGE_NAME)); - } - - /** * Tests {@link WebApkValidator.isValidWebApk} returns false if a WebAPK has more than 2 * signatures, even if the second one matches the expected signature. */ @@ -562,28 +484,9 @@ RuntimeEnvironment.application, INVALID_WEBAPK_PACKAGE_NAME)); } - /** Tests {@link WebApkValidator.isValidV1WebApk} returns false if the package name is maps lite. */ - @Test - public void testIsValidV1WebApkFalseForMapsLite() { - mPackageManager.addPackage( - newPackageInfoWithBrowserSignature( - MAPSLITE_PACKAGE_NAME, - new Signature(SIGNATURE_1), - MAPSLITE_EXAMPLE_STARTURL, - null)); - mPackageManager.addPackage( - newPackageInfoWithBrowserSignature( - MAPSLITE_PACKAGE_NAME + ".other", - new Signature(SIGNATURE_1), - MAPSLITE_EXAMPLE_STARTURL, - null)); - - assertFalse( - WebApkValidator.isValidV1WebApk( - RuntimeEnvironment.application, MAPSLITE_PACKAGE_NAME)); - } - - /** Tests {@link WebApkValidator#queryBoundWebApkForManifestUrl()} for a valid installed entry. */ + /** + * Tests {@link WebApkValidator#queryBoundWebApkForManifestUrl()} for a valid installed entry. + */ @Test public void testQueryBoundWebApkForManifestUrl() { mPackageManager.addPackage(
Original Bug Report
Potential cross-origin notification content leak via spoofed Maps Lite WebAPK
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: Chrome for Android may incorrectly route web push notifications to a malicious sideloaded application spoofing the deprecated Maps Lite WebAPK. This occurs due to a lack of signature verification and scope validation in WebApkValidator, potentially allowing the malicious app to intercept sensitive notification data from arbitrary origins.
Affected files:
components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.javachrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.javachrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkServiceClient.javachrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/WebApkIdentityServiceClient.javachrome/android/java/src/org/chromium/chrome/browser/webapps/ChromeWebApkHost.java
Estimated timestamp from git blame: 2024-02-21
Description
There is a potential vulnerability in how Chrome for Android validates WebAPKs before delegating push notifications. The WebApkValidator contains specific logic to identify the Maps Lite WebAPK (com.google.android.apps.mapslite). However, the verifyMapsLite method only checks the package name and internal metadata, entirely omitting cryptographic signature verification.
Furthermore, when querying for a WebAPK to handle a specific notification scope URL, Chrome relies on the Android PackageManager’s intent resolution. A malicious app can declare intent filters for arbitrary origins while spoofing the Maps Lite package name and metadata. Because verifyMapsLite assumes the app is valid based on metadata alone, and doesn’t cross-check the queried URL against the verified scope, Chrome trusts the app as a legitimate WebAPK for the target origin.
The attacker can further bypass runtime host checks by implementing IIdentityService to return Chrome’s package name. Consequently, Chrome will serialize the android.app.Notification object—containing sensitive cross-origin data like 2FA codes or messages—and pass it directly to the attacker’s IWebApkApi service.
Potential Attack Scenario (Unverified)
These are suggested steps an attacker might follow to exploit this issue:
- Create a Malicious App: The attacker creates an Android app with the package name
com.google.android.apps.mapslite. - Spoof Metadata: In the
AndroidManifest.xml, the attacker adds<meta-data>forSTART_URLandSCOPE, setting them tohttps://www.google.com/mapsto satisfyverifyMapsLite. - Target Intent Filters: The attacker adds an
<intent-filter>forandroid.intent.action.VIEWandCATEGORY_BROWSABLEtargeting the victim’s origin (e.g.,https://mail.example.com). - Implement Spoofed Services: The attacker implements
IIdentityServiceto returncom.android.chromeandIWebApkApito receive the incoming notifications. - Installation: The attacker convinces the victim to sideload this APK (this requires the legitimate Google Maps Go app to be absent from the device).
- Triggering: The victim visits
https://mail.example.comin Chrome and allows notifications. When a push notification arrives, Chrome delegates it to the malicious app viaWebApkServiceClient.notifyNotificationWithChannel, exposing the contents.
Code References
components/webapk/android/libs/client/src/org/chromium/components/webapk/lib/client/WebApkValidator.java:verifyMapsLite()checks package name and metadata but lacks signature verification.queryFirstWebApkPackage()relies onPackageManagerto find apps claiming to handle the URL, allowing the attacker’s arbitrary intent filter to be selected.
chrome/android/webapk/libs/client/src/org/chromium/webapk/lib/client/WebApkIdentityServiceClient.java:checkBrowserBacksWebApkAsync()trusts the unauthenticatedIIdentityServiceof the WebAPK to determine the host browser.
chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridge.java:displayNotificationInternal()delegates the notification based on the flawed validation.
Suggested Fix
- Enforce Signature Verification: Add strict cryptographic signature verification to
verifyMapsLite(), ensuring the package is genuinely signed by Google, similar to how V1 WebAPKs are verified. Alternatively, since Maps Lite is deprecated (as seen incanWebApkHandleUrl), consider removing support for it entirely. - Strict Scope Validation: Ensure that when a WebAPK is selected via
queryFirstWebApkPackage, the actual queried URL is strictly validated against the WebAPK’s authenticatedSCOPE, rather than relying solely on the presence of an intent filter.
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.