Low watch CVSS 4.3 chrome Spoofing 🔧 Commit mapped

Overview

Low
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in CustomTabs
DescriptionUI misrepresentation in CustomTabs
ComponentCustomTabs
Sub-componentTrustedCdn
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.
Tracker514061923
Fix commite363b1cb92fe (chromium/src) +35/-1
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-05-17 (per vendor advisory)
Fix landed2026-07-01 (public git)
Disclosed2026-08-25
Patch gap55 days — fix public in source before the advisory

Background

Custom Tabs / AMP viewer
an Android surface that lets an embedding app show a page, where a “trusted CDN” (e.g. an AMP cache) may serve content on behalf of a different publisher.
`TrustedCdn.getPublisherUrl()`
returns the publisher’s origin so the omnibox displays the publisher URL instead of the CDN’s URL, giving the impression the user is on the publisher’s site.
`ConnectionSecurityLevel`
an enum describing a connection’s security posture, with values such as SECURE, WARNING (shown as “Not secure”), and DANGEROUS.
`SecurityStateModel`
the browser-side source of truth for the real per-WebContents security level, consulted by LocationBarModel when rendering the omnibox.

Root Cause Analysis

TrustedCdn.getPublisherUrl() decides whether to substitute the publisher URL into the omnibox based on the connection’s ConnectionSecurityLevel. The pre-patch guard only bailed out when level == ConnectionSecurityLevel.DANGEROUS, so any non-DANGEROUS but still-degraded state — notably WARNING (“Not secure”) — passed the check and the publisher URL was returned and displayed. As a result the omnibox could show a trusted publisher origin while the underlying connection was flagged as not secure, since LocationBarModel used the returned publisher URL instead of falling through to the real SecurityStateModel result.

The fix inverts the gate to level != ConnectionSecurityLevel.SECURE, so the publisher URL is only produced when the connection is fully SECURE; in every degraded state the method returns null and the omnibox falls back to the genuine security state. This restores the invariant that a publisher origin is only shown together with a secure security indicator.

Key insight
The check was an allowlist-by-exclusion of a single bad value (DANGEROUS) rather than a strict requirement of the single good value (SECURE), leaving the intermediate WARNING state able to display a spoofed publisher URL; the fix flips it to require SECURE.

Attack Path

  1. Serve degraded content via the CDN An attacker arranges for a trusted-CDN-served page (e.g. an AMP-cached publisher page) to load over a connection that resolves to ConnectionSecurityLevel.WARNING rather than SECURE (for example mixed content or a downgraded TLS state).
  2. Bypass the weak gate In TrustedCdn.getPublisherUrl(), the level is not DANGEROUS, so the pre-patch if (level == ConnectionSecurityLevel.DANGEROUS) check does not fire and the valid publisher GURL is returned.
  3. Omnibox shows publisher origin without the security warning LocationBarModel uses the returned publisher URL, so the user sees the publisher’s origin in the Custom Tab omnibox even though the connection is “Not secure.”
  4. UI misrepresentation The user is misled into trusting content on a connection that Chrome would otherwise have flagged as not secure (inferred impact of the spoof).

Impact Assessment

An attacker gains UI spoofing in the Custom Tabs omnibox: a publisher origin can be displayed for a connection whose real security level is degraded (WARNING), suppressing the “Not secure” indication the user would normally rely on. This is a browser-UI/spoofing issue on Android, requiring the content to be delivered through the trusted-CDN publisher-URL path with a non-SECURE, non-DANGEROUS connection level. The advisory classifies this as spoofing / UI misrepresentation in CustomTabs; no exploited-in-the-wild status is stated.

Files Changed

  • chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TrustedCdn.java
  • chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java

Audit Directions

  • Negative-only security gates
    Look for checks that reject a single bad enum value (e.g. == DANGEROUS) instead of requiring the specific good value; intermediate/warning states often slip through.
  • URL substitution vs. security indicator coupling
    Audit any code that overrides the displayed origin (publisher URLs, AMP, reader mode) to confirm the substitution is gated on the same security state that drives the lock/“Not secure” indicator.
  • Fallthrough to authoritative state
    Verify that when an override path bails out it lets callers fall back to SecurityStateModel/LocationBarModel’s real result rather than a stale or optimistic default.
