Overview

Low
Severity
4.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in CustomTabs
DescriptionUI misrepresentation in CustomTabs
ComponentCustomTabs
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.
Tracker533018632
Fix commit3bd23769cab8 (chromium/src) +36/-11
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:L
CISA KEVNot listed
CreditedGoogle
Reported2026-07-09 (per vendor advisory)
Fix landed2026-07-13 (public git)
Disclosed2026-09-08
Patch gap57 days — fix public in source before the advisory

Background

Trusted Web Activity (TWA)
an Android mode where a first-party-verified web origin renders full-screen inside a CustomTabs-based app with Chrome’s URL bar and browser controls hidden.
Browser controls / toolbar
the Chrome UI (URL bar, security indicator) that a TWA normally suppresses; @BrowserControlsState values SHOWN and HIDDEN decide whether it is force-displayed.
`ConnectionSecurityLevel`
an enum describing a page’s connection trust, including SECURE, WARNING (e.g. mixed content / passive downgrade), NONE (plain HTTP), and DANGEROUS (active certificate errors).
Security indicator suppression
the spoofing risk where hiding the toolbar hides the security state, so a downgraded connection looks as trustworthy as a SECURE one.

Root Cause Analysis

computeBrowserControlsState(@Nullable Tab tab) in TrustedWebActivityBrowserControlsVisibilityManager decides whether the toolbar must be force-shown before falling through to the normal shouldShowBrowserControlsAndCloseButton(tab) logic that lets a TWA hide it.

Pre-patch, it force-showed only when getSecurityLevel(tab) == ConnectionSecurityLevel.DANGEROUS, so every non-DANGEROUS state — including WARNING (mixed content) — fell through and allowed the origin to keep the toolbar hidden. The invariant that a user must see a security indicator whenever the connection is not trustworthy was therefore violated for the WARNING band.

The fix relaxes the force-show gate: it now also returns BrowserControlsState.SHOWN when securityLevel == ConnectionSecurityLevel.WARNING, so mixed-content and similar warning pages can no longer suppress the indicator inside a TWA. The removed comment (“Force browser controls to show when the security level is dangerous”) documents the old, too-narrow behavior.

Key insight
The single mistake was treating only ConnectionSecurityLevel.DANGEROUS as toolbar-forcing, letting a WARNING-level (e.g. mixed-content) page keep the security indicator hidden in a TWA; the fix broadens the force-show condition to include WARNING so those states can no longer be visually suppressed.

Attack Path

  1. Launch a TWA for an attacker-controlled origin A malicious app opens its verified web origin as a Trusted Web Activity, which by default hides the URL bar and security indicator for a full-screen, app-like presentation.
  2. Serve content that yields a `WARNING` security level The page is delivered so the tab’s getSecurityLevel(tab) returns ConnectionSecurityLevel.WARNING (for example an HTTPS page with mixed/insecure sub-resources) rather than DANGEROUS.
  3. Fall through the too-narrow gate Because the pre-patch check only force-showed on DANGEROUS, computeBrowserControlsState skips the force-show branch and defers to shouldShowBrowserControlsAndCloseButton(tab), which permits the toolbar to stay hidden.
  4. Present a downgraded connection as trustworthy With no visible security indicator, the user cannot distinguish the WARNING connection from a SECURE one, enabling UI misrepresentation/spoofing of the site’s security state.

Impact Assessment

An attacker running a TWA gains the ability to hide the browser’s security indicator while serving a WARNING-level (e.g. mixed-content) connection, making an insecurely-loaded page appear as trustworthy as a fully secure one — a spoofing / UI-misrepresentation issue in the browser UI on Android. Preconditions are that the attacker controls (and has digital-asset-links-verified) the origin launched as a TWA and can drive the tab into the WARNING security band. The advisory classifies this as Spoofing / UI misrepresentation in CustomTabs; no exploited-in-the-wild status or memory-safety escalation is indicated by the patch.

Changed Functions

FunctionChangeNotes
if
chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManager.java
modified

Files Changed

  • chrome/android/junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManagerTest.java
  • chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManager.java

Audit Directions

  • Security-state gates that enumerate a single "bad" value
    Search for controls-visibility, indicator, or interstitial logic that special-cases only ConnectionSecurityLevel.DANGEROUS (or one enum member) instead of the inverse == SECURE check; any non-secure band left out is a suppression gap.
  • TWA / `CustomTabs` toolbar-hiding paths
    Audit every place a first-party origin can hide the URL bar (shouldShowBrowserControlsAndCloseButton, close-button visibility, minimal-ui/WebAPK modes) to confirm all untrustworthy connection states still force the indicator.
  • Intent-versus-implementation drift
    Note that the commit message states the goal as showing controls for everything except SECURE (including NONE), yet the diff only adds WARNING and the added testNoneSecurityLevel asserts NONE stays HIDDEN; re-audit whether plain-HTTP (NONE) TWAs should also force the toolbar.
