Overview

Low
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper certificate validation in FedCM
DescriptionImproper certificate validation in FedCM
ComponentFedCM
Sub-componentFedCM
Bug ClassLogic 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.
Tracker540072282
Fix commit4906428f67d6 (chromium/src) +37/-0
CWECWE-295
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-07-28 (per vendor advisory)
Fix landed2026-08-06 (public git)
Disclosed2026-09-08
Patch gap33 days — fix public in source before the advisory

Background

FedCM
the Federated Credential Management API, a browser feature that lets a Relying Party sign users in through an Identity Provider (IdP) while the browser mediates the exchange.
`Set-Login` header
an HTTP response header an IdP sends to tell the browser to persistently record origin-keyed sign-in status (logged-in / logged-out) for that IdP origin.
`IdentityUrlLoaderThrottle`
a blink::URLLoaderThrottle in content/common/webid/ that inspects IdP network responses and acts on Set-Login headers as they arrive.
`net::IsCertStatusError` / `cert_status`
cert_status is the bitfield of TLS certificate problems recorded for a response, and net::IsCertStatusError returns true when that bitfield contains a fatal certificate error (e.g. CERT_STATUS_DATE_INVALID).

Root Cause Analysis

The Set-Login handling path in content/common/webid/identity_url_loader_throttle.cc (reached via WillProcessResponse, the method the regression test invokes; the enclosing function name is not present in the hunk header, so this is (inferred)) validated that the response origin was potentially trustworthy but never consulted response_head.cert_status. Because a user who clicks through the certificate-error interstitial still receives the response body and headers, an HTTPS connection carrying a self-signed or otherwise invalid certificate would have its Set-Login header processed as if the TLS identity were sound, violating the invariant that security-sensitive, persistently-stored state must only be accepted over a connection whose certificate actually validated.

The fix adds an early return when net::IsCertStatusError(response_head.cert_status) is true — placed before the Set-Login processing logic — so a response over a cert-errored connection is dropped without side effects. The guard is bypassed only when the switches::kIgnoreCertificateErrors command-line switch is set, preserving test/dev workflows while closing the production path. This TIGHTENS the check: a header that previously would have been honored is now rejected.

Key insight
The single mistake was gating Set-Login processing on origin trustworthiness alone while ignoring whether the TLS certificate for that connection actually validated; the fix adds the missing net::IsCertStatusError(response_head.cert_status) guard so responses over cert-errored connections early-return and never persist sign-in status.

Attack Path

  1. Position as MITM An attacker intercepts the victim’s HTTPS connection to an IdP origin (e.g. https://accounts.idp.example/) and presents a self-signed or otherwise invalid TLS certificate.
  2. User clicks past the interstitial The victim proceeds through Chrome’s certificate-error warning, so the response is delivered to the renderer/network stack with cert_status set to a fatal error such as net::CERT_STATUS_DATE_INVALID.
  3. Inject `Set-Login` The attacker’s forged response includes a Set-Login header; pre-patch, IdentityUrlLoaderThrottle reached the header-processing code because it only checked origin trustworthiness, not cert_status.
  4. Persist forged sign-in state The header causes persistent origin-keyed FedCM sign-in status for the IdP to be written under attacker control, enabling cross-site denial of service or state confusion (inferred) from the advisory’s stated impact.

Impact Assessment

An attacker able to MITM an IdP HTTPS connection (and induce the user to bypass the certificate warning) could persist attacker-chosen origin-keyed FedCM sign-in status, producing cross-site denial of service or sign-in state confusion as the advisory describes. The precondition is a network-privileged (MITM) position plus user interaction to click through the cert-error interstitial, so it is not a remote-no-interaction bug. The advisory classifies this as improper certificate validation in FedCM (LogicError); no exploited-in-the-wild status is stated, and no memory-safety or code-execution escalation is demonstrated by the patch.

Changed Functions

FunctionChangeNotes
TEST_F
content/common/webid/identity_url_loader_throttle_unittest.cc
modified

Files Changed

  • content/common/webid/identity_url_loader_throttle_unittest.cc
  • content/common/DEPS
  • content/common/webid/identity_url_loader_throttle.cc
  • content/common/BUILD.gn

Audit Directions

  • Trustworthiness without cert validation
    Look for other response-header or response-body handlers that gate on origin trustworthiness (IsOriginPotentiallyTrustworthy / potentially-trustworthy checks) but never inspect response_head.cert_status, since click-through interstitials mean a trustworthy scheme does not imply a valid certificate.
  • Persistent state from network responses
    Audit code paths that write persistent or origin-keyed browser state (sign-in status, permissions, storage flags) from IdP or third-party responses, confirming they early-return on net::IsCertStatusError like other security-sensitive headers do.
  • Throttle ordering
    Verify that any newly added guards in URLLoaderThrottle subclasses run before the sink they protect, and that the kIgnoreCertificateErrors bypass is the only intended exception.
From 4906428f67d6d47d3cb5341b7517389e889181ed Mon Sep 17 00:00:00 2001
From: Nicolás Peña <npm@chromium.org>
Date: Thu, 06 Aug 2026 07:53:37 -0700
Subject: [PATCH] [FedCM] Reject Set-Login headers on connections with cert errors

IdentityUrlLoaderThrottle handles Set-Login response headers to set
persistent origin-keyed IdP sign-in status. Currently, it checks
whether the origin is potentially trustworthy, but does not check
whether the connection has TLS certificate errors.

An attacker MITM-ing an HTTPS connection with a self-signed or
invalid certificate could cause Set-Login headers to be processed
if the user clicked past the certificate error interstitial. This
could persist origin-keyed sign-in status, leading to cross-site
denial of service or state confusion.

This CL early-returns in IdentityUrlLoaderThrottle when
net::IsCertStatusError(response_head.cert_status) is true, matching
the pattern used by other security-sensitive response headers.

Bug: 540072282
Change-Id: I4693599760fa036b0c1194e84816e900f7bdd205
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8198686
Reviewed-by: Dave Tapuska <dtapuska@chromium.org>
Reviewed-by: Yi Gu <yigu@chromium.org>
Commit-Queue: Nicolás Peña <npm@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1674972}
---
diff --git a/content/common/webid/identity_url_loader_throttle.cc b/content/common/webid/identity_url_loader_throttle.cc
index 7f396480..c6e479e 100644
--- a/content/common/webid/identity_url_loader_throttle.cc
+++ b/content/common/webid/identity_url_loader_throttle.cc
@@ -8,14 +8,17 @@
 #include <string_view>
 
 #include "base/auto_reset.h"
