Low watch ✔︎ verified CVSS 6.5 chrome Authorization Error 🔧 Commit mapped 🧬 Authorization check exists but is not enforced on every path

Overview

Low
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in WebView
DescriptionMissing authorization in WebView
ComponentWebView
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety — upheld by independent review by AI
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.
Independent review by AIConfirmed
Reviewer notes: Omits a precondition: the test requires the embedding app to proceed past the SSL error (setAllowSslError) and to enable WebAuthn (setWebauthnSupport). The fix also sits behind a kill-switch feature that is on by default. The impact text is truncated.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker507219126
Fix commite84aaf8b1e01 (chromium/src) +143/-3
CWECWE-862
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-04-28 (per vendor advisory)
Fix landed2026-08-05 (public git)
Disclosed2026-09-08
Patch gap34 days — fix public in source before the advisory

Background

WebAuthn
the Web Authentication API (navigator.credentials.get) that lets a page request assertions from platform/roaming authenticators (e.g. FIDO2), normally gated on the page being served over a secure, trustworthy connection.
`IsSecurityLevelAcceptableForWebAuthn`
a content::ContentBrowserClient hook the browser consults before servicing a WebAuthn request; the embedder returns true to allow the request or false to block it.
`AwContentBrowserClient`
Android WebView’s embedder implementation of content::ContentBrowserClient, which prior to this patch inherited the base-class default for this hook.
`security_state::SecurityLevel`
an enum describing the computed security posture of the visible page (e.g. SECURE), derived from security_state::GetVisibleSecurityState, that distinguishes a properly-authenticated TLS connection from one with certificate/SSL errors.

Root Cause Analysis

As the commit message states, AwContentBrowserClient inherited ContentBrowserClient::IsSecurityLevelAcceptableForWebAuthn, which “unconditionally returns true,” so WebView permitted WebAuthn requests without considering TLS errors. The violated invariant is that a WebAuthn ceremony should only proceed from a page whose connection is actually secure; WebView never checked the visible security level, so a page loaded over a connection with SSL/certificate errors was treated identically to a genuinely secure one.

The fix overrides the hook in AwContentBrowserClient to gate the request: when the kWebViewWebAuthnRequiresSecureOrigin feature is enabled (default on), it resolves the page’s content::WebContents, allows net::IsLocalhost origins, and otherwise obtains the security_state::GetVisibleSecurityState, returning true only when security_state::GetSecurityLevel is SECURE (or when the kIgnoreCertificateErrors command-line switch is set). This tightens behavior: requests on pages with SSL errors, or where the security state cannot be determined (null web_contents or null state), now return false and are blocked.

The added test testWebAuthnBlockedOnSslError confirms the intended behavior by loading a page from an HTTPS server with ServerCertificate.CERT_MISMATCHED_NAME, allowing the SSL error, and asserting the FIDO2 API is never invoked and the JavaScript promise rejects with a NotAllowedError mentioning certificate errors.

Key insight
The single core mistake is a missing authorization check: WebView accepted the base class’s unconditional return true for IsSecurityLevelAcceptableForWebAuthn, so WebAuthn was never denied on insecure/SSL-error pages. The fix supplies a real override that consults security_state::GetSecurityLevel and returns false unless the page is SECURE (or localhost / cert errors explicitly ignored).

Attack Path

  1. Serve a page with an SSL error to a WebView An attacker in a network position (or hosting a site with an invalid/mismatched certificate) gets a WebAuthn-using page loaded in an app’s WebView where the SSL error is not fatal; the test models this with ServerCertificate.CERT_MISMATCHED_NAME and mContentsClient.setAllowSslError(true).
  2. Trigger a WebAuthn request from the insecure page JavaScript calls navigator.credentials.get({ publicKey: { ... rpId: 'a.test' } }), as the PoC does, reaching WebView’s WebAuthn handling.
  3. Bypass the security-level gate (pre-patch) Because AwContentBrowserClient inherited the base IsSecurityLevelAcceptableForWebAuthn that unconditionally returns true, the request is deemed acceptable despite the SSL error and proceeds to invoke the FIDO2 API (invokeFido2GetAssertion) instead of being rejected.
  4. Abuse of the assertion on an unauthenticated connection (inferred) With the ceremony allowed over a connection the browser could not authenticate, an attacker could attempt to obtain or relay a WebAuthn assertion in a context the security model intends to forbid; the specific downstream credential misuse is not demonstrated by the diff. (inferred)

