Medium watch CVSS 5.4 chrome Spoofing 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
5.4
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in CustomTabs
DescriptionUI misrepresentation in CustomTabs
ComponentCustomTabs
Sub-componentAndroid
Bug ClassSpoofing
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.
Tracker517719358
Fix commitd125c0dee27c (chromium/src) +142/-0
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:L
CISA KEVNot listed
CreditedGoogle
Reported2026-05-29 (per vendor advisory)
Fix landed2026-07-13 (public git)
Disclosed2026-08-25
Patch gap43 days — fix public in source before the advisory

Background

Custom Tabs (CCT) theming
CCT lets the launching app pick a toolbar color, which Chrome applies as the toolbar primary color only while Tab#isThemingAllowed() returns true.
`isThemingAllowed()`
a per-Tab predicate that disallows custom theming when the connection state should be shown neutrally (e.g. degraded/insecure SSL state), forcing the default toolbar color.
`TabObserver` SSL callback
onSSLStateUpdated(Tab) fires when a tab’s visible security/SSL state changes without a full navigation, whereas onDidChangeThemeColor only fires when the page’s own theme color changes.
`BrandedColorScheme` / `getThemeColor`
the toolbar’s derived color scheme and color value that the theme providers recompute in updateTheme()/updateColor().

Root Cause Analysis

BrowserServicesThemeColorProvider and TopUiThemeColorProvider derive the toolbar color from the activity/custom theme color only while isThemingAllowed() is true, and they recompute it from their TabObserver callbacks. Neither observer implemented onSSLStateUpdated, so when a tab’s visible SSL state changed without a navigation and the tab’s own theme color was already UNSPECIFIED (meaning onDidChangeThemeColor never fired), no callback recomputed the color. As a result the previously applied activity theme color and BrandedColorScheme were left in place even after isThemingAllowed() had become false, so the toolbar continued displaying the app-supplied brand color while other connection-state UI updated.

The fix adds onSSLStateUpdated overrides to both observers that call updateTheme() / updateColor(tab, tab.getThemeColor(), false), forcing the toolbar color and color scheme to be recomputed alongside the rest of the connection-state UI.

The added tests confirm the toolbar falls back to ChromeColors.getDefaultThemeColor(...) and BrandedColorScheme.APP_DEFAULT once theming is no longer allowed.

Key insight
The theme providers only recomputed on navigation or page-theme-color changes, missing SSL-state transitions; adding onSSLStateUpdated handlers makes the toolbar color track isThemingAllowed() at the moment the security state changes.

Attack Path

  1. Launch a themed Custom Tab A malicious or attacker-influenced app opens a CCT with a chosen toolbar (brand) color that is applied while theming is allowed.
  2. Load a page with no theme color The page leaves its own theme color UNSPECIFIED, so the toolbar color is sourced from the activity/custom color and onDidChangeThemeColor will not fire on later state changes.
  3. Change SSL state without navigating The visible security state degrades (e.g. an in-page connection-state transition), which fires onSSLStateUpdated but, pre-patch, triggers no recomputation.
  4. Stale trusted color persists The toolbar keeps the brand color and BrandedColorScheme even though isThemingAllowed() is now false, misrepresenting the connection’s security to the user (UI spoofing).

Impact Assessment

An attacker controlling the CCT-launching app (and page content) can keep a custom-branded toolbar color displayed after the connection state should have forced a neutral/default appearance, spoofing the browser’s connection-state UI in the Android browser UI. The precondition is a CCT tab whose own theme color is unspecified and whose SSL state changes without a navigation. The vendor advisory rates this medium severity and does not report in-the-wild exploitation; NVD scores it CVSS 5.4.

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
modified

Files Changed

  • chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
  • chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProviderUnitTest.java
  • chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProviderUnitTest.java
  • chrome/browser/ui/android/theme/BUILD.gn
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.java

Audit Directions

  • Missing observer callbacks
    Audit other TabObserver implementations that drive security-sensitive UI (toolbar, omnibox, security chip) for missing onSSLStateUpdated handling where state can change without a navigation or theme-color change.
  • Recompute triggered by wrong signal
    Look for UI derived from a predicate like isThemingAllowed() that is only refreshed on a proxy event (navigation, onDidChangeThemeColor) rather than on every input to that predicate.
  • `UNSPECIFIED`/default-value shortcuts
    Review paths where an “unspecified” page value suppresses an update callback, leaving previously computed trusted UI state stale.
