Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in Autofill
DescriptionInsufficient policy enforcement in Autofill
ComponentAutofill
Bug ClassLogic Error
Tracker508260619
Fix commitbd6ad46ebdd8 (chromium/src) +54/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm
modified

Files Changed

  • components/autofill/ios/browser/autofill_util.mm
  • ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm
  • ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
From bd6ad46ebdd81517dfc6c15f8492511a6ff568f4 Mon Sep 17 00:00:00 2001
From: Leo Zhao <[email protected]>
Date: Wed, 20 May 2026 13:58:26 -0700
Subject: [PATCH] [iOS] Eliminate potential security risks for "Autofill form" button

When "AUtofill form" was tapped, it did not check whether the connection was HTTPS. This CL adds a check before autofilling credentials to ensure    the filling is done on a form that is secure. This is part 1 of the Suggested Fix from the reported issue.

The issue also suggests a fix to "Enforce Re-authentication". After reviewing the process, we believe the conditions under which it can be exploited are very unlikely to be met, and therefore do not justify adding an extra layer of inconvenience to a common use case.

Bug: 508260619
Change-Id: I19df5a4811772c316269c9fd6e81a58b7e610a1f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7810331
Commit-Queue: Leo Zhao <[email protected]>
Reviewed-by: Tommy Martino <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1633817}
---

diff --git a/components/autofill/ios/browser/autofill_util.mm b/components/autofill/ios/browser/autofill_util.mm
index 8476e67..3588dca 100644
--- a/components/autofill/ios/browser/autofill_util.mm
+++ b/components/autofill/ios/browser/autofill_util.mm
@@ -83,8 +83,13 @@
     return false;
   }
 
+  const GURL& url = nav_item->GetURL();
+  if (net::IsLocalhost(url)) {
+    return true;
+  }
+
   const web::SSLStatus& ssl = nav_item->GetSSL();
-  return nav_item->GetURL().SchemeIsCryptographic() && ssl.certificate &&
+  return url.SchemeIsCryptographic() && ssl.certificate &&
          !net::IsCertStatusError(ssl.cert_status);
 }
 
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm
index e4b14d4..97e774e 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm
@@ -197,6 +197,10 @@
 
 - (void)autofillFormWithCredential:(ManualFillCredential*)credential
                       shouldReauth:(BOOL)shouldReauth {
+  if (![self canUserInjectInPasswordField:NO requiresHTTPS:YES]) {
+    return;
+  }
+
   if (shouldReauth && [self.reauthenticationModule canAttemptReauth]) {
     NSString* reason = l10n_util::GetNSString(IDS_IOS_AUTOFILL_REAUTH_REASON);
     __weak __typeof(self) weakSelf = self;
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
index 1dd624be..9d1de37 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
@@ -4,6 +4,7 @@
 
 #import "base/i18n/message_formatter.h"
 #import "base/ios/ios_util.h"
+#import "base/path_service.h"
 #import "base/strings/sys_string_conversions.h"
 #import "base/strings/utf_string_conversions.h"
 #import "base/test/ios/wait_util.h"
@@ -34,6 +35,7 @@
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
 #import "ios/testing/earl_grey/earl_grey_test.h"
 #import "ios/web/public/test/element_selector.h"
+#import "net/test/embedded_test_server/default_handlers.h"
 #import "net/test/embedded_test_server/embedded_test_server.h"
 #import "ui/base/l10n/l10n_util.h"
 #import "ui/base/l10n/l10n_util_mac.h"
@@ -254,7 +256,9 @@
 }  // namespace
 
 // Integration Tests for Mannual Fallback Passwords View Controller.
-@interface PasswordViewControllerTestCase : ChromeTestCase
+@interface PasswordViewControllerTestCase : ChromeTestCase {
+  std::unique_ptr<net::test_server::EmbeddedTestServer> _HTTPSServer;
+}
 
 // URL of the current page.
 @property(assign) GURL URL;
@@ -265,6 +269,7 @@
 
 - (void)setUp {
   [super setUp];
+  _HTTPSServer = nil;
   GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
   self.URL = self.testServer->GetURL(kFormHTMLFile);
   [self loadLoginPage];
@@ -293,6 +298,7 @@
       [MetricsAppInterface releaseUserActionTester]);
   chrome_test_util::GREYAssertErrorNil(
       [MetricsAppInterface releaseHistogramTester]);
+  _HTTPSServer = nil;
   [super tearDownHelper];
 }
 
