Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in UI
DescriptionInappropriate implementation in UI
ComponentUI
Bug ClassLogic Error
Tracker501504245
Fix commit83628f15cffd (chromium/src) +113/-10
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

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

Files Changed

  • chrome/android/BUILD.gn
  • chrome/android/DEPS
  • chrome/android/java/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandler.java
  • chrome/android/javatests/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandlerTest.java
  • chrome/browser/ui/android/chrome_http_auth_handler.cc
From 83628f15cffdc2b15afda5f6d1ae0fc1e3a90bb1 Mon Sep 17 00:00:00 2001
From: Adem Derinel <derinel@google.com>
Date: Wed, 22 Apr 2026 08:03:32 -0700
Subject: [PATCH] Pass challenger URL to ChromeHttpAuthHandler for the login dialog

Provides the origin of the challenger to the HttpAuth dialog for
autofill purposes.

Killswitch: kAndroidAutofillSupportForHttpAuthOrigin
Fixed: 501504245
Change-Id: I4bd7f0f92e8b8adfca7bd82ea811a5976111a909
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7754362
Reviewed-by: Friedrich Hauser <friedrichh@chromium.org>
Reviewed-by: Peter Conn <peconn@chromium.org>
Commit-Queue: Adem Derinel <derinel@google.com>
Cr-Commit-Position: refs/heads/main@{#1618872}
---

diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index d568b6f..b212728 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -582,6 +582,7 @@
       "//chrome/browser/xsurface:java",
       "//chrome/browser/xsurface_provider:dependency_provider_impl_java",
       "//chrome/browser/xsurface_provider:java",
+      "//components/android_autofill/browser:features_java",
       "//components/android_autofill/browser:java",
       "//components/autofill/android:autofill_features_java",
       "//components/autofill/android:autofill_java",
diff --git a/chrome/android/DEPS b/chrome/android/DEPS
index 959441a..05d9b4d 100644
--- a/chrome/android/DEPS
+++ b/chrome/android/DEPS
@@ -88,6 +88,12 @@
     "+chrome/browser/xsurface/android",
   ],
 
+  # LINT.IfChange
+  "ChromeHttpAuthHandler.*\.java": [
+    "+components/android_autofill",
+  ],
+  # LINT.ThenChange(//components/android_autofill/browser/android_autofill_features.cc)
+
   # Tests and test-oriented classes are allowed to rely on ChromeActivity for DEPS. When committing
   # a file that doesn't conform to these patterns, add an allow rule to the DEPS file to the
   # testing directory.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandler.java
index f3a5e4e..7544024 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandler.java
@@ -21,9 +21,11 @@
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabHidingType;
+import org.chromium.components.autofill.AndroidAutofillFeatures;
 import org.chromium.components.browser_ui.http_auth.LoginPrompt;
 import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.ui.base.WindowAndroid;
+import org.chromium.url.GURL;
 
 /**
  * Represents an HTTP authentication request to be handled by the UI.
@@ -93,7 +95,8 @@
     }
 
     @CalledByNative
-    private void showDialog(Tab tab, WindowAndroid windowAndroid) {
+    private void showDialog(
+            Tab tab, WindowAndroid windowAndroid, @JniType("GURL") GURL challengerUrl) {
         if (tab == null || tab.isHidden() || windowAndroid == null) {
             cancel();
             return;
@@ -111,12 +114,17 @@
         mTab.addObserver(this);
         String messageBody =
                 ChromeHttpAuthHandlerJni.get().getMessageBody(mNativeChromeHttpAuthHandler);
-        mLoginPrompt =
-                new LoginPrompt(
-                        activity,
-                        messageBody,
-                        shouldProvideAutofillUrl() ? mTab.getUrl() : null,
-                        this);
+
+        GURL autofillUrl = null;
+        if (shouldProvideAutofillUrl()) {
+            if (AndroidAutofillFeatures.ANDROID_AUTOFILL_SUPPORT_FOR_HTTP_AUTH_ORIGIN.isEnabled()) {
+                autofillUrl = challengerUrl;
+            } else {
+                autofillUrl = mTab.getUrl();
+            }
+        }
+
+        mLoginPrompt = new LoginPrompt(activity, messageBody, autofillUrl, this);
         // In case the autofill data arrives before the prompt is created.
 
         if (mAutofillUsername != null && mAutofillPassword != null) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandlerTest.java
index 8e60674..f68def6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandlerTest.java
@@ -42,8 +42,10 @@
 import org.chromium.chrome.test.transit.FreshCtaTransitTestRule;
 import org.chromium.chrome.test.transit.page.WebPageStation;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.components.autofill.AndroidAutofillFeatures;
 import org.chromium.components.browser_ui.http_auth.LoginPrompt;
 import org.chromium.components.browser_ui.http_auth.R;
+import org.chromium.components.browser_ui.widget.text.AlertDialogEditText;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.net.test.EmbeddedTestServer;
 
@@ -211,6 +213,44 @@
                 });
     }
 
+    @Test
+    @MediumTest
+    @EnableFeatures({
+        ChromeFeatureList.ANDROID_AUTOFILL_SUPPORT_FOR_HTTP_AUTH,
+        AndroidAutofillFeatures.ANDROID_AUTOFILL_SUPPORT_FOR_HTTP_AUTH_ORIGIN_NAME
+    })
+    public void testAutofillUrlProvidedWhenFeatureOriginEnabled() throws Exception {
+        AutofillClientProviderUtils.setAutofillAvailabilityToUseForTesting(
+                AndroidAutofillAvailabilityStatus.AVAILABLE);
+
+        ChromeHttpAuthHandler handler = triggerAuth();
+        verifyAuthDialogVisibility(handler, true);
+        CriteriaHelper.pollUiThread(
+                () -> {
+                    Criteria.checkThat(
+                            handler, hasAutofillImportance(View.IMPORTANT_FOR_AUTOFILL_YES));
+                    Criteria.checkThat(handler, hasAutofillUrl(mTestServer.getURL("/")));
+                });
+    }
+
+    @Test
+    @MediumTest
+    @EnableFeatures(ChromeFeatureList.ANDROID_AUTOFILL_SUPPORT_FOR_HTTP_AUTH)
+    @DisableFeatures(AndroidAutofillFeatures.ANDROID_AUTOFILL_SUPPORT_FOR_HTTP_AUTH_ORIGIN_NAME)
+    public void testAutofillUrlProvidedWhenFeatureOriginDisabled() throws Exception {
+        AutofillClientProviderUtils.setAutofillAvailabilityToUseForTesting(
+                AndroidAutofillAvailabilityStatus.AVAILABLE);
+
+        ChromeHttpAuthHandler handler = triggerAuth();
+        verifyAuthDialogVisibility(handler, true);
+        CriteriaHelper.pollUiThread(
+                () -> {
+                    Criteria.checkThat(
+                            handler, hasAutofillImportance(View.IMPORTANT_FOR_AUTOFILL_YES));
+                    Criteria.checkThat(handler, hasAutofillUrl(mTestServer.getURL("/auth-basic")));
+                });
+    }
+
     private static Matcher<ChromeHttpAuthHandler> hasAutofillImportance(int expectedImportance) {
         return new TypeSafeMatcher<ChromeHttpAuthHandler>() {
             @Override
@@ -235,4 +275,26 @@
             }
         };
     }
+
+    private static Matcher<ChromeHttpAuthHandler> hasAutofillUrl(String expectedUrl) {
+        return new TypeSafeMatcher<ChromeHttpAuthHandler>() {
+            @Override
+            protected boolean matchesSafely(ChromeHttpAuthHandler handler) {
+                LoginPrompt prompt = handler.getLoginPromptForTesting();
+                if (prompt == null) return false;
+                AlertDialog dialog = prompt.getDialogForTesting();
+                if (dialog == null) return false;
+                AlertDialogEditText usernameView = dialog.findViewById(R.id.username);
+                if (usernameView == null || usernameView.getUrlForTesting() == null) {
+                    return false;
+                }
+                return usernameView.getUrlForTesting().getSpec().equals(expectedUrl);
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("has autofill url " + expectedUrl);
+            }
+        };
+    }
 }
diff --git a/chrome/browser/ui/android/chrome_http_auth_handler.cc b/chrome/browser/ui/android/chrome_http_auth_handler.cc
index 4dfa15e..e06d529 100644
--- a/chrome/browser/ui/android/chrome_http_auth_handler.cc
+++ b/chrome/browser/ui/android/chrome_http_auth_handler.cc
@@ -14,6 +14,7 @@
 #include "base/android/scoped_java_ref.h"
 #include "base/check.h"
 #include "base/strings/utf_string_conversions.h"
+#include "url/android/gurl_android.h"
 
 // Must come after all headers that specialize FromJniType() / ToJniType().
 #include "chrome/android/chrome_jni_headers/ChromeHttpAuthHandler_jni.h"
@@ -28,10 +29,12 @@
 ChromeHttpAuthHandler::ChromeHttpAuthHandler(
     const std::u16string& authority,
     const std::u16string& explanation,
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential cross-origin credential disclosure via Android Autofill in HTTP Auth prompts

Project Fortify, 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 without the Chrome Security team.

Overview: When an HTTP authentication prompt is triggered on Android, Chrome incorrectly provides the top-level frame’s URL to the Android Autofill framework instead of the challenger’s origin. This allows a cross-origin iframe to trigger a prompt that tricks third-party password managers into suggesting credentials for the top-level site, leading to potential credential theft.

Affected files:

  • chrome/android/java/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandler.java
  • chrome/browser/ui/android/login_handler_android.cc
  • chrome/browser/ui/android/chrome_http_auth_handler.cc
  • components/browser_ui/http_auth/android/java/src/org/chromium/components/browser_ui/http_auth/LoginPrompt.java
  • components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/text/AlertDialogEditText.java

Estimated timestamp from git blame: 2025-09-29

Summary

When a navigation or subresource request triggers an HTTP Basic Authentication challenge (401 Unauthorized), Chrome for Android displays a modal login prompt. To integrate with third-party password managers acting as the system Autofill service (such as Bitwarden or 1Password), Chrome provides a webDomain to help the service identify which credentials to suggest.

Currently, Chrome incorrectly reports the URL of the top-level visible frame (mTab.getUrl()) instead of the origin of the actual challenger (the cross-origin iframe or resource). As a result, third-party password managers will suggest the user’s credentials for the top-level site. If the user accepts the suggestion and clicks “Sign in”, those credentials are sent to the cross-origin challenger in an Authorization: Basic header.

Technical Details

When a subframe navigation triggers a 401 response, HttpAuthCoordinator::Flow::ShowDialog handles the challenge and creates a LoginHandlerAndroid. LoginHandlerAndroid instantiates the native ChromeHttpAuthHandler, which in turn calls the Java ChromeHttpAuthHandler.showDialog method via JNI.

In ChromeHttpAuthHandler.java, the showDialog method calculates an Autofill URL to pass to the UI:

mLoginPrompt =
        new LoginPrompt(
                activity,
                messageBody,
                shouldProvideAutofillUrl() ? mTab.getUrl() : null,
                this);

The method mTab.getUrl() calls TabImpl.getUrl(), which returns getWebContents().getVisibleUrl() (the top-level main frame’s URL). This value is passed down to LoginPrompt.java and eventually to the username/password fields, which are instances of AlertDialogEditText.java.

When the Android Autofill framework requests the view structure, AlertDialogEditText emits this top-level URL:

@Override
public void onProvideAutofillStructure(ViewStructure structure, int flags) {
    if (mUrl != null && !mUrl.isEmpty()) {
        structure.setWebDomain(mUrl.getSpec());
    }
    super.onProvideAutofillStructure(structure, flags);
}

Because the native layer does not forward the raw challenger origin (auth_info.challenger.GetURL()) to the Java layer, the metadata provided to the system autofill service is incorrect, even though the dialog body text accurately names the challenger.

Potential Exploitation Steps

Note: These steps describe a potential attack path; an automated test environment has not executed a working proof of concept.

  1. An attacker compromises an iframe or embeds a cross-origin iframe on a legitimate site (e.g., https://victim.example).
  2. The attacker directs the iframe navigation to an attacker-controlled endpoint: https://attacker.example/auth.
  3. The attacker’s server responds with an HTTP 401 Unauthorized status and a WWW-Authenticate: Basic header.
  4. Chrome displays an HTTP Auth prompt. Because of the bug, Android’s Autofill framework is told the context is victim.example.
  5. The user’s third-party password manager suggests the stored credentials for victim.example.
  6. Trusting the password manager and the context of the top-level site they are visiting, the user selects the suggested credentials and clicks “Sign in”.
  7. Chrome transmits the credentials to https://attacker.example/auth, exposing them to the attacker.

Suggested Fix

The native ChromeHttpAuthHandler should be modified to accept the challenger’s URL (derived from net::AuthChallengeInfo) during initialization and pass it to the Java layer. ChromeHttpAuthHandler.java should then use this challenger URL, rather than mTab.getUrl(), as the argument to the LoginPrompt constructor to ensure the Autofill framework receives the correct domain context.

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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