Impact Assessment

An attacker gains the ability to have WebAuthn/FIDO2 ceremonies serviced in Android WebView from pages served over connections with SSL/certificate errors, i.e. connections that are not authenticated, running in the browser/embedder process context of the hosting app’s WebView. The precondition is that a page with an SSL error is loaded (e.g. via a MITM position or a bad-certificate site) and the app’s WebView permits WebAuthn; the advisory classifies this as “Missing authorization in WebView.” Any escalation beyond invoking the authenticator on an insecure origin — such as actual credential theft or account compromise — is not shown by the patch and is (inferred); the diff itself only demonstrates that the request was allowed rather than blocked.

Changed Functions

FunctionChangeNotes
if
android_webview/browser/aw_content_browser_client.cc
modified
android_library
android_webview/test/BUILD.gn
modified

Files Changed

  • android_webview/javatests/DEPS
  • android_webview/common/aw_features.h
  • android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
  • android_webview/browser/DEPS
  • android_webview/common/aw_features.cc
  • android_webview/browser/aw_content_browser_client.cc
  • android_webview/browser/BUILD.gn
  • android_webview/test/BUILD.gn
  • android_webview/browser/aw_content_browser_client.h

Audit Directions

  • Unoverridden `ContentBrowserClient` security hooks in embedders
    Audit other AwContentBrowserClient (and other embedder) methods that fall back to permissive base-class defaults (returning true/allowing) for security-relevant decisions, since WebView may silently inherit the wrong policy.
  • Security-level / TLS-error gating on privileged web APIs
    Look for other powerful capabilities (credential, payment, permission, or sensor APIs) that are exposed in WebView without consulting security_state::GetSecurityLevel or otherwise checking for SSL/certificate errors before proceeding.
  • Feature-flag-gated security checks that can be disabled
    Review checks guarded by a BASE_FEATURE (like kWebViewWebAuthnRequiresSecureOrigin) where flipping the flag off restores insecure behavior, ensuring the default state and any experiment/killswitch configuration keep the protection on.
From e84aaf8b1e016ff23ddc3e4d6e9e5d5e7ec6f35f Mon Sep 17 00:00:00 2001
From: Nate Fischer <[email protected]>
Date: Tue, 04 Aug 2026 23:18:51 -0700
Subject: [PATCH] Block WebAuthn requests on pages with SSL errors

AwContentBrowserClient inherits
ContentBrowserClient::IsSecurityLevelAcceptableForWebAuthn which
unconditionally returns true, allowing WebAuthn requests to proceed
without considering TLS errors.

This CL overrides IsSecurityLevelAcceptableForWebAuthn in
AwContentBrowserClient to check the security level similar to what
Chrome browser does. This adds an end-to-end test verifying WebAuthn is
blocked on sites with SSL errors.

TAG=agy
CONV=c1aad737-b624-4e3e-8c12-a5dd34c64316

Fixed: 507219126
Test: run_webview_instrumentation_test_apk -f WebAuthnTest.*
Change-Id: I977fe7ec62450f2ab6ae93458a2bd6d62213315e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8178688
Auto-Submit: Nate Fischer <[email protected]>
Reviewed-by: Rupert Wiser <[email protected]>
Commit-Queue: Rupert Wiser <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1673924}
---
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index 13dabf1a..eadf3d5 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -81,6 +81,7 @@
 #include "components/embedder_support/user_agent_utils.h"
 #include "components/heap_profiling/in_process/heap_profiler_controller.h"
 #include "components/navigation_interception/intercept_navigation_delegate.h"
+#include "components/network_session_configurator/common/network_switches.h"
 #include "components/page_load_metrics/browser/metrics_navigation_throttle.h"
 #include "components/page_load_metrics/browser/metrics_web_contents_observer.h"
 #include "components/policy/content/policy_blocklist_navigation_throttle.h"
@@ -93,6 +94,8 @@
 #include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/hashprefix_realtime/hash_realtime_utils.h"
 #include "components/sampling_profiler/process_type.h"
+#include "components/security_state/content/content_utils.h"
+#include "components/security_state/core/security_state.h"
 #include "components/url_matcher/url_matcher.h"
 #include "components/url_matcher/url_util.h"
 #include "components/user_prefs/user_prefs.h"
