Overview

Low
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Web Authentication
DescriptionIncorrect authorization in Web Authentication
ComponentWeb Authentication
Sub-componentWebauthn
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
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.
Tracker499217288
Fix commit7f59498d7097 (chromium/src) +53/-18
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-04-03 (per vendor advisory)
Fix landed2026-07-31 (public git)
Disclosed2026-09-08
Patch gap39 days — fix public in source before the advisory

Background

`remoteDesktopClientOverride`
A WebAuthn request extension that lets a trusted remote-desktop client tell the browser to substitute a different origin (the origin of the machine being remoted into) when building the ceremony’s clientDataJSON.
`clientDataJSON`
The browser-generated, signed-over blob that records the origin the WebAuthn ceremony was bound to; forging its origin lets an assertion or registration be attributed to a site the caller does not actually control.
`OriginMayUseRemoteDesktopClientOverride`
A WebAuthenticationDelegate policy hook that returns true only when caller_origin is on the device-level enterprise allowlist permitted to use the override extension.
Embedder-delegated validation
For certain callers (e.g. chrome-extension:// origins), ValidateDomainAndRelyingPartyID hands origin/RP-ID validation to the embedder and returns early instead of running the standard in-function checks.

Root Cause Analysis

In WebAuthRequestSecurityCheckerImpl::ValidateDomainAndRelyingPartyID, the OriginMayUseRemoteDesktopClientOverride policy check was performed inline near the end of the function, immediately before relying_party_origin was set to the renderer-supplied remote_desktop_client_override_origin. However, callers whose validation is delegated to the embedder — such as chrome-extension:// origins — hit an earlier return nullptr success path (in the extension-handling block) that ran before that policy check. Consequently the invariant “an origin may only override the RP origin if it is on the device-level allowlist” was never evaluated for those callers, so an extension could supply an arbitrary remoteDesktopClientOverride origin and have it flow into the signed clientDataJSON.

The fix hoists the has_value() + OriginMayUseRemoteDesktopClientOverride guard to the very top of the function, so it runs before every early-return path, and replaces the now-redundant inline check with remote_desktop_client_override_origin.value_or(caller_origin). Because the allowlist is now consulted before any delegation return, an unauthorized caller receives REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED and no override is applied.

Key insight
The single mistake was ordering: the authorization check for the override sat after an early-return branch that some caller types took, so it was skipped for them; hoisting the check to the function entry ensures it gates all callers before any return.

Attack Path

  1. Obtain a delegated-validation origin An attacker controls or induces a chrome-extension:// origin (a caller type whose origin/RP-ID validation is delegated to the embedder) to initiate a WebAuthn create/get ceremony.
  2. Supply the override The caller includes a remoteDesktopClientOverride extension carrying an arbitrary attacker-chosen origin, e.g. https://example.com, despite not being on the device-level allowlist.
  3. Bypass the gate ValidateDomainAndRelyingPartyID takes the extension/embedder early-return path before reaching the inline OriginMayUseRemoteDesktopClientOverride check, so the unauthorized override is never rejected.
  4. Origin substitution relying_party_origin is set from the renderer-supplied override and written into the browser-generated clientDataJSON, attributing the ceremony to an origin the caller is not authorized to claim (inferred as the downstream effect from the commit message; the diff shows only the gate, not the clientDataJSON writer).

Impact Assessment

An extension caller that is not on the enterprise remote-desktop allowlist could cause the browser to embed an origin it does not control into the signed clientDataJSON of a WebAuthn assertion or registration, an incorrect-authorization flaw in the Web Authentication component. The commit itself notes that OverrideCallerOriginAndRelyingPartyIdValidation severely constrains the set of origins an extension could actually claim, limiting real-world exploitability, and the preconditions include an extension origin with extensions permitted in the browser context. No exploited-in-the-wild status or severity rating is provided in the advisory text supplied.

Files Changed

  • content/browser/webauth/authenticator_impl_unittest.cc
  • content/browser/webauth/webauth_request_security_checker_impl.cc

Audit Directions

  • Guard-after-early-return
    Look for security/authorization checks placed inline mid-function when the same function has earlier return paths (delegation, platform #if branches, error shortcuts) that would skip them; the guard belongs before every exit.
  • Renderer-supplied origin substitution
    Audit every consumer of untrusted, renderer-provided origins (remote_desktop_client_override_origin, RP-ID claims, override extensions) to confirm each is authorized against a policy/allowlist before it reaches signed or trust-bearing output like clientDataJSON.
  • Caller-type-specific bypasses
    Review checks that assume a single caller shape (web origins) but are reachable by alternate caller types (chrome-extension://, embedder-delegated, immediate-mediation) that follow divergent code paths through the same validation routine.
From 7f59498d7097ca3905b4401463347e139bc905c5 Mon Sep 17 00:00:00 2001
From: Martin Kreichgauer <martinkr@google.com>
Date: Fri, 31 Jul 2026 14:20:44 -0700
Subject: [PATCH] webauthn: gate remoteDesktopClientOverride for all caller types

ValidateDomainAndRelyingPartyID returned early for callers whose
origin/RP-ID validation is delegated to the embedder (e.g.
chrome-extension:// origins) without first evaluating
OriginMayUseRemoteDesktopClientOverride against the caller. As a result,
such a caller could supply the remoteDesktopClientOverride extension and
have its origin written into the browser-generated clientDataJSON
regardless of whether the caller origin was on the device-level
allowlist.

In practice, OverrideCallerOriginAndRelyingPartyIdValidation severely
limits the set of origins an extension could claim by doing this, but
nevertheless it is a good idea to tighten this up.

Hoist the policy check to the start of the function so that it runs
before every early-return path, and replace the now redundant inline
check with a value_or() for the relying-party-origin assignment.

BUG=499217288
TAG=agy
CONV=b7e1564f-5289-478d-bba4-48195f9673f6

Change-Id: I4a423db7677e3abd875219621daec6ca9b95c884
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8140023
Reviewed-by: Ken Buchanan <kenrb@chromium.org>
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/main@{#1672106}
---
diff --git a/content/browser/webauth/webauth_request_security_checker_impl.cc b/content/browser/webauth/webauth_request_security_checker_impl.cc
index 8e49065..a40d72b 100644
--- a/content/browser/webauth/webauth_request_security_checker_impl.cc
+++ b/content/browser/webauth/webauth_request_security_checker_impl.cc
@@ -159,6 +159,23 @@
     RequestType request_type,
     const std::optional<url::Origin>& remote_desktop_client_override_origin,
     base::OnceCallback<void(blink::mojom::AuthenticatorStatus)> callback) {
+  if (remote_desktop_client_override_origin.has_value()) {
+    // SECURITY: `remote_desktop_client_override_origin` comes from the renderer
+    // process and should not be trusted by default. We only allow its use when
+    // the `caller_origin` is explicitly allowlisted through device level
+    // enterprise policy.
+    if (!GetContentClient()
+             ->browser()
+             ->GetWebAuthenticationDelegate()
+             ->OriginMayUseRemoteDesktopClientOverride(
+                 render_frame_host_->GetBrowserContext(), caller_origin)) {
+      std::move(callback).Run(
+          blink::mojom::AuthenticatorStatus::
+              REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
+      return nullptr;
+    }
+  }
+
 #if !BUILDFLAG(IS_ANDROID)
   // Extensions are not supported on Android.
   if (GetContentClient()
@@ -188,24 +205,8 @@
     return nullptr;
   }
 
-  url::Origin relying_party_origin = caller_origin;
-  if (remote_desktop_client_override_origin.has_value()) {
-    // SECURITY: `remote_desktop_client_override_origin` comes from the renderer
-    // process and should not be trusted by default. We only allow its use when
-    // the `caller_origin` is explicitly allowlisted through device level
-    // enterprise policy.
-    if (!GetContentClient()
-             ->browser()
-             ->GetWebAuthenticationDelegate()
-             ->OriginMayUseRemoteDesktopClientOverride(
-                 render_frame_host_->GetBrowserContext(), caller_origin)) {
-      std::move(callback).Run(
-          blink::mojom::AuthenticatorStatus::
-              REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
-      return nullptr;
-    }
-    relying_party_origin = remote_desktop_client_override_origin.value();
-  }
+  url::Origin relying_party_origin =
+      remote_desktop_client_override_origin.value_or(caller_origin);
 
   if (webauthn::OriginIsAllowedToClaimRelyingPartyId(relying_party_id,
                                                      relying_party_origin)) {
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index 5e9f2feb..6ef94c5 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -3249,6 +3249,40 @@
   }
 }
 
+// A Chrome extension should not be authorized to use the
+// remoteDesktopClientOverride request extension.
+TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest,
+       ExtensionCallerOrigin) {
+  static const std::string kExtensionOrigin =
+      base::StrCat({kExtensionScheme, "://abcdefg"});
+  test_client_.GetTestWebAuthenticationDelegate()->permit_extensions = true;
+
+  {
+    PublicKeyCredentialCreationOptionsPtr options =
+        GetTestPublicKeyCredentialCreationOptions();
+    options->relying_party.id = kExampleRpId;
+    options->remote_desktop_client_override = RemoteDesktopClientOverride::New(
+        url::Origin::Create(GURL(kExampleOrigin)), true);
+    EXPECT_EQ(
+        AuthenticatorMakeCredential(std::move(options)).status,
+        AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
+  }
+
+  {
+    PublicKeyCredentialRequestOptionsPtr options =
+        GetTestPublicKeyCredentialRequestOptions();
+    options->relying_party_id = kExampleRpId;
+    options->extensions->remote_desktop_client_override =
+        RemoteDesktopClientOverride::New(
+            url::Origin::Create(GURL(kExampleOrigin)), true);
+    ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
+        options->allow_credentials[0].id, kExtensionOrigin));
+    EXPECT_EQ(
+        AuthenticatorGetAssertion(std::move(options)).status,
+        AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
+  }
+}
+
 TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest,
        GetAssertionImmediateMediation) {
   // Verify that an authorized origin may not use the extension with immediate
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index 5e9f2feb..6ef94c5 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -3249,6 +3249,40 @@
   }
 }
 
+// A Chrome extension should not be authorized to use the
+// remoteDesktopClientOverride request extension.
+TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest,
+       ExtensionCallerOrigin) {
+  static const std::string kExtensionOrigin =
+      base::StrCat({kExtensionScheme, "://abcdefg"});
+  test_client_.GetTestWebAuthenticationDelegate()->permit_extensions = true;
+
+  {
+    PublicKeyCredentialCreationOptionsPtr options =
+        GetTestPublicKeyCredentialCreationOptions();
+    options->relying_party.id = kExampleRpId;
+    options->remote_desktop_client_override = RemoteDesktopClientOverride::New(
+        url::Origin::Create(GURL(kExampleOrigin)), true);
+    EXPECT_EQ(
+        AuthenticatorMakeCredential(std::move(options)).status,
+        AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
+  }
+
+  {
+    PublicKeyCredentialRequestOptionsPtr options =
+        GetTestPublicKeyCredentialRequestOptions();
+    options->relying_party_id = kExampleRpId;
+    options->extensions->remote_desktop_client_override =
+        RemoteDesktopClientOverride::New(
+            url::Origin::Create(GURL(kExampleOrigin)), true);
+    ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
+        options->allow_credentials[0].id, kExtensionOrigin));
+    EXPECT_EQ(
+        AuthenticatorGetAssertion(std::move(options)).status,
+        AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
+  }
+}
+
 TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest,
        GetAssertionImmediateMediation) {
   // Verify that an authorized origin may not use the extension with immediate
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.