Medium watch CVSS 4.2 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
4.2
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in FedCM
DescriptionImproper input validation in FedCM
ComponentFedCM
Sub-componentFedCM
Bug ClassInput Validation
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.
Tracker514016678
Fix commit562273008c5e (chromium/src) +82/-4
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:L
CISA KEVNot listed
CreditedGoogle
Reported2026-05-17 (per vendor advisory)
Fix landed2026-08-13 (public git)
Disclosed2026-09-08
Patch gap26 days — fix public in source before the advisory

Background

FedCM
Chromium’s browser-mediated Federated Credential Management API that lets sites request identity tokens from identity providers without third-party cookies.
IdP Registration API
a FedCM sub-feature (gated by FedCmIdPRegistration) where a browser-registered identity provider is used instead of an explicitly supplied config_url.
`from_idp_registration_api`
a boolean on blink::mojom::IdentityProviderConfig, sent from the renderer, signaling that a get-request should resolve against a registered IdP rather than a configured URL.
`ReportBadMessage`
a Mojo mechanism the browser process uses to reject malformed IPC and terminate the offending renderer for sending out-of-contract input.

Root Cause Analysis

The StartTokenRequest() handler in content/browser/webid/request_service.cc accepted renderer-supplied IdentityProviderConfig values and forwarded them into request creation without validating the from_idp_registration_api flag against its required invariants. A compromised or malicious renderer could set from_idp_registration_api to true even when the FedCmIdPRegistration feature was disabled, or set it true while also supplying a non-empty config_url, a combination the registration flow is not designed to handle. The browser process trusted this cross-process input as if it had been constrained by the renderer-side implementation, violating the rule that the privileged process must independently validate all Mojo input.

The fix iterates each provider and enforces two conditions: when the feature is disabled it sanitizes the flag back to false (tolerating benign test-config mismatches), and when the feature is enabled but config_url is non-empty it calls receivers_.ReportBadMessage("config_url must be empty for registered providers.") and aborts. This re-establishes the invariant that from_idp_registration_api implies both an enabled feature and an empty config_url before any request object is built.

Key insight
The single mistake was trusting the renderer-controlled from_idp_registration_api flag without re-validating it in the browser process; the fix adds an explicit browser-side check that resets the flag when the feature is off and rejects the message when a registered provider carries a config_url.

Attack Path

  1. Compromised renderer An attacker who controls a renderer process (e.g., via a prior bug) crafts a FedCM get-request through the FederatedRequest Mojo interface.
  2. Forge registration flag The attacker sets provider->config->from_idp_registration_api = true on an IdentityProviderConfig regardless of feature state or config_url contents.
  3. Bypass renderer constraints Because the browser’s StartTokenRequest() did not re-check the flag, the inconsistent config (registration requested with a populated config_url, or with the feature disabled) reached request construction.
  4. Enter unintended code path The registered-IdP handling proceeds on input it was never designed to accept, driving FedCM logic into an out-of-contract state.

Impact Assessment

An attacker who already controls a renderer can push malformed FedCM registration input past the browser-process trust boundary, exercising the IdP-registration code path under conditions it does not guard against. The gain is confined to logic/state confusion within the browser-side FedCM RequestService, consistent with the medium severity and “improper input validation” classification, and no memory-corruption primitive is evidenced by the diff. The precondition is a renderer able to send arbitrary blink::mojom FedCM messages.

Changed Functions

FunctionChangeNotes
for
content/browser/webid/request_service.cc
modified
if
content/browser/webid/request_service.cc
modified
TEST_F
content/browser/webid/request_registry_unittest.cc
modified

Files Changed

  • content/browser/webid/request_service.cc
  • third_party/blink/web_tests/virtual/fedcm-register/README.md
  • third_party/blink/web_tests/VirtualTestSuites
  • content/browser/webid/request_registry_unittest.cc
  • content/browser/webid/request_unittest.cc

Audit Directions

  • Renderer-supplied Mojo flags
    Audit other blink::mojom boolean and mode flags (like from_idp_registration_api) that select privileged code paths to confirm the browser process re-validates them rather than trusting renderer enforcement.
  • Feature-gated inputs
    Wherever a base::Feature gate exists only on the renderer side, verify the browser independently checks IsXEnabled() before honoring input that assumes the feature, and decide deliberately between sanitize-and-continue and ReportBadMessage.
  • Cross-field invariants
    Look for cases where one field’s value constrains another (here, a set registration flag requires an empty config_url) and ensure those combined constraints are validated together at the trust boundary.