@@ -320,6 +326,34 @@
   [ChromeEarlGrey waitForWebStateContainingText:"hello!"];
 }
 
+// Loads the page over HTTPS and proceeds past the SSL warning interstitial if
+// present.
+- (void)loadHTTPSLoginPage {
+  [ChromeEarlGrey loadURL:self.URL];
+
+  // Check if the SSL warning page is displayed by verifying if the
+  // "details-button" element exists on the page.
+  base::Value result = [ChromeEarlGrey
+      evaluateJavaScript:@"document.getElementById('details-button') !== null"];
+  if (result.is_bool() && result.GetBool()) {
+    [ChromeEarlGrey tapWebStateElementWithID:@"details-button"];
+    [ChromeEarlGrey tapWebStateElementWithID:@"proceed-link"];
+  }
+
+  [ChromeEarlGrey waitForWebStateContainingText:"hello!"];
+}
+
+// Starts the dedicated HTTPS test server.
+- (void)startHTTPSServer {
+  _HTTPSServer = std::make_unique<net::test_server::EmbeddedTestServer>(
+      net::test_server::EmbeddedTestServer::TYPE_HTTPS);
+  _HTTPSServer->ServeFilesFromDirectory(
+      base::PathService::CheckedGet(base::DIR_ASSETS)
+          .AppendASCII("ios/testing/data/http_server_files/"));
+  RegisterDefaultHandlers(_HTTPSServer.get());
+  GREYAssertTrue(_HTTPSServer->Start(), @"HTTPS Test server failed to start.");
+}
+
 // Opens the "Other Passwords" screen.
 - (void)openOtherPasswords {
   // Bring up the keyboard.
@@ -1172,11 +1206,14 @@
   // Disable the credential bottom sheet.
   [CredentialSuggestionBottomSheetAppInterface disableBottomSheet];
 
+  [self startHTTPSServer];
+  self.URL = _HTTPSServer->GetURL(kFormHTMLFile);
+
   // Save password for site.
   NSString* URLString = base::SysUTF8ToNSString(self.URL.spec());
   [AutofillAppInterface savePasswordFormForURLSpec:URLString];
 
-  [self loadLoginPage];
+  [self loadHTTPSLoginPage];
 
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
@@ -1283,11 +1320,15 @@
   // Disable the credential bottom sheet.
   [CredentialSuggestionBottomSheetAppInterface disableBottomSheet];
 
+  [self startHTTPSServer];
+
+  self.URL = _HTTPSServer->GetURL(kFormHTMLFile);
+
   // Save a credential with a backup password for the current site.
   NSString* URLString = base::SysUTF8ToNSString(self.URL.spec());
   [AutofillAppInterface savePasswordFormWithBackupForURLSpec:URLString];
 
-  [self loadLoginPage];
+  [self loadHTTPSLoginPage];
 
   // Bring up the keyboard.
   [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
Loading diff…

Original Bug Report

reported by [email protected]

Potential security bypasses in iOS manual password filler's Autofill Form button

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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: The ‘Autofill Form’ button in the iOS manual password filler bypasses HTTP warnings and re-authentication (Face ID/Touch ID) requirements. This allows silent credential injection over insecure connections and potential unauthorized access to credentials after the authentication grace period has expired.

Affected files:

  • ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_password_cell.mm
  • ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_injection_handler.mm

Estimated timestamp from git blame: 2024-07-24

Summary

The ManualFillPasswordCell in Chrome for iOS contains two potential security logic flaws in its “Autofill Form” button handler (onAutofillFormButtonTapped). This button fails to enforce the same security constraints that are applied when a user manually taps individual credential chips (e.g., username or password chips).

Specifically, it allows credential injection over insecure HTTP connections without displaying a security warning, and it bypasses re-authentication requirements when used from the “All Passwords” context.

Vulnerability Details

1. Protocol Downgrade / HTTP Bypass

In ManualFillPasswordCell.mm, when a user taps an individual password chip, the userDidTapPasswordButton: method is triggered. This method explicitly checks if injection is safe via [self.contentInjector canUserInjectInPasswordField:YES requiresHTTPS:YES]. If the target page is not using HTTPS, a security warning (IDS_IOS_MANUAL_FALLBACK_NOT_SECURE_GENERIC_BODY) is displayed via the securityAlertHandler, and the injection is aborted.

However, the “Autofill Form” button handler, onAutofillFormButtonTapped, directly calls autofillFormWithCredential:shouldReauth:. Neither the cell handler nor the downstream implementation in ManualFillInjectionHandler.mm calls canUserInjectInPasswordField:requiresHTTPS: or performs any other HTTPS check.

Consequently, credentials can be silently injected into insecure HTTP pages via the “Autofill Form” button, bypassing the intentional security warning intended for manual fallback scenarios.

2. Re-authentication (Face ID/Touch ID) Bypass

Manual fill operations are typically protected by re-authentication. When a user selects a credential chip, the ReauthenticationModule is invoked, allowing for a 60-second grace period (canReusePreviousAuth:YES). If this grace period has expired, the user is prompted to re-authenticate (e.g., via Face ID).

The “Autofill Form” button handler bypasses this security measure when the cell is displayed in the “All Passwords” context. In manual_fill_credentials_mediator.mm, when the ManualFillAllPasswordCoordinator displays the full list of saved passwords, the isFromAllPasswordsContext flag is set to YES.

In this scenario, onAutofillFormButtonTapped passes shouldReauth:NO to the injection handler:

[self.contentInjector autofillFormWithCredential:self.credential
                                    shouldReauth:!_fromAllPasswordsContext];

In ManualFillInjectionHandler.mm, passing shouldReauth:NO causes the ReauthenticationModule to be skipped entirely:

  if (shouldReauth && [self.reauthenticationModule canAttemptReauth]) {
      // ... reauth logic ...
  } else {
    [self fillFormWithCredential:credential];
  }

While tapping an individual password chip in the “All Passwords” list correctly triggers a Face ID prompt once the 60-second grace period expires (via userDidPickContent:), the “Autofill Form” button remains functional indefinitely without further authentication.

Potential Attack Scenario

(Note: These are suggested steps based on static analysis; our tooling agent cannot execute code to confirm.)

  1. An attacker creates a malicious webpage served over HTTP containing a login form and data-exfiltration JavaScript.
  2. The attacker gains physical access to the victim’s unlocked iOS device.
  3. The attacker navigates to their HTTP webpage, focuses a password field, and taps the key icon to open the manual fill keyboard accessory.
  4. The attacker taps “Other Passwords…” to open the “All Passwords” view. This prompts for Face ID/Passcode. The attacker either knows the passcode or points the device at the nearby victim to pass this initial check.
  5. The attacker leaves the device on the “All Passwords” screen and waits for more than 60 seconds to expire the ReauthenticationModule’s grace period.
  6. The attacker taps the “Autofill Form” button on a high-value credential (e.g., a bank account).
  7. Because shouldReauth is passed as NO, no re-authentication prompt is shown. Because the HTTPS check is missing, no security warning is shown.
  8. The plaintext credentials are automatically injected into the HTTP page and exfiltrated by the attacker’s script.

Suggested Fix

  1. Enforce HTTPS Check: In ManualFillInjectionHandler.mm, update autofillFormWithCredential:shouldReauth: to call canUserInjectInPasswordField:YES requiresHTTPS:YES before proceeding with the fill operation. If the check fails, the method should return early.
  2. Enforce Re-authentication: Remove the !_fromAllPasswordsContext logic from onAutofillFormButtonTapped. The decision to skip re-authentication should not be based on whether the view is the “All Passwords” list, as the user could dwell on this screen longer than the 60-second grace period. shouldReauth should always be YES when dealing with password injection, or the grace period check should be relied upon exclusively.

Evaluated with Chrome root at commit: cc901875d53bf4e4fe0e01f02843871da4106e70


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