From e363b1cb92fe93b6bdaf2142aa044c37ad43a806 Mon Sep 17 00:00:00 2001
From: Mohamed Adel <[email protected]>
Date: Wed, 01 Jul 2026 16:47:57 -0700
Subject: [PATCH] [TrustedCdn] Require SECURE state for publisher URL

Tighten the gate in TrustedCdn.getPublisherUrl() to bail on anything
other than SECURE (not just DANGEROUS), so LocationBarModel falls
through to the real SecurityStateModel result whenever the connection is
degraded.

TAG=agy

Bug: 514061923
Change-Id: I86e9079540079b5e7284fb5c50b1f2f06d7248ea
Fixed: 514061923
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8032512
Commit-Queue: Moe Adel <[email protected]>
Auto-Submit: Moe Adel <[email protected]>
Reviewed-by: Jinsuk Kim <[email protected]>
Commit-Queue: Jinsuk Kim <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1655822}
---
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java
index 923cd68..c22d1c6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java
@@ -10,6 +10,8 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static org.hamcrest.CoreMatchers.allOf;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
 
 import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
 
@@ -42,6 +44,9 @@
 import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import org.chromium.base.CommandLine;
 import org.chromium.base.ContextUtils;
@@ -77,6 +82,9 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.components.offlinepages.SavePageResult;
+import org.chromium.components.security_state.ConnectionSecurityLevel;
+import org.chromium.components.security_state.SecurityStateModel;
+import org.chromium.components.security_state.SecurityStateModelJni;
 import org.chromium.components.url_formatter.SchemeDisplay;
 import org.chromium.components.url_formatter.UrlFormatter;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
@@ -96,6 +104,10 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 public class TrustedCdnPublisherUrlTest {
     public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
+
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Mock SecurityStateModel.Natives mSecurityStateModelNatives;
+
     public ChromeRenderTestRule mRenderTestRule =
             ChromeRenderTestRule.Builder.withPublicCorpus()
                     .setBugComponent(ChromeRenderTestRule.Component.UI_BROWSER_MOBILE_CUSTOM_TABS)
@@ -148,6 +160,12 @@
         ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
 
         LibraryLoader.getInstance().ensureInitialized();
+
+        SecurityStateModelJni.setInstanceForTesting(mSecurityStateModelNatives);
+        doReturn(ConnectionSecurityLevel.SECURE)
+                .when(mSecurityStateModelNatives)
+                .getSecurityLevelForWebContents(any());
+
         mWebServer = TestWebServer.start();
         if (mOverrideTrustedCdn.isEnabled()) {
             CommandLine.getInstance()
@@ -242,6 +260,22 @@
     @SmallTest
     @Feature({"UiCatalogue"})
     @OverrideTrustedCdn
+    public void testNotSecureConnectionLevel() throws Exception {
+        doReturn(ConnectionSecurityLevel.WARNING)
+                .when(mSecurityStateModelNatives)
+                .getSecurityLevelForWebContents(any());
+
+        runTrustedCdnPublisherUrlTest(
+                "https://example.com/test",
+                "com.example.test",
+                null,
+                R.drawable.omnibox_not_secure_warning);
+    }
+
+    @Test
+    @SmallTest
+    @Feature({"UiCatalogue"})
+    @OverrideTrustedCdn
     public void testPageInfo() throws Exception {
         runTrustedCdnPublisherUrlTest(
                 "https://example.com/test",
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TrustedCdn.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TrustedCdn.java
index 06e129a9..e746e2d 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TrustedCdn.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/TrustedCdn.java
@@ -146,7 +146,7 @@
             return null;
         }
         int level = SecurityStateModel.getSecurityLevelForWebContents(mTab.getWebContents());
-        if (level == ConnectionSecurityLevel.DANGEROUS) return null;
+        if (level != ConnectionSecurityLevel.SECURE) return null;
         GURL publisherUrl = TrustedCdnJni.get().getPublisherUrl(mNativeTrustedCdn);
         return publisherUrl.isValid() ? publisherUrl : null;
     }
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.