From 562273008c5efc49a07d8adbb66a4cb50ee98dd8 Mon Sep 17 00:00:00 2001
From: Nicolás Peña <[email protected]>
Date: Wed, 12 Aug 2026 17:25:27 -0700
Subject: [PATCH] [FedCM] Validate from_idp_registration_api in StartTokenRequest()

This CL adds security checks to ensure that from_idp_registration_api is
only set to true when the FedCmIdPRegistration feature is enabled and
the config_url is empty. If these conditions are violated, a bad message
is reported. Unit tests are added to verify this behavior.

Fixed: 514016678
Change-Id: Ica99d5522f0ed77c48928cfb27c16872ff764f02
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8221783
Auto-Submit: Nicolás Peña <[email protected]>
Reviewed-by: Philip Rogers <[email protected]>
Commit-Queue: Nicolás Peña <[email protected]>
Reviewed-by: Yi Gu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1678418}
---
diff --git a/content/browser/webid/request_service.cc b/content/browser/webid/request_service.cc
index 8a0aefa..5a9109d 100644
--- a/content/browser/webid/request_service.cc
+++ b/content/browser/webid/request_service.cc
@@ -197,6 +197,24 @@
     return;
   }
 
+  for (auto& provider : idp_get_params[0]->providers) {
+    if (provider->config->from_idp_registration_api) {
+      if (!IsIdPRegistrationEnabled()) {
+        // In layout and web platform tests, features with "status: test" (like
+        // FedCmIdPRegistration) are enabled by default on the renderer-side,
+        // but the corresponding browser-side base::Feature may be disabled.
+        // To prevent mismatches in benign test configurations from terminating
+        // the renderer, we sanitize/reset from_idp_registration_api to false
+        // instead of reporting a bad message.
+        provider->config->from_idp_registration_api = false;
+      } else if (!provider->config->config_url.is_empty()) {
+        receivers_.ReportBadMessage(
+            "config_url must be empty for registered providers.");
+        return;
+      }
+    }
+  }
+
   RenderFrameHost& rfh = render_frame_host();
   auto new_request = std::make_unique<Request>(&rfh, *this);
   new_request->BindReceiver(std::move(request_receiver));
