CVE-2026-79233
Overview
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”), andDANGEROUS. - `SecurityStateModel`
- the browser-side source of truth for the real per-
WebContentssecurity level, consulted byLocationBarModelwhen 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.
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
- 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.WARNINGrather thanSECURE(for example mixed content or a downgraded TLS state). - Bypass the weak gate
In
TrustedCdn.getPublisherUrl(), the level is notDANGEROUS, so the pre-patchif (level == ConnectionSecurityLevel.DANGEROUS)check does not fire and the valid publisherGURLis returned. - Omnibox shows publisher origin without the security warning
LocationBarModeluses the returned publisher URL, so the user sees the publisher’s origin in the Custom Tab omnibox even though the connection is “Not secure.” - 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
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.javachrome/android/javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java
Audit Directions
- Negative-only security gatesLook 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 couplingAudit 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 stateVerify 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.
Patch
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; }