@@ -125,6 +128,7 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "net/android/network_library.h"
 #include "net/base/features.h"
+#include "net/base/url_util.h"
 #include "net/cookies/cookie_setting_override.h"
 #include "net/cookies/site_for_cookies.h"
 #include "net/http/http_util.h"
@@ -583,6 +587,32 @@
   }
 }
 
+bool AwContentBrowserClient::IsSecurityLevelAcceptableForWebAuthn(
+    content::RenderFrameHost* rfh,
+    const url::Origin& caller_origin) {
+  if (!base::FeatureList::IsEnabled(
+          android_webview::features::kWebViewWebAuthnRequiresSecureOrigin)) {
+    return true;
+  }
+  content::WebContents* web_contents =
+      content::WebContents::FromRenderFrameHost(rfh);
+  if (!web_contents) {
+    return false;
+  }
+  if (net::IsLocalhost(caller_origin.GetURL())) {
+    return true;
+  }
+  auto state = security_state::GetVisibleSecurityState(web_contents);
+  if (!state) {
+    return false;
+  }
+  security_state::SecurityLevel security_level =
+      security_state::GetSecurityLevel(*state);
+  return security_level == security_state::SecurityLevel::SECURE ||
+         base::CommandLine::ForCurrentProcess()->HasSwitch(
+             switches::kIgnoreCertificateErrors);
+}
+
 base::OnceClosure AwContentBrowserClient::SelectClientCertificate(
     content::BrowserContext* browser_context,
     int process_id,
diff --git a/android_webview/browser/aw_content_browser_client.h b/android_webview/browser/aw_content_browser_client.h
index f18b542f..a2adea2 100644
--- a/android_webview/browser/aw_content_browser_client.h
+++ b/android_webview/browser/aw_content_browser_client.h
@@ -108,6 +108,9 @@
       bool strict_enforcement,
       base::OnceCallback<void(content::CertificateRequestResultType)> callback)
       override;
