Low watch CVSS 9.8 chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
9.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in CustomTabs
DescriptionIncorrect authorization in CustomTabs
ComponentCustomTabs
Bug ClassAuthorization Error
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.
Tracker533083384
Fix commit0e64745bdd4a (chromium/src) +25/-3
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-07-09 (per vendor advisory)
Fix landed2026-07-13 (public git)
Disclosed2026-08-25
Patch gap43 days — fix public in source before the advisory

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.

Key insight
The single mistake was authorizing privileged extra headers against the origin taken from the initial intent rather than the origin actually being navigated to; the fix passes the effective navigation URL into the first-party check so authorization and destination match.

Attack Path

  1. Establish a session A malicious or colluding app holds a Custom Tabs session whose first-party relation is verified for its own origin.
  2. Craft the intent The app launches with Browser.EXTRA_HEADERS containing non-safelisted headers and arranges the intent so the origin evaluated by getUrlFromIntent differs from the URL that is actually navigated (params.getUrl()).
  3. Bypass the origin check Pre-patch, isFirstPartyOriginForIntent(intent) validates against the initial-intent origin, so the first-party test passes for the wrong origin and shouldAllowNonSafelistedHeaders becomes true.
  4. 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

An attacker controlling a Custom Tabs launch can cause non-safelisted HTTP headers to be sent to an origin that was not the one authorized by the first-party relation, an incorrect-authorization flaw in the browser process on Android. The precondition is the ability to launch a Custom Tab with a session and to make the evaluated intent URL differ from the effective navigation URL; the advisory classifies this as incorrect authorization in CustomTabs and reports no in-the-wild exploitation. Any further consequence (e.g. injecting sensitive headers to attacker-chosen sites) depends on which headers pass the safelist gate (inferred).

Changed Functions

FunctionChangeNotes
if
chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
modified

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java

Audit Directions

  • Validation-vs-use URL divergence
    Look 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 Tabs
    Audit other EXTRA_* handlers (referrer, headers, POST data) for first-party or session gating that keys off the wrong URL source.
  • Overload defaults
    When 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.
From 0e64745bdd4a1534f8fe63a532fb25d8d44d18c5 Mon Sep 17 00:00:00 2001
From: Mohamed Adel <adelm@google.com>
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 <wenyufu@chromium.org>
Commit-Queue: Moe Adel <adelm@google.com>
Reviewed-by: Sinan Sahin <sinansahin@google.com>
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);
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.