+#include "base/command_line.h"
 #include "base/functional/bind.h"
 #include "base/strings/string_split.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
+#include "components/network_session_configurator/common/network_switches.h"
 #include "content/common/features.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
+#include "net/cert/cert_status_flags.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/structured_headers.h"
 #include "services/data_decoder/public/cpp/data_decoder.h"
@@ -139,6 +142,12 @@
     return;
   }
 
+  if (net::IsCertStatusError(response_head.cert_status) &&
+      !base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kIgnoreCertificateErrors)) {
+    return;
+  }
+
   // TODO(crbug.com/40236764):
   // - Limit to toplevel frames
   // - Decide whether to limit to same-origin
diff --git a/content/common/webid/identity_url_loader_throttle_unittest.cc b/content/common/webid/identity_url_loader_throttle_unittest.cc
index da7a8475..b972da46 100644
--- a/content/common/webid/identity_url_loader_throttle_unittest.cc
+++ b/content/common/webid/identity_url_loader_throttle_unittest.cc
@@ -11,6 +11,7 @@
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/web_identity.h"
+#include "net/cert/cert_status_flags.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/structured_headers.h"
 #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
@@ -354,4 +355,29 @@
   EXPECT_TRUE(result_item->item.is_integer());
 }
 
+TEST_F(IdentityUrlLoaderThrottleTest, CertError) {
+  TestDelegate delegate;
+  std::unique_ptr<blink::URLLoaderThrottle> throttle =
+      MaybeCreateIdentityUrlLoaderThrottle(CreateCallback(),
+                                           CreateParseCallback());
+  ASSERT_NE(nullptr, throttle);
+  throttle->set_delegate(&delegate);
+
+  network::ResourceRequest request;
+  request.url = GURL("https://accounts.idp.example/");
+  bool defer = false;
+
+  throttle->WillStartRequest(&request, &defer);
+  EXPECT_FALSE(defer);
+
+  network::mojom::URLResponseHead response_head;
+  response_head.cert_status = net::CERT_STATUS_DATE_INVALID;
+  response_head.headers = net::HttpResponseHeaders::TryToCreate(
+      "HTTP/1.1 200 OK\nSet-Login: logged-in\n");
+  throttle->WillProcessResponse(request.url, &response_head, &defer);
+  EXPECT_FALSE(defer);
+
+  EXPECT_EQ(0, cb_num_calls_);
+}
+
 }  // namespace content
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index 264cdb4d..6db21bb 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -224,6 +224,7 @@
     "//build:branding_buildflags",
     "//components/discardable_memory/common",
     "//components/input",
+    "//components/network_session_configurator/common",
     "//components/services/filesystem/public/mojom",
     "//components/tracing:tracing_config",
     "//content:content_resources",
diff --git a/content/common/DEPS b/content/common/DEPS
index fff9622..6379910 100644
--- a/content/common/DEPS
+++ b/content/common/DEPS
@@ -2,6 +2,7 @@
   "-storage/browser",
 
   "+components/discardable_memory/common",
+  "+components/network_session_configurator/common",
   "+components/viz/common",
   "+device/base/synchronization",
   "+services/data_decoder/public/cpp",
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/common/webid/identity_url_loader_throttle_unittest.cc b/content/common/webid/identity_url_loader_throttle_unittest.cc
index da7a8475..b972da46 100644
--- a/content/common/webid/identity_url_loader_throttle_unittest.cc
+++ b/content/common/webid/identity_url_loader_throttle_unittest.cc
@@ -11,6 +11,7 @@
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/web_identity.h"
+#include "net/cert/cert_status_flags.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/structured_headers.h"
 #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
@@ -354,4 +355,29 @@
   EXPECT_TRUE(result_item->item.is_integer());
 }
 
+TEST_F(IdentityUrlLoaderThrottleTest, CertError) {
+  TestDelegate delegate;
+  std::unique_ptr<blink::URLLoaderThrottle> throttle =
+      MaybeCreateIdentityUrlLoaderThrottle(CreateCallback(),
+                                           CreateParseCallback());
+  ASSERT_NE(nullptr, throttle);
+  throttle->set_delegate(&delegate);
+
+  network::ResourceRequest request;
+  request.url = GURL("https://accounts.idp.example/");
+  bool defer = false;
+
+  throttle->WillStartRequest(&request, &defer);
+  EXPECT_FALSE(defer);
+
+  network::mojom::URLResponseHead response_head;
+  response_head.cert_status = net::CERT_STATUS_DATE_INVALID;
+  response_head.headers = net::HttpResponseHeaders::TryToCreate(
+      "HTTP/1.1 200 OK\nSet-Login: logged-in\n");
+  throttle->WillProcessResponse(request.url, &response_head, &defer);
+  EXPECT_FALSE(defer);
+
+  EXPECT_EQ(0, cb_num_calls_);
+}
+
 }  // namespace content
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.