From 3bd23769cab8ef0cc0512054a26c467eeeda2c53 Mon Sep 17 00:00:00 2001
From: Peter E Conn <peconn@google.com>
Date: Mon, 13 Jul 2026 08:43:17 -0700
Subject: [PATCH] Fix TWA SecurityLevel checks for toolbar hiding

Instead of specifically checking for ConnectionSecurityLevel.DANGEROUS
to force-show the browser controls, check if the security level is
SECURE. This ensures that the URL bar is shown not only for DANGEROUS
but also for WARNING and NONE states (e.g. mixed content, http).
This prevents origins from suppressing security indicators with TWAs.

TAG=agy
CONV=c4062454-4b45-484e-9930-7d4b9c0288d5

Fix: 533018632
Change-Id: Ic5c84ccc6650b7c346898e0603112761473aefb3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8065230
Reviewed-by: Daniel Murphy <dmurph@chromium.org>
Commit-Queue: Peter Conn <peconn@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1661143}
---
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManager.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManager.java
index 4755dab..9701c6a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManager.java
@@ -115,10 +115,13 @@
     }
 
     private @BrowserControlsState int computeBrowserControlsState(@Nullable Tab tab) {
-        // Force browser controls to show when the security level is dangerous for consistency with
-        // TabStateBrowserControlsVisibilityDelegate.
-        if (tab != null && getSecurityLevel(tab) == ConnectionSecurityLevel.DANGEROUS) {
-            return BrowserControlsState.SHOWN;
+        // Force browser controls to show when the security level is dangerous or warning.
+        if (tab != null) {
+            int securityLevel = getSecurityLevel(tab);
+            if (securityLevel == ConnectionSecurityLevel.DANGEROUS
+                    || securityLevel == ConnectionSecurityLevel.WARNING) {
+                return BrowserControlsState.SHOWN;
+            }
         }
 
         return shouldShowBrowserControlsAndCloseButton(tab)
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManagerTest.java
index 5b70936..e560e63 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManagerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/trustedwebactivityui/controller/TrustedWebActivityBrowserControlsVisibilityManagerTest.java
@@ -100,6 +100,25 @@
         assertFalse(getLastCloseButtonVisibility());
     }
 
+    /** Browser controls should be shown for pages with mixed content warnings. */
+    @Test
+    public void testWarningSecurityLevel() {
+        mController = buildController(mock(BrowserServicesIntentDataProvider.class));
+        setTabSecurityLevel(ConnectionSecurityLevel.WARNING);
+        mController.updateIsInAppMode(true);
+        assertEquals(BrowserControlsState.SHOWN, getLastBrowserControlsState());
+        assertFalse(getLastCloseButtonVisibility());
+    }
+
+    /** Browser controls should be hidden for HTTP connections and mixed forms. */
+    @Test
+    public void testNoneSecurityLevel() {
+        mController = buildController(mock(BrowserServicesIntentDataProvider.class));
+        setTabSecurityLevel(ConnectionSecurityLevel.NONE);
+        mController.updateIsInAppMode(true);
+        assertEquals(BrowserControlsState.HIDDEN, getLastBrowserControlsState());
+    }
+
     /** Browser controls should not be shown for WebAPKs with 'minimal-ui' display mode. */
     @Test
     public void testMinimalUiDisplayMode() {
@@ -201,13 +220,16 @@
 
     private TrustedWebActivityBrowserControlsVisibilityManager buildController(
             BrowserServicesIntentDataProvider intentDataProvider) {
-        return spy(
-                new TrustedWebActivityBrowserControlsVisibilityManager(
-                        mTabObserverRegistrar,
-                        mTabProvider,
-                        mToolbarCoordinator,
-                        mCloseButtonVisibilityManager,
-                        intentDataProvider));
+        TrustedWebActivityBrowserControlsVisibilityManager controller =
+                spy(
+                        new TrustedWebActivityBrowserControlsVisibilityManager(
+                                mTabObserverRegistrar,
+                                mTabProvider,
+                                mToolbarCoordinator,
+                                mCloseButtonVisibilityManager,
+                                intentDataProvider));
+        doReturn(ConnectionSecurityLevel.SECURE).when(controller).getSecurityLevel(any());
+        return controller;
     }
 
     /** Returns the current browser controls state. */
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.