CVE-2026-79180
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/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.javachrome/android/junit/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProviderUnitTest.javachrome/browser/ui/android/theme/BUILD.gnchrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.javachrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProviderUnitTest.java
Patch
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/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()); + }
Original Bug Report
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.javachrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.javachrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.javachrome/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
-
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 toUNSPECIFIED_THEME_COLORsince theming is no longer allowed. However, if the page does not explicitly specify a<meta name="theme-color">tag, the currentmThemeColorinTabImplis alreadyTabState.UNSPECIFIED_THEME_COLOR(default value). As a result, the update inTabImpl.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,
onDidChangeThemeColoris never fired to notify the registered theme providers. -
Lack of SSL State Observers: Both
BrowserServicesThemeColorProviderandTopUiThemeColorProviderfail to listen directly foronSSLStateUpdatedevents in their respective tab observers (chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.javaline 89 andchrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.javaline 72). SinceonDidChangeThemeColoris never fired, neither provider recalculates whether theming is allowed. -
Neutral Warning Tint and Lack of URL Emphasis: Because the theme providers fail to update,
LocationBarModel.mPrimaryColorretains 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.javaline 851), it calculates a branded color scheme (e.g.LIGHT_BRANDED_THEMEorDARK_BRANDED_THEME) instead ofBrandedColorScheme.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) returnsfalsebecause the system still reports that it is using a brand color (mIsUsingBrandColor == true), preventing the URL scheme from being emphasized.
- When the toolbar determines the warning icon tint in
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:
- 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. - 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. - Open the installed WebAPK. The origin will verify, the toolbar will be hidden, and the status bar will be the manifest green color (
#00aa00). - Trigger the injection of the allowlisted insecure subresource (e.g.,
<script src="https://cdn.attacker.test/script.js"></script>) without executing a page navigation. - 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.
-
In
chrome/browser/ui/android/theme/java/src/org/chromium/chrome/browser/theme/TopUiThemeColorProvider.java, addonSSLStateUpdatedtomTabObserverto triggerupdateColor():@Override public void onSSLStateUpdated(Tab tab) { if (tab != null) { updateColor(tab, tab.getThemeColor(), false); } } -
In
chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/BrowserServicesThemeColorProvider.java, addonSSLStateUpdatedtomTabObserverto triggerupdateTheme():@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.