Medium chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactConfused deputy in SignIn
DescriptionConfused deputy in SignIn
ComponentSignIn
Sub-componentServices (Use Subcomponents)>SignIn
Bug ClassAuthorization Error
Tracker515477538
Fix commit6be501d3a7b1 (chromium/src) +29/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegate.java
modified

Files Changed

  • chrome/android/javatests/src/org/chromium/chrome/browser/signin/services/WebSigninLoadingDialogTest.java
  • chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegate.java
  • chrome/browser/ui/android/signin/junit/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegateTest.java
  • chrome/android/java/src/org/chromium/chrome/browser/signin/WebSigninRedirectCoordinator.java
From 6be501d3a7b18c5dfc6b88d33050aabf44c6d56e Mon Sep 17 00:00:00 2001
From: Liza Bipin <mlbipin@google.com>
Date: Wed, 09 Sep 2026 03:16:10 -0700
Subject: [PATCH] Mark web sign-in continue URLs as renderer-initiated

Treat post-signin redirection navigations to the continue URL as
renderer-initiated rather than browser-initiated.

Fixed: 515477538
Change-Id: I078cd250cfdf1925e6e71e85b4ec0a20fc8d457b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8304472
Commit-Queue: Liza Bipin <mlbipin@google.com>
Reviewed-by: Boris Sazonov <bsazonov@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1694463}
---
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/WebSigninRedirectCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/WebSigninRedirectCoordinator.java
index 7fe471b..bf5c1c0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/WebSigninRedirectCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/WebSigninRedirectCoordinator.java
@@ -31,6 +31,7 @@
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.util.RunnableTimer;
 import org.chromium.url.GURL;
+import org.chromium.url.Origin;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -360,7 +361,14 @@
 
         recordWaitOutcomeCompleted();
         destroy();
-        mTab.loadUrl(new LoadUrlParams(mContinueUrl));
+        LoadUrlParams params = new LoadUrlParams(mContinueUrl);
+        // Mark the navigation as renderer-initiated rather than browser-initiated as the continue
+        // URL originates from the web page.
+        params.setIsRendererInitiated(true);
+        // Use an opaque origin because the source cannot be trusted and should not have same-origin
+        // privileges.
+        params.setInitiatorOrigin(Origin.createOpaqueOrigin());
+        mTab.loadUrl(params);
     }
 
     private void startTimerOrForceShowDialog() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/services/WebSigninLoadingDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/services/WebSigninLoadingDialogTest.java
index 3ead9d4..7747e2a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/signin/services/WebSigninLoadingDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/signin/services/WebSigninLoadingDialogTest.java
@@ -393,6 +393,9 @@
         ArgumentCaptor<LoadUrlParams> captor = ArgumentCaptor.forClass(LoadUrlParams.class);
         verify(spyTab).loadUrl(captor.capture());
         Assert.assertEquals("https://continue.url/", captor.getValue().getUrl());
+        Assert.assertTrue(captor.getValue().getIsRendererInitiated());
+        Assert.assertNotNull(captor.getValue().getInitiatorOrigin());
+        Assert.assertTrue(captor.getValue().getInitiatorOrigin().isOpaque());
     }
 
     @Test
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegate.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegate.java
index a0b867e..d6b849c 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegate.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegate.java
@@ -25,6 +25,7 @@
 import org.chromium.components.signin.metrics.SigninAccessPoint;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.url.GURL;
+import org.chromium.url.Origin;
 
 import java.util.function.Function;
 
@@ -169,7 +170,7 @@
                     if (tab != null && !tab.isDestroyed()) {
                         // This code path may be called asynchronously, so check
                         // that the tab is still alive.
-                        tab.loadUrl(new LoadUrlParams(continueUrl));
+                        tab.loadUrl(createLoadUrlParamsForContinueUrl(continueUrl));
                     }
                     SigninSurveyController.registerTrigger(
                             mProfile, SigninSurveyController.SigninSurveyType.WEB);
@@ -202,7 +203,7 @@
                     if (!tab.isDestroyed()) {
                         // This code path may be called asynchronously, so check that the tab is
                         // still alive.
-                        tab.loadUrl(new LoadUrlParams(continueUrl));
+                        tab.loadUrl(createLoadUrlParamsForContinueUrl(continueUrl));
                     }
                     break;
                 case WebSigninTrackerResult.AUTH_ERROR:
@@ -221,6 +222,17 @@
         };
     }
 