From d125c0dee27c0641133c990eb1ae3865564f5eac Mon Sep 17 00:00:00 2001
From: Mohamed Adel <[email protected]>
Date: Mon, 13 Jul 2026 14:20:04 -0700
Subject: [PATCH] [Android] Recompute toolbar theme on SSL state changes

BrowserServicesThemeColorProvider and TopUiThemeColorProvider derive the
toolbar primary color from the activity theme color when
Tab#isThemingAllowed() is true. Neither provider's tab observer reacted
to onSSLStateUpdated, so when the visible SSL state changed without a
navigation and the tab's own theme color was already UNSPECIFIED (so
onDidChangeThemeColor did not fire), the previous activity theme color
and BrandedColorScheme were left in place even though theming was no
longer allowed.

Add onSSLStateUpdated overrides to both tab observers so the toolbar
color and color scheme are recomputed alongside the other
connection-state UI when the SSL state changes.

TAG=agy

Bug: 517719358
Change-Id: Ia84ac465aaee46e36a8d7e64b14e094a78af498f
Fixed: 517719358
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8087875
Commit-Queue: Moe Adel <[email protected]>
Reviewed-by: Jinsuk Kim <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1661375}
---
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.java
index 41b2200..a402dd6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.java
@@ -109,6 +109,11 @@
                 }
 
                 @Override
+                public void onSSLStateUpdated(Tab tab) {
+                    updateTheme();
+                }
+
+                @Override
                 public void onDidChangeThemeColor(Tab tab, int color) {
                     updateTheme();
                 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProviderUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProviderUnitTest.java
index 8a5095e2..71de7b0f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProviderUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProviderUnitTest.java
@@ -340,6 +340,39 @@
     }
 
     @Test
+    public void testSSLStateUpdateRecomputesTheme() {
+        when(mCustomTabActivityTabProvider.get()).thenReturn(tab);
+        when(mToolbarThemeColorProvider.getToolbarBackgroundColor(eq(tab))).thenReturn(LIGHT_COLOR);
+        var intentDataProvider =
+                buildCctIntentDataProvider(
+                        COLOR_SCHEME_LIGHT,
+                        /* schemeParams= */ null,
+                        /* isOpenedByChrome= */ false,
+                        /* isIncognito= */ false);
+        var themeColorProvider = createThemeColorProvider(intentDataProvider);
+        themeColorProvider.setUseTabTheme(true);
+
+        assertEquals(
+                "Should use the page theme color before the SSL state changes",
+                LIGHT_COLOR,
+                themeColorProvider.getThemeColor());
+
+        int defaultColor = ChromeColors.getDefaultThemeColor(mContext, false);
+        when(mToolbarThemeColorProvider.getToolbarBackgroundColor(eq(tab)))
+                .thenReturn(defaultColor);
+        themeColorProvider.getTabObserver().onSSLStateUpdated(tab);
+
+        assertEquals(
+                "Should refresh the theme color when the SSL state changes",
+                defaultColor,
+                themeColorProvider.getThemeColor());
+        assertEquals(
+                "Should refresh the color scheme when the SSL state changes",
+                BrandedColorScheme.APP_DEFAULT,
+                themeColorProvider.getBrandedColorScheme());
+    }
+
+    @Test
     public void testIncognitoTheme() {
         // emulate incognito tab with chrome default theme
         var intentDataProvider =
diff --git a/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
index 4bf998a5..5c51e27 100644
--- a/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
+++ b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
@@ -79,6 +79,11 @@
                             }
 
                             @Override
