Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Network
DescriptionIncorrect authorization in Network
ComponentNetwork
Bug ClassLogic Error
Tracker497646947
Fix commitdb1fc539aaee (chromium/src) +64/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
TestNetworkAnnotationMonitor
services/network/network_service_unittest.cc
modified

Files Changed

  • services/network/network_service_network_delegate.cc
  • services/network/network_service_unittest.cc
From db1fc539aaee75e109e430b2c3929ff026ae2f9b Mon Sep 17 00:00:00 2001
From: Jiacheng Guo <gjc@google.com>
Date: Mon, 29 Jun 2026 16:47:07 -0700
Subject: [PATCH] Use IsolationInfo's SiteForCookies when handling Clear-Site-Data

NetworkServiceNetworkDelegate::HandleClearSiteDataHeader feeds
URLRequest::site_for_cookies() into IsPrivacyModeEnabled() to decide
whether the resulting OnClearSiteData notification is restricted to
partitioned state. The adjacent top_frame_origin argument already
comes from the request's IsolationInfo, which is supplied via
URLLoaderFactoryParams or trusted_params.

Consult the same IsolationInfo for site_for_cookies as well so that
both inputs agree and the partitioned-state-only decision matches the
embedding context described by the factory even if the per-request
site_for_cookies disagrees.

Add a unit test that creates a factory whose IsolationInfo describes
a cross-site subframe, enables third-party cookie blocking, and
issues a request whose site_for_cookies claims a first-party
context. It verifies that OnClearSiteData reports
partitioned_state_allowed_only=true.

Bug: 497646947
Change-Id: Id61ffa5e6f2eec79721f5b5f8557853d433c8976
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8017564
Commit-Queue: Jiacheng Guo <gjc@google.com>
Reviewed-by: Adam Rice <ricea@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1654466}
---

diff --git a/services/network/network_service_network_delegate.cc b/services/network/network_service_network_delegate.cc
index fa98a19..1d0afb6 100644
--- a/services/network/network_service_network_delegate.cc
+++ b/services/network/network_service_network_delegate.cc
@@ -417,7 +417,7 @@
   auto& cookie_settings = network_context_->cookie_manager()->cookie_settings();
   net::NetworkDelegate::PrivacySetting privacy_settings =
       cookie_settings.IsPrivacyModeEnabled(
-          request->url(), request->site_for_cookies(),
+          request->url(), request->isolation_info().site_for_cookies(),
           request->isolation_info().top_frame_origin(),
           request->cookie_setting_overrides());
   bool partitioned_state_allowed_only =
diff --git a/services/network/network_service_unittest.cc b/services/network/network_service_unittest.cc
index 4326564..32435238 100644
--- a/services/network/network_service_unittest.cc
+++ b/services/network/network_service_unittest.cc
@@ -32,11 +32,13 @@
 #include "net/base/features.h"
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
+#include "net/base/isolation_info.h"
 #include "net/base/mock_network_change_notifier.h"
 #include "net/base/url_util.h"
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/cookie_options.h"
 #include "net/cookies/cookie_util.h"
+#include "net/cookies/site_for_cookies.h"
 #include "net/dns/dns_client.h"
 #include "net/dns/dns_config.h"
 #include "net/dns/dns_config_service.h"
@@ -82,6 +84,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 #if BUILDFLAG(USE_KERBEROS)
 #include "net/http/http_auth_handler_negotiate.h"
@@ -2092,6 +2095,7 @@
       OnClearSiteDataCallback callback) override {
     ++on_clear_site_data_counter_;
     last_on_clear_site_data_header_value_ = header_value;
+    last_partitioned_state_allowed_only_ = partitioned_state_allowed_only;
     std::move(callback).Run();
   }
 
@@ -2101,14 +2105,20 @@
     return last_on_clear_site_data_header_value_;
   }
 
+  bool last_partitioned_state_allowed_only() const {
+    return last_partitioned_state_allowed_only_;
+  }
+
   void ClearOnClearSiteDataCounter() {
     on_clear_site_data_counter_ = 0;
     last_on_clear_site_data_header_value_.clear();
+    last_partitioned_state_allowed_only_ = false;
   }
 
  private:
   int on_clear_site_data_counter_ = 0;
   std::string last_on_clear_site_data_header_value_;