+  bool IsSecurityLevelAcceptableForWebAuthn(
+      content::RenderFrameHost* rfh,
+      const url::Origin& caller_origin) override;
   base::OnceClosure SelectClientCertificate(
       content::BrowserContext* browser_context,
       int process_id,
diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc
index cdcdcd6..0433b98 100644
--- a/android_webview/common/aw_features.cc
+++ b/android_webview/common/aw_features.cc
@@ -406,6 +406,10 @@
 // browser process startup in WebView.
 BASE_FEATURE(kWebViewWarmupNetworkService, base::FEATURE_DISABLED_BY_DEFAULT);
 
+// When enabled, WebAuthn requests are blocked on pages with TLS/SSL errors.
+BASE_FEATURE(kWebViewWebAuthnRequiresSecureOrigin,
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Kill switch for reporting web performance metrics.
 BASE_FEATURE(kWebViewWebPerformanceMetricsReporting,
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h
index bf614a6..836493d 100644
--- a/android_webview/common/aw_features.h
+++ b/android_webview/common/aw_features.h
@@ -99,6 +99,7 @@
 BASE_DECLARE_FEATURE(kWebViewUseWVLESForLayeredStudy);
 BASE_DECLARE_FEATURE(kWebViewVizDirectCompositorThreadIpcFrameSinkManager);
 BASE_DECLARE_FEATURE(kWebViewWarmupNetworkService);
+BASE_DECLARE_FEATURE(kWebViewWebAuthnRequiresSecureOrigin);
 BASE_DECLARE_FEATURE(kWebViewWebPerformanceMetricsReporting);
 // Don't add new features to the end! Insert them in alphabetical order to
 // reduce conflicts.
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
index 3477bc0..2448307 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
@@ -4,8 +4,17 @@
 
 package org.chromium.android_webview.test;
 
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ResultReceiver;
+
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -22,8 +31,14 @@
 import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.base.test.util.HistogramWatcher;
+import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
+import org.chromium.components.webauthn.AuthenticationContextProvider;
+import org.chromium.components.webauthn.Fido2ApiCallHelper;
 import org.chromium.components.webauthn.WebauthnMode;
+import org.chromium.components.webauthn.cred_man.CredManSupportProvider;
 import org.chromium.content_public.common.ContentSwitches;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.ServerCertificate;
 import org.chromium.net.test.util.TestWebServer;
 import org.chromium.url.GURL;
 
@@ -32,6 +47,23 @@
 @CommandLineFlags.Add({ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
 @Batch(Batch.PER_CLASS)
 public class WebAuthnTest {
+    private static class TestFido2ApiCallHelper extends Fido2ApiCallHelper {
+        public boolean mGetAssertionCalled;
+
+        @Override
+        public void invokeFido2GetAssertion(
+                AuthenticationContextProvider authenticationContextProvider,
+                PublicKeyCredentialRequestOptions options,
+                Uri uri,
+                byte[] clientDataHash,
+                ResultReceiver resultReceiver,
+                OnSuccessListener<PendingIntent> successCallback,
+                OnFailureListener failureCallback) {
+            mGetAssertionCalled = true;
+            failureCallback.onFailure(new Exception("MOCK_FIDO2_API_INVOKED"));
+        }
+    }
+
     @Rule public AwActivityTestRule mActivityTestRule = new AwActivityTestRule();
 
     private TestAwContentsClient mContentsClient;
@@ -158,6 +190,69 @@
         histogramWatcher.assertExpected();
     }
 
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testWebAuthnBlockedOnSslError() throws Throwable {
+        CredManSupportProvider.setupForTesting(Build.VERSION_CODES.TIRAMISU, false);
+        TestFido2ApiCallHelper testHelper = new TestFido2ApiCallHelper();
+        Fido2ApiCallHelper.overrideInstanceForTesting(testHelper);
+
+        EmbeddedTestServer testServer =
+                EmbeddedTestServer.createAndStartHTTPSServer(
+                        InstrumentationRegistry.getInstrumentation().getContext(),
+                        ServerCertificate.CERT_MISMATCHED_NAME);
+        try {
+            mAwSettings.setJavaScriptEnabled(true);
+            mAwSettings.setWebauthnSupport(WebauthnMode.APP);
+            mContentsClient.setAllowSslError(true);
+
+            final String pageUrl =
+                    testServer.getURLWithHostName(
+                            "a.test", "/android_webview/test/data/hello_world.html");
+            mActivityTestRule.loadUrlSync(
+                    mAwContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
+
+            mActivityTestRule.executeJavaScriptAndWaitForResult(
+                    mAwContents,
+                    mContentsClient,
+                    """
+                    window.webauthnResult = 'PENDING';
+                    navigator.credentials.get({
+                      publicKey: {
+                        challenge: new Uint8Array([1, 2, 3, 4]),
+                        timeout: 500,
+                        rpId: 'a.test'
+                      }
+                    }).then(
+                      () => { window.webauthnResult = 'SUCCESS'; },
+                      (e) => { window.webauthnResult = e.name + ': ' + e.message; }
+                    );
+                    """);
+
+            AwActivityTestRule.pollInstrumentationThread(
+                    () -> {
+                        String val =
+                                mActivityTestRule.executeJavaScriptAndWaitForResult(
+                                        mAwContents, mContentsClient, "window.webauthnResult");
+                        return !"\"PENDING\"".equals(val) && !"null".equals(val);
+                    });
+
+            String result =
+                    mActivityTestRule.executeJavaScriptAndWaitForResult(
+                            mAwContents, mContentsClient, "window.webauthnResult");
+
+            Assert.assertFalse(
+                    "FIDO2 API should not be invoked for pages with SSL errors",
+                    testHelper.mGetAssertionCalled);
+            Assert.assertTrue(
+                    "WebAuthn should be blocked due to SSL error, but got: " + result,
+                    result.contains("NotAllowedError") && result.contains("certificate errors"));
+        } finally {
+            testServer.stopAndDestroyServer();
+        }
+    }
+
     private static boolean isSecureDomain(GURL url) {
         if ("https".equals(url.getScheme())) {
             return true;
diff --git a/android_webview/browser/BUILD.gn b/android_webview/browser/BUILD.gn
index 09c77123..cec21723b 100644
--- a/android_webview/browser/BUILD.gn
+++ b/android_webview/browser/BUILD.gn
@@ -297,6 +297,7 @@
     "//components/minidump_uploader",
     "//components/navigation_interception",
     "//components/network_hints/browser",
+    "//components/network_session_configurator/common",
     "//components/optimization_guide/core:bloomfilter",
     "//components/origin_matcher",
     "//components/origin_trials:browser",
@@ -332,6 +333,8 @@
     "//components/sampling_profiler:profile_params",
     "//components/security_interstitials/content:security_interstitial_page",
     "//components/security_interstitials/core",
+    "//components/security_state/content",
+    "//components/security_state/core",
     "//components/sensitive_content",
     "//components/services/heap_profiling/public/cpp",
     "//components/spellcheck:buildflags",
diff --git a/android_webview/browser/DEPS b/android_webview/browser/DEPS
index 8f171c20..1a571c2 100644
--- a/android_webview/browser/DEPS
+++ b/android_webview/browser/DEPS
@@ -52,6 +52,8 @@
   "+components/safe_browsing/core/browser",
   "+components/safe_browsing/core/common",
   "+components/security_interstitials",
+  "+components/security_state/content",
+  "+components/security_state/core",
   "+components/sensitive_content",
   "+components/services/heap_profiling",
   "+components/spellcheck/browser",
diff --git a/android_webview/javatests/DEPS b/android_webview/javatests/DEPS
index 15d6e6f..43dac3b 100644
--- a/android_webview/javatests/DEPS
+++ b/android_webview/javatests/DEPS
@@ -9,6 +9,7 @@
   "+components/policy/android/javatests",
   "+components/safe_browsing/android/java",
   "+components/variations/android/java",
+  "+components/webauthn/android/java/src/org/chromium/components/webauthn",
 
   "-content/public/android/java",
   "+content/public/android/java/src/org/chromium/content_public",
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 1d748689..69b320f 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -276,9 +276,8 @@
 android_library("webview_compose_javatests") {
   testonly = true
   enable_compose = true
-  sources = [
-    "../javatests/src/org/chromium/android_webview/test/AwComposeTest.kt",
-  ]
+  sources =
+      [ "../javatests/src/org/chromium/android_webview/test/AwComposeTest.kt" ]
   deps = [
     ":base_webview_instrumentation_java",
     ":base_webview_instrumentation_test_java",
@@ -309,6 +308,7 @@
 
   deps = [
     ":webview_compose_javatests",
+    "$google_play_services_package:google_play_services_tasks_java",
     "//android_webview:common_aidl_java",
     "//android_webview:common_origin_trials_java",
     "//android_webview:common_variations_java",
@@ -384,6 +384,7 @@
     "//third_party/androidx_javascriptengine:javascriptengine_java",
     "//third_party/blink/public:blink_headers_java",
     "//third_party/blink/public/common:common_java",
+    "//third_party/blink/public/mojom:authenticator_mojo_bindings_java",
     "//third_party/blink/public/mojom:mojom_platform_java",
     "//third_party/blink/public/mojom:web_feature_mojo_bindings_java",
     "//third_party/google-truth:google_truth_java",
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
index 3477bc0..2448307 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
@@ -4,8 +4,17 @@
 
 package org.chromium.android_webview.test;
 
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ResultReceiver;
+
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -22,8 +31,14 @@
 import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.base.test.util.HistogramWatcher;
+import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
+import org.chromium.components.webauthn.AuthenticationContextProvider;
+import org.chromium.components.webauthn.Fido2ApiCallHelper;
 import org.chromium.components.webauthn.WebauthnMode;
+import org.chromium.components.webauthn.cred_man.CredManSupportProvider;
 import org.chromium.content_public.common.ContentSwitches;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.ServerCertificate;
 import org.chromium.net.test.util.TestWebServer;
 import org.chromium.url.GURL;
 
@@ -32,6 +47,23 @@
 @CommandLineFlags.Add({ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
 @Batch(Batch.PER_CLASS)
 public class WebAuthnTest {
+    private static class TestFido2ApiCallHelper extends Fido2ApiCallHelper {
+        public boolean mGetAssertionCalled;
+
+        @Override
+        public void invokeFido2GetAssertion(
+                AuthenticationContextProvider authenticationContextProvider,
+                PublicKeyCredentialRequestOptions options,
+                Uri uri,
+                byte[] clientDataHash,
+                ResultReceiver resultReceiver,
+                OnSuccessListener<PendingIntent> successCallback,
+                OnFailureListener failureCallback) {
+            mGetAssertionCalled = true;
+            failureCallback.onFailure(new Exception("MOCK_FIDO2_API_INVOKED"));
+        }
+    }
+
     @Rule public AwActivityTestRule mActivityTestRule = new AwActivityTestRule();
 
     private TestAwContentsClient mContentsClient;
@@ -158,6 +190,69 @@
         histogramWatcher.assertExpected();
     }
 
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testWebAuthnBlockedOnSslError() throws Throwable {
+        CredManSupportProvider.setupForTesting(Build.VERSION_CODES.TIRAMISU, false);
+        TestFido2ApiCallHelper testHelper = new TestFido2ApiCallHelper();
+        Fido2ApiCallHelper.overrideInstanceForTesting(testHelper);
+
+        EmbeddedTestServer testServer =
+                EmbeddedTestServer.createAndStartHTTPSServer(
+                        InstrumentationRegistry.getInstrumentation().getContext(),
+                        ServerCertificate.CERT_MISMATCHED_NAME);
+        try {
+            mAwSettings.setJavaScriptEnabled(true);
+            mAwSettings.setWebauthnSupport(WebauthnMode.APP);
+            mContentsClient.setAllowSslError(true);
+
+            final String pageUrl =
+                    testServer.getURLWithHostName(
+                            "a.test", "/android_webview/test/data/hello_world.html");
+            mActivityTestRule.loadUrlSync(
+                    mAwContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
+
+            mActivityTestRule.executeJavaScriptAndWaitForResult(
+                    mAwContents,
+                    mContentsClient,
+                    """
+                    window.webauthnResult = 'PENDING';
+                    navigator.credentials.get({
+                      publicKey: {
+                        challenge: new Uint8Array([1, 2, 3, 4]),
+                        timeout: 500,
+                        rpId: 'a.test'
+                      }
+                    }).then(
+                      () => { window.webauthnResult = 'SUCCESS'; },
+                      (e) => { window.webauthnResult = e.name + ': ' + e.message; }
+                    );
+                    """);
+
+            AwActivityTestRule.pollInstrumentationThread(
+                    () -> {
+                        String val =
+                                mActivityTestRule.executeJavaScriptAndWaitForResult(
+                                        mAwContents, mContentsClient, "window.webauthnResult");
+                        return !"\"PENDING\"".equals(val) && !"null".equals(val);
+                    });
+
+            String result =
+                    mActivityTestRule.executeJavaScriptAndWaitForResult(
+                            mAwContents, mContentsClient, "window.webauthnResult");
+
+            Assert.assertFalse(
+                    "FIDO2 API should not be invoked for pages with SSL errors",
+                    testHelper.mGetAssertionCalled);
+            Assert.assertTrue(
+                    "WebAuthn should be blocked due to SSL error, but got: " + result,
+                    result.contains("NotAllowedError") && result.contains("certificate errors"));
+        } finally {
+            testServer.stopAndDestroyServer();
+        }
+    }
+
     private static boolean isSecureDomain(GURL url) {
         if ("https".equals(url.getScheme())) {
             return true;
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 1d748689..69b320f 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -276,9 +276,8 @@
 android_library("webview_compose_javatests") {
   testonly = true
   enable_compose = true
-  sources = [
-    "../javatests/src/org/chromium/android_webview/test/AwComposeTest.kt",
-  ]
+  sources =
+      [ "../javatests/src/org/chromium/android_webview/test/AwComposeTest.kt" ]
   deps = [
     ":base_webview_instrumentation_java",
     ":base_webview_instrumentation_test_java",
@@ -309,6 +308,7 @@
 
   deps = [
     ":webview_compose_javatests",
+    "$google_play_services_package:google_play_services_tasks_java",
     "//android_webview:common_aidl_java",
     "//android_webview:common_origin_trials_java",
     "//android_webview:common_variations_java",
@@ -384,6 +384,7 @@
     "//third_party/androidx_javascriptengine:javascriptengine_java",
     "//third_party/blink/public:blink_headers_java",
     "//third_party/blink/public/common:common_java",
+    "//third_party/blink/public/mojom:authenticator_mojo_bindings_java",
     "//third_party/blink/public/mojom:mojom_platform_java",
     "//third_party/blink/public/mojom:web_feature_mojo_bindings_java",
     "//third_party/google-truth:google_truth_java",
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.