+                            public void onSSLStateUpdated(Tab tab) {
+                                updateColor(tab, tab.getThemeColor(), false);
+                            }
+
+                            @Override
                             public void onContentChanged(Tab tab) {
                                 if (tab != null) {
                                     updateColor(tab, tab.getThemeColor(), false);
diff --git a/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProviderUnitTest.java b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProviderUnitTest.java
new file mode 100644
index 0000000..66fe6ca3
--- /dev/null
+++ b/chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProviderUnitTest.java
@@ -0,0 +1,98 @@
+// 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.chrome.browser.theme;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.view.ContextThemeWrapper;
+
+import androidx.annotation.ColorInt;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.supplier.ObservableSuppliers;
+import org.chromium.base.supplier.SettableNullableObservableSupplier;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.TabObserver;
+import org.chromium.chrome.browser.tab.TabState;
+import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
+import org.chromium.components.browser_ui.styles.ChromeColors;
+
+/** Unit tests for {@link TopUiThemeColorProvider}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class TopUiThemeColorProviderUnitTest {
+    @Rule public MockitoRule mMockitoJUnit = MockitoJUnit.rule();
+
+    @Mock private Tab mTab;
+    @Captor private ArgumentCaptor<TabObserver> mTabObserverCaptor;
+
+    private static final @ColorInt int ACTIVITY_COLOR = Color.GREEN;
+
+    private Context mContext;
+    private TopUiThemeColorProvider mProvider;
+    private SettableNullableObservableSupplier<Tab> mTabSupplier;
+
+    @Before
+    public void setUp() {
+        mContext =
+                new ContextThemeWrapper(
+                        ContextUtils.getApplicationContext(), R.style.Theme_BrowserUI_DayNight);
+
+        when(mTab.getContext()).thenReturn(mContext);
+        when(mTab.isIncognito()).thenReturn(false);
+        when(mTab.isNativePage()).thenReturn(false);
+        when(mTab.isThemingAllowed()).thenReturn(true);
+        when(mTab.getThemeColor()).thenReturn(TabState.UNSPECIFIED_THEME_COLOR);
+
+        mTabSupplier = ObservableSuppliers.createNullable();
+        mProvider =
+                new TopUiThemeColorProvider(
+                        mContext,
+                        mTabSupplier,
+                        () -> ACTIVITY_COLOR,
+                        /* isTablet= */ false,
+                        /* allowThemingInNightMode= */ true,
+                        /* allowBrightThemeColors= */ true,
+                        /* allowThemingOnTablets= */ true);
+        mTabSupplier.set(mTab);
+        verify(mTab).addObserver(mTabObserverCaptor.capture());
+    }
+
+    @Test
+    public void testOnSSLStateUpdated() {
+        assertEquals(
+                "Activity theme color should be used while theming is allowed",
+                ACTIVITY_COLOR,
+                mProvider.getThemeColor());
+
+        when(mTab.isThemingAllowed()).thenReturn(false);
+        mTabObserverCaptor.getValue().onSSLStateUpdated(mTab);
+
+        assertEquals(
+                "Default theme color should be used once theming is no longer allowed",
+                ChromeColors.getDefaultThemeColor(mContext, /* isIncognito= */ false),
+                mProvider.getThemeColor());
+        assertEquals(
+                "Default color scheme should be used once theming is no longer allowed",
+                BrandedColorScheme.APP_DEFAULT,
+                mProvider.getBrandedColorScheme());
+    }
+}
diff --git a/chrome/browser/ui/android/theme/BUILD.gn b/chrome/browser/ui/android/theme/BUILD.gn
index d5937f2..b17cf4f 100644
--- a/chrome/browser/ui/android/theme/BUILD.gn
+++ b/chrome/browser/ui/android/theme/BUILD.gn
@@ -59,6 +59,7 @@
     "java/src/org/chromium/chrome/browser/theme/BottomUiThemeColorProviderTest.java",
     "java/src/org/chromium/chrome/browser/theme/ThemeUtilsUnitTest.java",
     "java/src/org/chromium/chrome/browser/theme/ToolbarThemeColorProviderTest.java",