diff --git a/content/browser/webid/request_registry_unittest.cc b/content/browser/webid/request_registry_unittest.cc
index 793eb498..e7448f5 100644
--- a/content/browser/webid/request_registry_unittest.cc
+++ b/content/browser/webid/request_registry_unittest.cc
@@ -39,6 +39,8 @@
 
 namespace content::webid {
 
+namespace {
+
 using ::testing::_;
 using ::testing::NiceMock;
 using ::testing::Return;
@@ -46,8 +48,7 @@
 using ApiPermissionStatus =
     FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
 using blink::mojom::RegisterIdpStatus;
-
-namespace {
+using MediationRequirement = ::password_manager::CredentialMediationRequirement;
 
 constexpr char kIdpUrl[] = "https://idp.example/";
 
@@ -489,4 +490,61 @@
   run_loop.Run();
 }
 
+// Test that calling StartTokenRequest with from_idp_registration_api as true
+// when the feature is disabled resolves with a normal error instead of a bad
+// message.
+TEST_F(RequestRegistryTest, StartTokenRequestFromIdpRegistrationDisabled) {
+  // Feature is disabled by default.
+  std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
+  auto get_params = blink::mojom::IdentityProviderGetParameters::New();
+  auto provider = blink::mojom::IdentityProviderRequestOptions::New();
+  provider->config = blink::mojom::IdentityProviderConfig::New();
+  provider->config->from_idp_registration_api = true;
+  get_params->providers.push_back(std::move(provider));
+  idp_get_params.push_back(std::move(get_params));
+
+  EXPECT_CALL(*mock_permission_delegate_, RemoveIdpSigninStatusObserver(_))
+      .WillOnce(Return());
+
+  mojo::Remote<blink::mojom::FederatedRequest> request_remote;
+  base::RunLoop run_loop;
+  request_service_remote_->StartTokenRequest(
+      std::move(idp_get_params), MediationRequirement::kOptional,
+      request_remote.BindNewPipeAndPassReceiver(),
+      base::BindLambdaForTesting(
+          [&run_loop](
+              blink::mojom::FederatedRequestService::StartTokenRequestResult
+                  result) {
+            EXPECT_FALSE(result.has_value());
+            EXPECT_EQ(blink::mojom::RequestTokenStatus::kError,
+                      result.error()->status);
+            run_loop.Quit();
+          }));
+  run_loop.Run();
+}
+
+// Test that calling StartTokenRequest with from_idp_registration_api as true
+// and a non-empty config_url triggers a Bad Message.
+TEST_F(RequestRegistryTest, StartTokenRequestFromIdpRegistrationNonEmptyUrl) {
+  feature_list_.InitAndEnableFeature(features::kFedCmIdPRegistration);
+
+  std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
+  auto get_params = blink::mojom::IdentityProviderGetParameters::New();
+  auto provider = blink::mojom::IdentityProviderRequestOptions::New();
+  provider->config = blink::mojom::IdentityProviderConfig::New();
+  provider->config->from_idp_registration_api = true;
+  provider->config->config_url = GURL(kIdpUrl);
+  get_params->providers.push_back(std::move(provider));
+  idp_get_params.push_back(std::move(get_params));
+
+  mojo::test::BadMessageObserver bad_message_observer;
+  mojo::Remote<blink::mojom::FederatedRequest> request_remote;
+  request_service_remote_->StartTokenRequest(
+      std::move(idp_get_params), MediationRequirement::kOptional,
+      request_remote.BindNewPipeAndPassReceiver(), base::DoNothing());
+
+  EXPECT_EQ("config_url must be empty for registered providers.",
+            bad_message_observer.WaitForBadMessage());
+}
+
 }  // namespace content::webid
diff --git a/content/browser/webid/request_unittest.cc b/content/browser/webid/request_unittest.cc
index 0359465..775d244 100644
--- a/content/browser/webid/request_unittest.cc
+++ b/content/browser/webid/request_unittest.cc
@@ -2185,6 +2185,7 @@
   config.idp_info[idp_config_url].well_known = {
       {kWellKnownMismatchConfigUrl}, {ParseStatus::kSuccess, net::HTTP_OK}};
   RequestParameters requestParameters = kDefaultRequestParameters;
+  requestParameters.identity_providers[0].provider = "";
   requestParameters.identity_providers[0].from_idp_registration_api = true;
 
   // Need to simulate there is actually a registered IdP, or the call will fail.
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index ed4b2f9a..3738572 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1156,7 +1156,7 @@
     ],
     "exclusive_tests": "ALL",
     "args": [
-      "--enable-features=FedCmIdPregistration"
+      "--enable-features=FedCmIdPRegistration"
     ],
     "owners": [
       "[email protected]",
diff --git a/third_party/blink/web_tests/virtual/fedcm-register/README.md b/third_party/blink/web_tests/virtual/fedcm-register/README.md
index 8a5b56c..8d5646d 100644
--- a/third_party/blink/web_tests/virtual/fedcm-register/README.md
+++ b/third_party/blink/web_tests/virtual/fedcm-register/README.md
@@ -1,5 +1,6 @@
 # FedCmRegister
+
 This suite runs the tests in wpt/credential-management/fedcm-register/ with
-`--enable-features=FedCmIdPregistration`.
+`--enable-features=FedCmIdPRegistration`.
 
 See crbug.com/40252825.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/webid/request_registry_unittest.cc b/content/browser/webid/request_registry_unittest.cc
index 793eb498..e7448f5 100644
--- a/content/browser/webid/request_registry_unittest.cc
+++ b/content/browser/webid/request_registry_unittest.cc
@@ -39,6 +39,8 @@
 
 namespace content::webid {
 
+namespace {
+
 using ::testing::_;
 using ::testing::NiceMock;
 using ::testing::Return;
@@ -46,8 +48,7 @@
 using ApiPermissionStatus =
     FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
 using blink::mojom::RegisterIdpStatus;
-
-namespace {
+using MediationRequirement = ::password_manager::CredentialMediationRequirement;
 
 constexpr char kIdpUrl[] = "https://idp.example/";
 
@@ -489,4 +490,61 @@
   run_loop.Run();
 }
 
+// Test that calling StartTokenRequest with from_idp_registration_api as true
+// when the feature is disabled resolves with a normal error instead of a bad
+// message.
+TEST_F(RequestRegistryTest, StartTokenRequestFromIdpRegistrationDisabled) {
+  // Feature is disabled by default.
+  std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
+  auto get_params = blink::mojom::IdentityProviderGetParameters::New();
+  auto provider = blink::mojom::IdentityProviderRequestOptions::New();
+  provider->config = blink::mojom::IdentityProviderConfig::New();
+  provider->config->from_idp_registration_api = true;
+  get_params->providers.push_back(std::move(provider));
+  idp_get_params.push_back(std::move(get_params));
+
+  EXPECT_CALL(*mock_permission_delegate_, RemoveIdpSigninStatusObserver(_))
+      .WillOnce(Return());
+
+  mojo::Remote<blink::mojom::FederatedRequest> request_remote;
+  base::RunLoop run_loop;
+  request_service_remote_->StartTokenRequest(
+      std::move(idp_get_params), MediationRequirement::kOptional,
+      request_remote.BindNewPipeAndPassReceiver(),
+      base::BindLambdaForTesting(
+          [&run_loop](
+              blink::mojom::FederatedRequestService::StartTokenRequestResult
+                  result) {
+            EXPECT_FALSE(result.has_value());
+            EXPECT_EQ(blink::mojom::RequestTokenStatus::kError,
+                      result.error()->status);
+            run_loop.Quit();
+          }));
+  run_loop.Run();
+}
+
+// Test that calling StartTokenRequest with from_idp_registration_api as true
+// and a non-empty config_url triggers a Bad Message.
+TEST_F(RequestRegistryTest, StartTokenRequestFromIdpRegistrationNonEmptyUrl) {
+  feature_list_.InitAndEnableFeature(features::kFedCmIdPRegistration);
+
+  std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
+  auto get_params = blink::mojom::IdentityProviderGetParameters::New();
+  auto provider = blink::mojom::IdentityProviderRequestOptions::New();
+  provider->config = blink::mojom::IdentityProviderConfig::New();
+  provider->config->from_idp_registration_api = true;
+  provider->config->config_url = GURL(kIdpUrl);
+  get_params->providers.push_back(std::move(provider));
+  idp_get_params.push_back(std::move(get_params));
+
+  mojo::test::BadMessageObserver bad_message_observer;
+  mojo::Remote<blink::mojom::FederatedRequest> request_remote;
+  request_service_remote_->StartTokenRequest(
+      std::move(idp_get_params), MediationRequirement::kOptional,
+      request_remote.BindNewPipeAndPassReceiver(), base::DoNothing());
+
+  EXPECT_EQ("config_url must be empty for registered providers.",
+            bad_message_observer.WaitForBadMessage());
+}
+
 }  // namespace content::webid
diff --git a/content/browser/webid/request_unittest.cc b/content/browser/webid/request_unittest.cc
index 0359465..775d244 100644
--- a/content/browser/webid/request_unittest.cc
+++ b/content/browser/webid/request_unittest.cc
@@ -2185,6 +2185,7 @@
   config.idp_info[idp_config_url].well_known = {
       {kWellKnownMismatchConfigUrl}, {ParseStatus::kSuccess, net::HTTP_OK}};
   RequestParameters requestParameters = kDefaultRequestParameters;
+  requestParameters.identity_providers[0].provider = "";
   requestParameters.identity_providers[0].from_idp_registration_api = true;
 
   // Need to simulate there is actually a registered IdP, or the call will fail.
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index ed4b2f9a..3738572 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1156,7 +1156,7 @@
     ],
     "exclusive_tests": "ALL",
     "args": [
-      "--enable-features=FedCmIdPregistration"
+      "--enable-features=FedCmIdPRegistration"
     ],
     "owners": [
       "[email protected]",
diff --git a/third_party/blink/web_tests/virtual/fedcm-register/README.md b/third_party/blink/web_tests/virtual/fedcm-register/README.md
index 8a5b56c..8d5646d 100644
--- a/third_party/blink/web_tests/virtual/fedcm-register/README.md
+++ b/third_party/blink/web_tests/virtual/fedcm-register/README.md
@@ -1,5 +1,6 @@
 # FedCmRegister
+
 This suite runs the tests in wpt/credential-management/fedcm-register/ with
-`--enable-features=FedCmIdPregistration`.
+`--enable-features=FedCmIdPRegistration`.
 
 See crbug.com/40252825.
Loading diff…

Original Bug Report

reported by [email protected]

FedCM: Potential bypass of .well-known enforcement via renderer-controlled flag

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

Overview: A logic error in FedCM allows a compromised renderer to bypass mandatory origin authorization checks by spoofing the ‘from_idp_registration_api’ flag. This permits an attacker to display trusted browser UI impersonating arbitrary domains.

Affected files:

  • content/browser/webid/request_service.cc
  • content/browser/webid/config_fetcher.cc
  • third_party/blink/public/mojom/webid/federated_auth_request.mojom
  • content/browser/webid/accounts_fetcher.cc
  • content/browser/webid/webid_utils.cc

Estimated timestamp from git blame: 2025-01-17

Summary

Federated Credential Management (FedCM) requires that an origin acting as an Identity Provider (IdP) authorize its configuration URLs via a /.well-known/web-identity file. This enforcement is bypassed if the IdP is registered via the browser’s IdP registration API. A logic flaw allows a compromised renderer to spoof this “registered” status, causing the browser to skip the mandatory well-known file check.

Vulnerability Details

The blink::mojom::IdentityProviderConfig struct, passed from the renderer to the browser in the FederatedAuthRequest::RequestToken IPC, contains a boolean field from_idp_registration_api. This flag is intended to signal that the configuration was retrieved from the browser’s trusted IdP registry.

In content/browser/webid/request_service.cc, the RequestToken method handles the incoming IPC. It contains logic to sanitize the provider list via MaybeAddRegisteredProviders (line 312), but this block is only executed if the kFedCmIdPRegistration feature is enabled (line 309). Since this feature is disabled by default, the browser proceeds with the raw, attacker-controlled IdentityProviderConfig structs provided in the IPC. Unlike the RequestUserInfo method (line 616), RequestToken fails to verify or clear the from_idp_registration_api flag when the feature is disabled.

The from_idp_registration_api flag propagates to ConfigFetcher::FetchRequest::force_skip_well_known_enforcement (line 806). In content/browser/webid/config_fetcher.cc, this flag causes ShouldSkipWellKnownEnforcementForIdp to return true (line 388). As a result:

  1. Network failures when fetching the /.well-known/web-identity file are ignored (line 101).
  2. The mandatory check to ensure the config URL is listed in the well-known file’s provider_urls list is skipped (line 305).

Impact

An attacker with control over a renderer process can potentially perform UI spoofing on a primary security surface. By hosting a FedCM configuration file on a victim domain (e.g., via an upload path) and initiating a request with the spoofed flag, the attacker can cause the trusted native FedCM dialog to appear, identifying the victim origin as the IdP. This bypasses the fundamental requirement that the victim origin must opt-in to acting as an IdP.

If the experimental kFedCmLightweightMode is enabled, the browser may even populate the spoofed dialog with the user’s real accounts for the victim origin, as the browser trusts the (spoofed) registration status to skip the standard accounts fetch.

Potential Steps to Reproduce

  1. Compromise a renderer process.
  2. Identify a victim origin https://victim.com that does not host a /.well-known/web-identity file.
  3. Host a malicious FedCM config file at https://victim.com/path/to/fedcm.json (e.g., via a file upload).
  4. From the compromised renderer, send a RequestToken Mojo IPC where the IdentityProviderConfig has config_url = "https://victim.com/path/to/fedcm.json" and from_idp_registration_api = true.
  5. Observe that the browser fetches the malicious config, ignores the absence of the well-known file, and displays trusted native UI for victim.com.

Note: These are potential steps based on code analysis; our current environment does not support running proof-of-concept code.

Suggested Fix

The browser process should never trust the from_idp_registration_api flag directly from a renderer. In content/browser/webid/request_service.cc, the RequestToken method should sanitize all input providers. Specifically, if IsIdPRegistrationEnabled() is false, the from_idp_registration_api flag must be forced to false for all providers. If the feature is enabled, the browser must verify that any provider claiming to be from the registration API is actually present in the trusted registry.

Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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