+  bool last_partitioned_state_allowed_only_ = false;
 };
 
 // Check that |NetworkServiceNetworkDelegate| handles Clear-Site-Data header
@@ -2194,6 +2204,59 @@
   }
 }
 
+// When third-party cookies are blocked and a Clear-Site-Data response is
+// received for a request issued from a cross-site subframe, the network
+// delegate must report that only partitioned state may be cleared. This must
+// hold even if the request's site_for_cookies disagrees with the factory's
+// IsolationInfo.
+TEST_F(NetworkServiceNetworkDelegateTest,
+       ClearSiteDataPartitionedStateOnlyForCrossSiteSubframe) {
+  const char kClearCookiesHeader[] = "Clear-Site-Data: \"cookies\"";
+
+  mojom::NetworkContextParamsPtr context_params =
+      mojom::NetworkContextParams::New();
+  context_params->cookie_manager_params = mojom::CookieManagerParams::New();
+  context_params->cookie_manager_params->block_third_party_cookies = true;
+  CreateNetworkContext(std::move(context_params));
+
+  ClearSiteDataAuthCertObserver clear_site_observer;
+
+  GURL url = https_server()->GetURL("/foo");
+  url = AddQuery(url, "header", kClearCookiesHeader);
+  const url::Origin top_frame_origin = url::Origin::Create(url);
+  const url::Origin frame_origin =
+      url::Origin::Create(GURL("https://other-site.test"));
+
+  mojo::Remote<mojom::URLLoaderFactory> loader_factory;
+  mojom::URLLoaderFactoryParamsPtr params =
+      mojom::URLLoaderFactoryParams::New();
+  params->process_id = OriginatingProcessId::browser();
+  params->is_orb_enabled = false;
+  params->isolation_info = net::IsolationInfo::Create(
+      net::IsolationInfo::RequestType::kOther, top_frame_origin, frame_origin,
+      net::SiteForCookies());
+  params->url_loader_network_observer = clear_site_observer.Bind();
+  network_context_->CreateURLLoaderFactory(
+      loader_factory.BindNewPipeAndPassReceiver(), std::move(params));
+
+  ResourceRequest request;
+  request.url = url;
+  request.method = "GET";
+  request.request_initiator = frame_origin;
+  request.site_for_cookies = net::SiteForCookies::FromOrigin(top_frame_origin);
+
+  client_ = std::make_unique<TestURLLoaderClient>();
+  loader_.reset();
+  loader_factory->CreateLoaderAndStart(
+      loader_.BindNewPipeAndPassReceiver(), 1, mojom::kURLLoadOptionNone,
+      request, client_->CreateRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  client_->RunUntilComplete();
+
+  EXPECT_EQ(1, clear_site_observer.on_clear_site_data_counter());
+  EXPECT_TRUE(clear_site_observer.last_partitioned_state_allowed_only());
+}
+
 class TestNetworkAnnotationMonitor : public mojom::NetworkAnnotationMonitor {
  public:
   mojo::PendingRemote<mojom::NetworkAnnotationMonitor> GetClient() {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/services/network/network_service_unittest.cc b/services/network/network_service_unittest.cc
index 4326564..32435238 100644
--- a/services/network/network_service_unittest.cc
+++ b/services/network/network_service_unittest.cc
@@ -32,11 +32,13 @@
 #include "net/base/features.h"
 #include "net/base/ip_address.h"
 #include "net/base/ip_endpoint.h"
+#include "net/base/isolation_info.h"
 #include "net/base/mock_network_change_notifier.h"
 #include "net/base/url_util.h"
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/cookie_options.h"
 #include "net/cookies/cookie_util.h"
+#include "net/cookies/site_for_cookies.h"
 #include "net/dns/dns_client.h"
 #include "net/dns/dns_config.h"
 #include "net/dns/dns_config_service.h"
@@ -82,6 +84,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 #if BUILDFLAG(USE_KERBEROS)
 #include "net/http/http_auth_handler_negotiate.h"
@@ -2092,6 +2095,7 @@
       OnClearSiteDataCallback callback) override {
     ++on_clear_site_data_counter_;
     last_on_clear_site_data_header_value_ = header_value;
+    last_partitioned_state_allowed_only_ = partitioned_state_allowed_only;
     std::move(callback).Run();
   }
 
@@ -2101,14 +2105,20 @@
     return last_on_clear_site_data_header_value_;
   }
 
+  bool last_partitioned_state_allowed_only() const {
+    return last_partitioned_state_allowed_only_;
+  }
+
   void ClearOnClearSiteDataCounter() {
     on_clear_site_data_counter_ = 0;
     last_on_clear_site_data_header_value_.clear();
+    last_partitioned_state_allowed_only_ = false;
   }
 
  private:
   int on_clear_site_data_counter_ = 0;
   std::string last_on_clear_site_data_header_value_;
+  bool last_partitioned_state_allowed_only_ = false;
 };
 
 // Check that |NetworkServiceNetworkDelegate| handles Clear-Site-Data header