+    "java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProviderUnitTest.java",
   ]
   deps = [
     ":java",
Loading diff…

Original Bug Report

reported by [email protected]

Potential security indicator degradation on post-commit downgrade in WebAPK/TWA

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: A potential vulnerability in Android WebAPK/TWA surface theme color handling can lead to security indicator degradation. When a page undergoes a post-commit security state downgrade to DANGEROUS without navigation, the custom branded theme color can persist on the toolbar. Consequently, the forced-visible toolbar remains themed, the warning icon is tinted neutrally instead of red, and URL scheme emphasis is disabled.

Affected files:

  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.java
  • chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
  • chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
  • chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java

Estimated timestamp from git blame: 2022-01-17

Potential Security Indicator Degradation on Post-Commit Downgrade in WebAPK/TWA

Description

In Android WebAPK and Trusted Web Activity (TWA) surfaces, a potential logic flaw allows an attacker-controlled manifest theme_color to persist even after the page’s security state is downgraded to DANGEROUS (e.g., due to loading an active subresource with certificate errors on a previously trusted domain).

Consequently, when the security toolbar is forced on-screen to warn the user, it remains themed with the attacker-chosen color, the SSL warning triangle icon is tinted with a neutral contrast color (grey or white) instead of warning red, and URL scheme emphasis is disabled. This significantly weakens the visual prominence of the DANGEROUS security warning.

Potential Root Cause Analysis

  1. Short-Circuit in Theme Updates: When a tab’s security state changes to DANGEROUS, the SSL-to-theme bridge (TabThemeColorHelper.onSSLStateUpdated -> TabImpl.updateThemeColor()) clamps the theme color to UNSPECIFIED_THEME_COLOR since theming is no longer allowed. However, if the page does not explicitly specify a <meta name="theme-color"> tag, the current mThemeColor in TabImpl is already TabState.UNSPECIFIED_THEME_COLOR (default value). As a result, the update in TabImpl.java (line 1956) short-circuits:

    // chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
    void updateThemeColor(int themeColor) {
        if (!isThemingAllowed()) {
            themeColor = TabState.UNSPECIFIED_THEME_COLOR;
        }
        if (mThemeColor == themeColor) { // UNSPECIFIED_THEME_COLOR == UNSPECIFIED_THEME_COLOR (0 == 0)
            return; // Short-circuits; onDidChangeThemeColor is never fired
        }
        ...
    }
    

    Because it returns early, onDidChangeThemeColor is never fired to notify the registered theme providers.

  2. Lack of SSL State Observers: Both BrowserServicesThemeColorProvider and TopUiThemeColorProvider fail to listen directly for onSSLStateUpdated events in their respective tab observers (chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.java line 89 and chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java line 72). Since onDidChangeThemeColor is never fired, neither provider recalculates whether theming is allowed.

  3. Neutral Warning Tint and Lack of URL Emphasis: Because the theme providers fail to update, LocationBarModel.mPrimaryColor retains the stale, attacker-provided WebAPK manifest color.

    • When the toolbar determines the warning icon tint in LocationBarModel.getSecurityIconColorWithSecurityLevel() (chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java line 851), it calculates a branded color scheme (e.g. LIGHT_BRANDED_THEME or DARK_BRANDED_THEME) instead of BrandedColorScheme.APP_DEFAULT. Under branded schemes, it falls back to a neutral grey/white contrast tint (line 868) instead of the standard warning red (R.color.default_red).
    • Additionally, LocationBarModel.shouldEmphasizeHttpsScheme() (line 597) returns false because the system still reports that it is using a brand color (mIsUsingBrandColor == true), preventing the URL scheme from being emphasized.

Potential Steps to Trigger / Reproduce

Note: Since our automated tooling cannot run arbitrary code or execute interactive Android environments, the following are suggested/potential reproduction steps based on static analysis:

  1. Compile and install an Android WebAPK or TWA for a PWA that specifies "theme_color": "#00aa00" (or another highly distinct color) in its manifest, but has no <meta name="theme-color"> tag in its web pages.
  2. From an external domain (e.g., https://cdn.attacker.test), serve an active subresource over TLS with an invalid certificate. Have the user proceed through the SSL certificate interstitial once (e.g., in Chrome) so that the certificate error is added to the local allowlist.
  3. Open the installed WebAPK. The origin will verify, the toolbar will be hidden, and the status bar will be the manifest green color (#00aa00).
  4. Trigger the injection of the allowlisted insecure subresource (e.g., <script src="https://cdn.attacker.test/script.js"></script>) without executing a page navigation.
  5. Observe that the TWA/WebAPK toolbar slides back into view to warn the user, but:
    • The toolbar background is still green.
    • The warning triangle icon is displayed but tinted white/grey instead of red.
    • The HTTPS scheme is not emphasized.

Suggested Fix

Ensure that both TopUiThemeColorProvider and BrowserServicesThemeColorProvider listen to SSL/security state changes.

  1. In chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java, add onSSLStateUpdated to mTabObserver to trigger updateColor():

    @Override
    public void onSSLStateUpdated(Tab tab) {
        if (tab != null) {
            updateColor(tab, tab.getThemeColor(), false);
        }
    }
    
  2. In chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.java, add onSSLStateUpdated to mTabObserver to trigger updateTheme():

    @Override
    public void onSSLStateUpdated(Tab tab) {
        updateTheme();
    }
    

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


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