Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in CustomTabs
DescriptionUI misrepresentation in CustomTabs
ComponentCustomTabs
Bug ClassLogic Error
Tracker517719358
Fix commitd125c0dee27c (chromium/src) +142/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

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

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.java
  • chrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProviderUnitTest.java
  • chrome/browser/ui/android/theme/BUILD.gn
  • chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java
  • chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProviderUnitTest.java
From d125c0dee27c0641133c990eb1ae3865564f5eac Mon Sep 17 00:00:00 2001
From: Mohamed Adel <adelm@google.com>
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 <adelm@google.com>
Reviewed-by: Jinsuk Kim <jinsukkim@chromium.org>
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/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",
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());
+    }
Loading diff…

Original Bug Report

reported by vm...@google.com

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