+    private static LoadUrlParams createLoadUrlParamsForContinueUrl(GURL continueUrl) {
+        LoadUrlParams params = new LoadUrlParams(continueUrl);
+        // Mark the navigation as renderer-initiated rather than browser-initiated as the continue
+        // URL originates from the web page.
+        params.setIsRendererInitiated(true);
+        // Use an opaque origin because the source cannot be trusted and should not have same-origin
+        // privileges.
+        params.setInitiatorOrigin(Origin.createOpaqueOrigin());
+        return params;
+    }
+
     private void destroyWebSigninBridge() {
         if (mWebSigninBridge != null) {
             mWebSigninBridge.destroy();
diff --git a/chrome/browser/ui/android/signin/junit/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegateTest.java b/chrome/browser/ui/android/signin/junit/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegateTest.java
index cc04e24..90b0690 100644
--- a/chrome/browser/ui/android/signin/junit/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegateTest.java
+++ b/chrome/browser/ui/android/signin/junit/src/org/chromium/chrome/browser/ui/signin/account_picker/WebSigninAccountPickerDelegateTest.java
@@ -199,6 +199,9 @@
             LoadUrlParams loadUrlParams = mLoadUrlParamsCaptor.getValue();
             Assert.assertEquals(
                     "Continue url does not match!", CONTINUE_URL.getSpec(), loadUrlParams.getUrl());
+            Assert.assertTrue(loadUrlParams.getIsRendererInitiated());
+            Assert.assertNotNull(loadUrlParams.getInitiatorOrigin());
+            Assert.assertTrue(loadUrlParams.getInitiatorOrigin().isOpaque());
         } else if (result == WebSigninTrackerResult.OTHER_ERROR) {
             verify(mSigninMetricsUtilsJniMock)
                     .logAccountConsistencyPromoAction(
Loading diff…

Original Bug Report

reported by rj...@google.com

Privilege escalation via unvalidated continue_url in X-Chrome-Manage-Accounts header

Flapjack, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: The X-Chrome-Manage-Accounts (Mirror) header allows Google domains to trigger account management actions. When processing the ADDSESSION service type on Android, Chrome fails to validate the scheme of the provided continue_url, potentially allowing a malicious website to force a highly privileged browser-initiated navigation to internal chrome:// or intent:// URLs.

Affected files:

  • chrome/browser/signin/chrome_signin_helper.cc
  • components/signin/core/browser/chrome_connected_header_helper.cc

Estimated timestamp from git blame: 2014-09-24

Overview

The X-Chrome-Manage-Accounts header is used by Google domains to signal account management actions to Chrome. When processing this header on Android, specifically for the GAIA_SERVICE_TYPE_ADDSESSION service type, Chrome does not validate the scheme of the continue_url parameter.

Because the resulting navigation is executed as browser-initiated (is_renderer_initiated = false), this lack of validation potentially allows an attacker to navigate the user’s browser to privileged chrome:// or intent:// URLs, bypassing web security boundaries.

Potential Exploitation Steps

Note: These steps are suggested based on static code analysis; an active Proof of Concept has not yet been executed.

  1. Attacker Lures User: An attacker hosts a malicious website (e.g., https://attacker.com/exploit.html) and lures a user to it on Chrome for Android.
  2. Navigation to Google Endpoint: The attacker’s page executes a renderer-initiated top-level navigation to a specific Google endpoint that reflects the continue query parameter into the X-Chrome-Manage-Accounts header. For example: window.location.href = "https://accounts.google.com/SignOutOptions?continue=chrome://flags".
  3. Header Interception & Parsing: The accounts.google.com server responds with an HTTP redirect and the header: X-Chrome-Manage-Accounts: action=ADDSESSION,continue_url=chrome://flags. Chrome parses this header in ChromeConnectedHeaderHelper::BuildManageAccountsParams but fails to validate the scheme of the continue_url.
  4. Navigation Cancellation: The browser’s navigation stack blocks the 302 redirect to chrome://flags because it was initiated by web content. Consequently, the navigation fails to commit, and the tab’s visible URL remains the attacker’s page (https://attacker.com/exploit.html).
  5. Task Execution (Missing Initiator Check): The asynchronously posted task ProcessMirrorHeader executes. While GAIA_SERVICE_TYPE_INCOGNITO includes an initiator origin check, GAIA_SERVICE_TYPE_ADDSESSION does not (noted by a TODO on line 246 of chrome_signin_helper.cc). The code proceeds into the ADDSESSION flow.
  6. Java Coordinator and Equality Check Bypass: The C++ code calls SigninBridgeFactory::GetForProfile(profile)->WaitForCookiesAndRedirect(...), passing the unvalidated chrome://flags URL. This transitions to Java (SigninBridge.java), which records the current URL (mInitialTabURL). Because the previous navigation failed, mInitialTabURL remains https://attacker.com/exploit.html.
  7. Browser-Initiated Navigation: After waiting for cookies, WebSigninRedirectCoordinator#onSigninResult verifies that mTab.getUrl().equals(mInitialTabURL). Since the URL never changed, this check passes. The code executes mTab.loadUrl(new LoadUrlParams(mContinueUrl)). By default, LoadUrlParams sets mIsRendererInitiated = false.
  8. Privileged Access: The navigation to chrome://flags (or an intent:// URI) succeeds because Chrome trusts browser-initiated navigations, resulting in a sandbox/security boundary escape.

Suggested Fix

  1. Scheme Validation: Update ChromeConnectedHeaderHelper::BuildManageAccountsParams or ProcessMirrorHeader to strictly validate the scheme of the continue_url. It should only permit http:// and https:// schemes, strictly forbidding chrome://, intent://, file://, etc.
  2. Origin Initiator Check: Implement the missing initiator domain check for GAIA_SERVICE_TYPE_ADDSESSION in ProcessMirrorHeader (resolving the TODO on line 246 in chrome/browser/signin/chrome_signin_helper.cc).

Evaluated with Chrome root at commit: a9e0d24757518a6f49f01bf5068f2cadfc6092f4


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker