CVE-2026-113092
Overview
Files Changed
components/password_manager/core/browser/password_autofill_manager_unittest.cccomponents/password_manager/core/browser/password_autofill_manager.cccomponents/password_manager/core/browser/password_autofill_manager.hcomponents/password_manager/core/browser/BUILD.gn
Original Bug Report
Access to cross-site data: on Android Stable a compromised renderer receives in plaintext a password saved for another origin; the cross-domain consent guard is compiled out of that fill path.
Report description
Access to cross-site data: on Android Stable a compromised renderer receives in plaintext a password saved for another origin; the cross-domain consent guard is compiled out of that fill path.
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
Which URL (or repository) have you found the vulnerability in?
The problem
Please describe the technical details of the vulnerability
TL;DR
Shipping on Android Stable today, with no flag, policy or origin trial: 152.0.7977.x (M152) and
153.0.8010.27 (M153). A compromised renderer sends one message; the browser fills a password
saved for a different site into the attacker’s form, in plaintext, on one tap:
AskForValuesToFill(form, field_id, caret_bounds, trigger_source, // attacker's renderer
password_request{ field.trigger_source = kManualFallbackPasswords, // the one changed field
field.element_id = the attacker's password input })
// browser: components/password_manager/core/browser/password_manual_fallback_flow.cc
503 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)
505 if (payload.is_cross_domain) { ShowCrossDomainConfirmationPopup(...); return; }
515 #endif
518 std::move(on_allowed).Run(); // Android lands here: the plaintext goes to the renderer
Confidentiality: the attacker’s origin obtains, in plaintext, passwords the victim saved for other origins, one per tap. Integrity: none.
Attack scenario
Attacker. Whoever serves attacker.example and has compromised the renderer process that
legitimately loaded their own page — not a WebUI process, not a spare renderer, not an extension
process, no flag and no policy.
Victim. Anyone on Chrome for Android using Chrome’s built-in password manager with at least one password saved for some other site.
What the attacker does. Sends one AskForValuesToFill carrying
password_request.field.trigger_source = kManualFallbackPasswords; the message is otherwise shaped
exactly like an ordinary click, and nothing between the wire and the fill re-derives that value.
What the victim does. Taps a password field on the attacker’s page, then taps one chip on the keyboard accessory bar — the ordinary Android gesture for filling a saved password.
Starting position. The compromised renderer can already obtain credentials saved for its own origin through ordinary autofill; what it gains here is credentials saved for other origins.
Victim assumption. The victim is not asked to navigate away, install anything, or accept any prompt — the one prompt Chrome normally shows here, the sheet that names the other site, is precisely what does not appear.
Reproduction
Measured 2026-08-26 on a Pixel 8 Pro (Android 17, build CP2A.260805.005, security patch
2026-08-05), in a dedicated secondary Android user, not signed in, no command-line flags. Every
value below is transcribed in android-manual-fallback-cross-domain-fill.txt.
- Build. Build Chrome for Android from base commit
ce7f1dd1ae9bcb33571f4bd9e0d3a8953c217491withpoc-compromised-renderer.patchapplied — one renderer-side sender, confined to a process whose type isrenderer, emitting the message in step 5. On a public Chromium build also applypoc-local-store.patch, which supplies the credential store Android otherwise lacks. Both patches are described inanalysis.md. Expected: installs asorg.chromium.chrome,versionName 154.0.8024.0; no flags. - Victim data. Save one password for
victim-site.example, nothing forattacker.example. Expected: exactly one row — measuredhttp://a.127-0-0-1.nip.io:8080/|victim-user, plaintextTr1cky-Fern-8823. - Page. Load
attacker.example, an ordinary page with a username input and aninput type="password". Expected: it loads in a normal web renderer, with no prompt of any kind. - Control. Load the identical page at
/control, whose password input carries no attribute for the reproduction hook, and tap its password input. Expected: the field is focused and nothing is offered — measured, the view hierarchy at that moment contains the other origin’s name 0 times and the password 0 times. - The message. Back on the page from step 3, the compromised renderer sends
AskForValuesToFillwithpassword_request.field.trigger_source = kManualFallbackPasswordsand.element_id= the password input’s renderer id; every other field is what an ordinary click would have sent. Expected: the renderer is not killed for a bad message — measured, the browser log matchesbad.?message|killing renderer|terminating renderer0 times and the sandboxed renderers survive. - The list. Tap the password input and read the keyboard accessory bar above the keyboard.
Expected: a chip appears for a credential saved for another site — measured
text="http://a.127-0-0-1.nip.io:8080"next totext="victim-u". Steps 4 and 6 differ in exactly one thing: the suggestion trigger source. - The tap. Tap that chip. ← BUG FIRES HERE
Expected: the “Use your
victim-site.examplepassword?” bottom sheet (android_chrome_strings.grd:928-939) does not appear and the password field is filled — measured, the dump taken after the tap holdstext="Tr1cky-Fern-8823" class="android.widget.EditText" password="true"and contains none of the sheet’s four strings. - The read-out. From the attacker’s page, read the password input’s
value. Expected:Tr1cky-Fern-8823— the plaintext saved forvictim-site.example, read by script running on the attacker’s origin. - Fix arm. Repeat steps 3–8 on the same build with
proposed-patch.diffalso applied. Expected: the chip is still offered, but the tap now shows the sheet naming both origins and the field stays empty until it is accepted — measured; the two builds differ only by that patch.
Root cause
Both the routing and the value come from the renderer, and nothing between the wire and the sink re-derives either:
components/autofill/content/common/mojom/autofill_driver.mojom:113-117—AskForValuesToFill(FormData form, FieldRendererId field_id, gfx.mojom.Rect caret_bounds, AutofillSuggestionTriggerSource trigger_source, PasswordSuggestionRequest? password_request);—password_requestis supplied by the renderer in full.components/autofill/core/common/mojom/autofill_types_mojom_traits.cc:602-609—return data.ReadField(&out->field) && data.ReadFormData(&out->form_data) && ...;— deserialization reads the fields and checks nothing about them.components/autofill/content/browser/content_autofill_driver.cc:303—if (!bad_message::CheckArgs(args...) || ...) { return; }— andcomponents/autofill/content/browser/bad_message.cc:18-25CheckFieldInFormvalidates only the top-levelFieldRendererId, not the one nested inpassword_request.field.components/autofill/core/browser/foundations/browser_autofill_manager.cc:1083-1087—#if !BUILDFLAG(IS_ANDROID) password_delegate->ShowSuggestions(password_request->field); #else password_delegate->ShowKeyboardReplacingSurface(password_request.value()); #endif— the branch that is meant to keep Android off the desktop path.chrome/browser/password_manager/chrome_password_manager_client.cc:619-620and:724—driver->GetPasswordAutofillManager()->ShowSuggestions(request.field);— two Android-only lines that call the desktop entry point anyway.:724is taken whenever Touch-To-Fill has no credential for the current origin (touch_to_fill_password_manager_controller.cc:116-123returns false), which is always true on a domain with nothing saved.components/password_manager/core/browser/password_autofill_manager.cc:578—if (autofill::IsPasswordsAutofillManuallyTriggered(field.trigger_source)) {— and:590-601constructsPasswordManualFallbackFlowand runs it, with no platform condition.components/password_manager/core/browser/password_manual_fallback_flow.cc:309-321—EnsureCrossDomainPasswordUsageGetsConsent(payload, /*on_allowed=*/base::BindOnce(... &PasswordManagerDriver::FillSuggestionById, ..., payload.password, ...));password_manual_fallback_flow.cc:503-504—#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS)— noIS_ANDROID, so the wholeif (payload.is_cross_domain)body is compiled out and control reaches:518std::move(on_allowed).Run();.components/password_manager/content/browser/content_password_manager_driver.cc:325-327—GetPasswordAutofillAgent()->FillPasswordSuggestionById(username_element_id, password_element_id, username, password, suggestion_source);— the plaintext is the IPC payload itself.
Of the four preprocessor conditions in this file, :503-515 is the only one wrapping a check; the
other three wrap a helper definition (:54-90), a different suggestion type’s browser-side action
(:352-362) and a re-auth message string (:467-472), so on an excluded platform those degrade
while :503 fails open. The owning team’s own guard for this decision is
password_client_->ShowCrossDomainConfirmationPopup(...), and the sibling call sites in the same
directory place an #if that does list IS_ANDROID inside if (payload.is_cross_domain) { ... } else { fill }, so an excluded platform fills nothing rather than filling without consent
(password_autofill_manager.cc:410-428, :461-482); two further Android paths do the same job with
the Android sheet (touch_to_fill_password_manager_controller.cc:180-196,
password_accessory_controller_impl.cc:845-856). This fill path is also the widest of the four, not
the narrowest: the offered set is the whole store (password_manual_fallback_flow.cc:121,
password_suggestion_generator.cc:664-667). Full comparison in analysis.md.
The #if was never a decision that Android does not need consent. When the guard was extended to
kPasswordEntry suggestions in https://chromium-review.googlesource.com/c/chromium/src/+/5783088
(merged 2024-08-21), a reviewer asked about this exact line and the answer is on the public record —
password_manual_fallback_flow.cc, patch set 2, line 390 (the #if continuation line):
> reviewer: “why is this needed? Can this code even be triggered outside desktop?” > author: “triggered - not, but compiled with it”
The fall-through therefore rests on a reachability claim, not a policy: the class is believed not to
be triggered outside desktop. chrome_password_manager_client.cc:619 and :724 are the
counterexample — both sit inside that file’s #if BUILDFLAG(IS_ANDROID) block (:546-727) and
both call ShowSuggestions, the desktop entry point, on Android. Chrome’s own threat model states the
boundary this crosses: docs/security/compromised-renderers.md:136-137 — “Compromised renderers
shouldn’t be able to read or write passwords of other sites.”
Line numbers are from origin/main at b8885e8f2ca63ea5439a88bd0fcb2ece7836ce5b. In the guard’s
own file every cited line has the same number on branch-heads/7977 (M152) and branch-heads/8010
(M153), the two branches on Android Stable today. Per-branch table in analysis.md.
Suggested fix
Give the guard the condition its own interface already uses. ShowCrossDomainConfirmationPopup is
declared for every platform except iOS (password_manager_client.h:600-609), so the single call
site that narrows it to four desktop platforms should say !BUILDFLAG(IS_IOS) as well; the sibling
already includes the controller header unconditionally (password_autofill_manager.h:31), so the
header’s include guard can go with it. This adds no UI: that method’s implementation
(chrome/browser/password_manager/chrome_password_manager_client.cc:1506-1526) already has a
complete Android branch that shows the acknowledge sheet and runs the confirmation callback only on
DismissReason::kAccept — the same Android acknowledge sheet the three sibling paths already show.
The complete patch is attached as proposed-patch.diff; the hunk that matters is:
--- a/components/password_manager/core/browser/password_manual_fallback_flow.cc
+++ b/components/password_manager/core/browser/password_manual_fallback_flow.cc
@@ -500,8 +500,7 @@ void PasswordManualFallbackFlow::CancelBiometricReauthIfOngoing() {
void PasswordManualFallbackFlow::EnsureCrossDomainPasswordUsageGetsConsent(
const Suggestion::PasswordSuggestionDetails& payload,
base::OnceClosure on_allowed) {
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || \
- BUILDFLAG(IS_CHROMEOS)
+#if !BUILDFLAG(IS_IOS)
if (payload.is_cross_domain) {
CHECK(payload.display_signon_realm);
cross_domain_confirmation_popup_controller_ =
Prior reports this is not
https://issues.chromium.org/issues/498269651 — public, Fixed, CVE-2026-6312, “Cross-domain password
leak via manual-fallback preview in PasswordManualFallbackFlow”; that is the preview path
(DidSelectSuggestion), and its shipped fix refuses even a masked preview on every platform,
which is why Android today refuses to show the credential while still handing over its plaintext.
The in-flight hardening in this file is a different check: kPasswordManualFallbackSecurityChecks
(password_autofill_manager.cc:578-589, password_features.cc:178-179) is
FEATURE_DISABLED_BY_DEFAULT, and its content is frame eligibility —
HasValidURL() and IsRenderFrameHostSupported() — both of which an ordinary top-level frame
serving the attacker’s own origin satisfies. It says nothing about whose credential is released.
Impact analysis
A compromised renderer walks away with the victim’s saved password for a different site, in plaintext.
Confidentiality. One tap yields one complete plaintext password belonging to another origin,
delivered as the IPC payload itself — autofill_agent.mojom:192-197
FillPasswordSuggestionById(FieldRendererId, FieldRendererId, String16 username, String16 password, AutofillSuggestionTriggerSource)
— so the renderer does not have to read it back out of the DOM at all. Measured: the 16-character
Tr1cky-Fern-8823, saved for one origin, read by script running on another. The offered set is the
entire saved-password store: on a newly registered attacker domain every stored credential is
marked cross-domain (password_suggestion_generator.cc:664-667).
Integrity. None. The path reads stored credentials; it writes nothing to the victim’s profile.
Who is affected / Reach. Chrome for Android users of the built-in password manager with at least
one saved password. No flag, origin trial, policy or permission prompt: the sources are compiled
unconditionally (components/password_manager/core/browser/BUILD.gn:127-128) and the guard is
unchanged on both branches serving Android Stable today, branch-heads/7977 (152.0.7977.x) and
branch-heads/8010 (153.0.8010.27). The attacker needs a compromised renderer for a page they
legitimately serve, plus one tap on the keyboard accessory — the ordinary way to fill a saved
password on Android.
Bound. One tap yields one credential, and the list is not enumerable behind the user’s back: the
accessory’s hover-preview path is behind kAutofillAndroidKeyboardAccessoryHoverPreview, disabled
by default (autofill_features.cc:523-524), and previewing a cross-domain credential is refused on
every platform (password_manual_fallback_flow.cc:231-238, :251-260). Android only: iOS
hard-codes trigger_source = kiOS and password_request = std::nullopt at the entry point
(autofill_driver_ios.mm:519-537), and a user on a third-party Android autofill provider gets a
different client (autofill_client_provider.cc:59,74). Biometric re-auth is not a substitute and is
not always present: on Android it requires kBiometricTouchToFill (password_features.cc:60,
disabled by default) and the user’s own pref (chrome_password_manager_client.cc:756-759), and
where it is on, the message passed to AuthenticateWithMessage is left empty on Android
(password_manual_fallback_flow.cc:462-473) — the prompt confirms the device owner without naming
whose credential is about to be released.
Severity. Chrome’s own documents state the boundary this crosses. content/SECURITY.md:82-83
lists, among High-Priority Vulnerabilities under Site Isolation Bypasses, “Access to Cross-Site
Data: Allowing a compromised renderer to access cross-site data, such as cookies or passwords”, and
docs/security/compromised-renderers.md:136-137 states “Compromised renderers shouldn’t be able to
read or write passwords of other sites” — a document whose own preamble (:21-25) says newly
discovered holes in that protection are considered security bugs. The decided precedent is the same
policy in the same file, one switch arm away: https://issues.chromium.org/issues/498269651, CVE-2026-6312, fixed and
merged to M147, M148 and the M144 LTS branch; that report was the masked preview of a cross-domain
credential, and this one is its plaintext. The rubric’s mitigating factors do not apply: one tap on
a password chip is the typical Android filling gesture rather than implausible interaction
(severity-guidelines.md:24-26), and the rubric states that applying only to a particular group of
users is not mitigating (:28-31).
The cause
What version of Chrome have you found the security issue in?
Google Chrome for Android 154.0.8024.0, a build made for this report from chromium/src ce7f1dd1ae9bcb33571f4bd9e0d3a8953c217491 with the two attached patches, measured 2026-08-26 on a Pixel 8 Pro running Android 17 (build CP2A.260805.005, security patch 2026-08-05); the supporting measurements on stock released binaries are Google Chrome for Android 151.0.7922.169 (Pixel 8 Pro) and 151.0.7922.171 (Pixel 8), measured 2026-08-25 on that same Android 17 build. Android Stable on 2026-09-04 is 152.0.7977.77 and 152.0.7977.82 (M152) and 153.0.8010.27 (M153, promoted 2026-09-03); the guard is present unchanged on both shipping branches, branch-heads/7977 and branch-heads/8010.
Is the security issue related to a crash?
No, it is not related to a crash.
Choose the type of vulnerability
Site Isolation Bypass
How would you like to be publicly acknowledged for your report?
OGINOME Tomohito
- http://a.127-0-0-1.nip.io:8080
- http://a.127-0-0-1.nip.io:8080/|victim-user
- https://bughunters.google.com/about/rules/5745167867576320/chrome-vulnerability-reward-program-rules
- https://chromium-review.googlesource.com/c/chromium/src/+/5783088
- https://chromium.googlesource.com/chromium/src/+/HEAD/components/password_manager/core/browser/password_manual_fallback_flow.cc