CVE-2026-79152
Overview
Background
- Custom Tabs (`CustomTabsConnection`)
- Chrome for Android’s embedded-browser surface that lets a host app launch web content via an intent, with a session that may be associated with a verified first-party app.
- `Browser.EXTRA_HEADERS`
- an intent extra carrying HTTP headers to attach to the launched navigation; non-safelisted headers are only honored when the intent is deemed first-party.
- First-party origin check (`isFirstPartyOriginForIntent`)
- the authorization test that confirms the app behind the session has a verified relation with a given origin before privileged headers are allowed.
- `params.getUrl()` vs `getUrlFromIntent`
- the URL actually being navigated to (as resolved into
LoadUrlParams) versus the raw URL read straight from the intent extra.
Root Cause Analysis
The vulnerable path is header extraction for a Custom Tabs navigation: getExtraHeadersFromIntent decided whether to allow non-safelisted headers by calling isFirstPartyOriginForIntent(intent), which internally re-read the origin via IntentHandler.getUrlFromIntent(intent) — the URL sitting in the initial intent state, not the URL the request is actually navigating to. The invariant that should hold is that the origin authorized for privileged headers must be the origin those headers are ultimately sent to; when the effective navigation URL (params.getUrl()) diverges from the raw intent URL, that invariant breaks.
The fix threads the actual destination URL down: getExtraHeadersFromIntent now takes an explicit url parameter (populated from params.getUrl() at the call site) and forwards it to isFirstPartyOriginForIntent(intent, url), which validates against that URL instead of re-deriving one from the intent. This ensures the first-party relation is checked against the origin the headers will accompany, closing the gap between validation time and use time.
Attack Path
- Establish a session A malicious or colluding app holds a Custom Tabs session whose first-party relation is verified for its own origin.
- Craft the intent
The app launches with
Browser.EXTRA_HEADERScontaining non-safelisted headers and arranges the intent so the origin evaluated bygetUrlFromIntentdiffers from the URL that is actually navigated (params.getUrl()). - Bypass the origin check
Pre-patch,
isFirstPartyOriginForIntent(intent)validates against the initial-intent origin, so the first-party test passes for the wrong origin andshouldAllowNonSafelistedHeadersbecomes true. - Deliver privileged headers cross-origin The non-safelisted headers are attached to the navigation to the real destination origin (inferred), which should not have received them.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java |
modified |
Files Changed
chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.javachrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
Audit Directions
- Validation-vs-use URL divergenceLook for security checks that re-derive a URL/origin from the raw intent or request instead of the value actually used downstream (
params.getUrl()), a classic time-of-check/time-of-use mismatch. - Intent extra trust in Custom TabsAudit other
EXTRA_*handlers (referrer, headers, POST data) for first-party or session gating that keys off the wrong URL source. - Overload defaultsWhen a check gains an overload with a defaulted parameter, verify every legacy caller that keeps the old signature is not silently reintroducing the original wrong-source behavior.
Patch
From 0e64745bdd4a1534f8fe63a532fb25d8d44d18c5 Mon Sep 17 00:00:00 2001 From: Mohamed Adel <[email protected]> Date: Mon, 13 Jul 2026 16:03:16 -0700 Subject: [PATCH] Update intent URL extraction for EXTRA_HEADERS in Custom Tabs Ensure that EXTRA_HEADERS intent evaluation properly uses the navigated URL rather than relying solely on the initial intent state for validation. TAG=agy Bug: 533083384 Change-Id: If7f794ed46742776edfb970d524733861f445880 Fixed: 533083384 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8086206 Reviewed-by: Wenyu Fu <[email protected]> Commit-Queue: Moe Adel <[email protected]> Reviewed-by: Sinan Sahin <[email protected]> Cr-Commit-Position: refs/heads/main@{#1661446} --- diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java index 386acc6..abaf8cb 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java @@ -732,7 +732,7 @@ if (referrer != null) { params.setReferrer(new Referrer(referrer, getReferrerPolicyFromIntent(intent))); } - String headers = getExtraHeadersFromIntent(intent); + String headers = getExtraHeadersFromIntent(intent, params.getUrl()); if (headers != null) params.setVerbatimHeaders(headers); } @@ -915,13 +915,25 @@ * @param intent The intent containing the bundle extra with the HTTP headers. */ public static @Nullable String getExtraHeadersFromIntent(Intent intent) { + return getExtraHeadersFromIntent(intent, IntentHandler.getUrlFromIntent(intent)); + } + + /** + * Returns a String (or null) containing the extra headers sent by the intent, if any. + * + * <p>This methods skips the referrer header. + * + * @param intent The intent containing the bundle extra with the HTTP headers. + * @param url The destination URL to verify first party origin against. + */ + public static @Nullable String getExtraHeadersFromIntent(Intent intent, @Nullable String url) { Bundle bundleExtraHeaders = IntentUtils.safeGetBundleExtra(intent, Browser.EXTRA_HEADERS); if (bundleExtraHeaders == null) return null; StringBuilder extraHeaders = new StringBuilder(); boolean fromChrome = IntentHandler.wasIntentSenderChrome(intent); boolean shouldAllowNonSafelistedHeaders = - CustomTabsConnection.getInstance().isFirstPartyOriginForIntent(intent); + CustomTabsConnection.getInstance().isFirstPartyOriginForIntent(intent, url); for (String key : bundleExtraHeaders.keySet()) { String value = bundleExtraHeaders.getString(key); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java index a8e73f5..268f1ab 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java @@ -1007,10 +1007,20 @@ * @param intent The intent to verify. */ public boolean isFirstPartyOriginForIntent(Intent intent) { + return isFirstPartyOriginForIntent(intent, IntentHandler.getUrlFromIntent(intent)); + } + + /** + * Returns whether an intent is first-party with respect to its session, that is if the + * application linked to the session has a relation with the provided origin. + * + * @param intent The intent to verify. + * @param url The url to verify against. + */ + public boolean isFirstPartyOriginForIntent(Intent intent, @Nullable String url) { SessionHolder<?> session = SessionHolder.getSessionHolderFromIntent(intent); if (session == null) return false; - String url = IntentHandler.getUrlFromIntent(intent); if (url == null) return false; Origin origin = Origin.create(url);