@@ -2194,6 +2204,59 @@
   }
 }
 
+// When third-party cookies are blocked and a Clear-Site-Data response is
+// received for a request issued from a cross-site subframe, the network
+// delegate must report that only partitioned state may be cleared. This must
+// hold even if the request's site_for_cookies disagrees with the factory's
+// IsolationInfo.
+TEST_F(NetworkServiceNetworkDelegateTest,
+       ClearSiteDataPartitionedStateOnlyForCrossSiteSubframe) {
+  const char kClearCookiesHeader[] = "Clear-Site-Data: \"cookies\"";
+
+  mojom::NetworkContextParamsPtr context_params =
+      mojom::NetworkContextParams::New();
+  context_params->cookie_manager_params = mojom::CookieManagerParams::New();
+  context_params->cookie_manager_params->block_third_party_cookies = true;
+  CreateNetworkContext(std::move(context_params));
+
+  ClearSiteDataAuthCertObserver clear_site_observer;
+
+  GURL url = https_server()->GetURL("/foo");
+  url = AddQuery(url, "header", kClearCookiesHeader);
+  const url::Origin top_frame_origin = url::Origin::Create(url);
+  const url::Origin frame_origin =
+      url::Origin::Create(GURL("https://other-site.test"));
+
+  mojo::Remote<mojom::URLLoaderFactory> loader_factory;
+  mojom::URLLoaderFactoryParamsPtr params =
+      mojom::URLLoaderFactoryParams::New();
+  params->process_id = OriginatingProcessId::browser();
+  params->is_orb_enabled = false;
+  params->isolation_info = net::IsolationInfo::Create(
+      net::IsolationInfo::RequestType::kOther, top_frame_origin, frame_origin,
+      net::SiteForCookies());
+  params->url_loader_network_observer = clear_site_observer.Bind();
+  network_context_->CreateURLLoaderFactory(
+      loader_factory.BindNewPipeAndPassReceiver(), std::move(params));
+
+  ResourceRequest request;
+  request.url = url;
+  request.method = "GET";
+  request.request_initiator = frame_origin;
+  request.site_for_cookies = net::SiteForCookies::FromOrigin(top_frame_origin);
+
+  client_ = std::make_unique<TestURLLoaderClient>();
+  loader_.reset();
+  loader_factory->CreateLoaderAndStart(
+      loader_.BindNewPipeAndPassReceiver(), 1, mojom::kURLLoadOptionNone,
+      request, client_->CreateRemote(),
+      net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+  client_->RunUntilComplete();
+
+  EXPECT_EQ(1, clear_site_observer.on_clear_site_data_counter());
+  EXPECT_TRUE(clear_site_observer.last_partitioned_state_allowed_only());
+}
+
 class TestNetworkAnnotationMonitor : public mojom::NetworkAnnotationMonitor {
  public:
   mojo::PendingRemote<mojom::NetworkAnnotationMonitor> GetClient() {
Loading diff…

Original Bug Report

reported by vm...@google.com

Site Isolation Bypass via forged site_for_cookies in Clear-Site-Data

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A compromised renderer can forge the site_for_cookies field in a cross-origin subresource request to bypass third-party cookie restrictions. This allows an attacker to trigger the Clear-Site-Data: "cookies" mechanism and delete unpartitioned, first-party cookies (including HttpOnly session cookies) of any target site.

Affected files:

  • services/network/network_service_network_delegate.cc
  • components/content_settings/core/common/cookie_settings_base.cc
  • services/network/cors/cors_url_loader_factory.cc
  • content/browser/storage_partition_impl.cc
  • content/browser/browsing_data/clear_site_data_utils.cc
  • net/cookies/cookie_deletion_info.cc

Estimated timestamp from git blame: 2024-10-24

Summary

A potential vulnerability exists in how the Network Service handles the Clear-Site-Data HTTP response header. A compromised renderer can forge the site_for_cookies attribute in a subresource request originating from a cross-origin iframe. This forged value bypasses validation in CorsURLLoaderFactory for iframe subresources. When the target server responds with Clear-Site-Data: "cookies", the Network Service incorrectly relies on this forged site_for_cookies value to determine if the request is first-party. Consequently, the browser deletes all unpartitioned (first-party) cookies for the target site, bypassing Site Isolation guarantees that should restrict cross-site cookie clearing to partitioned state only.

(Note: These are potential steps, as our tooling agent doesn’t yet have the ability to run a live proof-of-concept.)

Potential Reproduction Steps

  1. Enable third-party cookie blocking in Chrome.
  2. A user visits https://victim.com, which embeds a cross-origin iframe pointing to https://attacker.com.
  3. The attacker compromises the renderer process hosting the attacker.com iframe.
  4. The compromised attacker.com renderer initiates a fetch request to an endpoint on the victim’s domain (e.g., https://victim.com/logout) that is known to return the Clear-Site-Data: "cookies" HTTP response header. The attacker sets credentials_mode=kInclude.
  5. Over the URLLoaderFactory Mojo IPC, the attacker forges the site_for_cookies property of the network::ResourceRequest to match the target site (https://victim.com).
  6. The Network Service receives the request at CorsURLLoaderFactory::CreateLoaderAndStart.
  7. Inside CorsURLLoaderFactory::IsValidRequest (services/network/cors/cors_url_loader_factory.cc), the validation of site_for_cookies is skipped because require_cross_site_request_for_cookies_ is configured as false for iframe subresource factories (content/browser/url_loader_factory_params_helper.cc).
  8. The network request is executed with the forged site_for_cookies. (Note: Due to SameSite protections evaluating the request_initiator, SameSite=Lax/Strict cookies will likely not be sent. This attack relies on the endpoint unconditionally returning Clear-Site-Data or relying on SameSite=None cookies).
  9. The server responds with Clear-Site-Data: "cookies".
  10. NetworkServiceNetworkDelegate::HandleClearSiteDataHeader (services/network/network_service_network_delegate.cc) processes the header and calls CookieSettings::IsPrivacyModeEnabled to determine if only partitioned cookies should be cleared.
  11. IsPrivacyModeEnabled delegates to CookieSettingsBase::GetCookieSettingInternal, which calls IsThirdPartyRequest(request->url(), request->site_for_cookies()).
  12. Because both values are forged to victim.com, IsThirdPartyRequest incorrectly evaluates to false (first-party request).
  13. IsPrivacyModeEnabled returns kStateAllowed, resulting in partitioned_state_allowed_only = false.
  14. The Network Service sends an OnClearSiteData IPC to the Browser process (StoragePartitionImpl), passing partitioned_state_allowed_only = false.
  15. The Browser process executes SiteDataClearer, setting cookie_filter_builder->SetPartitionedCookiesOnly(false).
  16. The BrowsingDataRemover deletes all unpartitioned, first-party cookies for victim.com, successfully executing a cross-site logout and bypassing Site Isolation protections.

Suggested Fix

The Network Service should not trust the site_for_cookies value provided by the renderer process for security-sensitive decisions like processing Clear-Site-Data.

Instead of relying on request->site_for_cookies(), NetworkServiceNetworkDelegate::HandleClearSiteDataHeader should use request->isolation_info().site_for_cookies(). The IsolationInfo is securely populated by the browser process and cannot be forged by a compromised renderer.

Alternatively, validation in CorsURLLoaderFactory::IsValidRequest could be hardened to verify that if site_for_cookies is present and matches the target URL, it must also be consistent with the securely verified request_initiator_origin_lock_ or the factory’s IsolationInfo.

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from 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