Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Network
DescriptionUse after free in Network
ComponentNetwork
Sub-componentCookieSettings
Bug ClassUAF
Tracker554556700
Fix commitbaebffbdff20 (chromium/src) +154/-37
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
for
services/network/cookie_manager.cc
modified
GetHostIndexedContentSettings
services/network/cookie_settings.cc
modified
for
services/network/cookie_settings.cc
modified
GURL
services/network/cookie_settings.h
modified
TEST_F
services/network/cookie_settings_unittest.cc
modified

Files Changed

  • content/public/browser/content_browser_client.cc
  • content/browser/devtools/devtools_url_loader_interceptor.cc
  • services/network/cookie_settings_unittest.cc
  • chrome/browser/net/profile_network_context_service.cc
  • content/public/browser/content_browser_client.h
  • services/network/cookie_manager.cc
  • services/network/cookie_settings.h
  • chrome/browser/chrome_content_browser_client.cc
  • chrome/browser/chrome_content_browser_client.h
  • services/network/public/mojom/cookie_manager.mojom
  • services/network/cookie_settings.cc
From baebffbdff20a0e7f30afd7bb70a2323a625423c Mon Sep 17 00:00:00 2001
From: Maks Orlovich <morlovich@chromium.org>
Date: Wed, 16 Sep 2026 10:47:10 -0700
Subject: [PATCH] CookieSettings: specify what embedded hosts to accept in secure_origin_cookies_allowed_origins

And narrow the lens exception with it.

Bug: 554556700
Change-Id: Ic4103755d51be3e74c774b52b84a4cf95331046c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8407287
Commit-Queue: Maks Orlovich <morlovich@chromium.org>
Reviewed-by: Lily Chen <chlily@chromium.org>
Reviewed-by: Camille Lamy <clamy@chromium.org>
Reviewed-by: Giovanni Ortuno Urquidi <ortuno@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1699052}
---
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index c5661d4..ca78f427 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -2156,18 +2156,28 @@
 bool ChromeContentBrowserClient::
     ShouldIgnoreSameSiteCookieRestrictionsWhenTopLevel(
         const url::Origin& top_frame_origin,
-        bool is_embedded_origin_secure) {
+        const GURL& current_url_chain_entry) {
+  if (!current_url_chain_entry.SchemeIsCryptographic()) {
+    return false;
+  }
+
+  if (top_frame_origin.scheme() == content::kChromeUIScheme) {
+    return true;
+  }
+
+#if !BUILDFLAG(IS_ANDROID)
   // TODO(crbug.com/483614998): Granting Lens side panel is a temporary
   // exception to use SameSite cookies while it migrates to a <webview>
   // approach. This should not be done for other untrusted WebUI.
-  return is_embedded_origin_secure &&
-         (top_frame_origin.scheme() == content::kChromeUIScheme
-#if !BUILDFLAG(IS_ANDROID)
-          ||
-          (top_frame_origin == url::Origin::Create(GURL(
-                                   chrome::kChromeUILensUntrustedSidePanelURL)))
+  if (top_frame_origin == url::Origin::Create(GURL(
+                              chrome::kChromeUILensUntrustedSidePanelURL)) &&
+      net::SchemefulSite::IsSameSite(current_url_chain_entry,
+                                     GURL("https://google.com"))) {
+    return true;
+  }
 #endif
-         );
+
+  return false;
 }
 
 // TODO(crbug.com/40694933): This is based on SubframeTask::GetTitle()
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 4bd6004f..1aeebbf 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -234,7 +234,7 @@
       bool is_embedded_origin_secure) override;
   bool ShouldIgnoreSameSiteCookieRestrictionsWhenTopLevel(
       const url::Origin& top_frame_origin,
-      bool is_embedded_origin_secure) override;
+      const GURL& current_url_chain_entry) override;
   std::string GetSiteDisplayNameForCdmProcess(
       content::BrowserContext* browser_context,
       const GURL& site_url) override;
diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/browser/net/profile_network_context_service.cc
index 67be696b..c7a6df0 100644
--- a/chrome/browser/net/profile_network_context_service.cc
+++ b/chrome/browser/net/profile_network_context_service.cc
@@ -1134,8 +1134,11 @@
   // TODO(crbug.com/483614998): Granting Lens side panel is a temporary
   // exception to use SameSite cookies while it migrates to a <webview>
   // approach. This should not be done for other untrusted WebUI.
-  out->secure_origin_cookies_allowed_origins.push_back(
-      url::Origin::Create(GURL(chrome::kChromeUILensUntrustedSidePanelURL)));
+  std::vector<net::SchemefulSite> accessed_by_lens{
+      net::SchemefulSite(GURL("https://google.com"))};
+  out->secure_origin_cookies_allowed_origins.insert_or_assign(
+      url::Origin::Create(GURL(chrome::kChromeUILensUntrustedSidePanelURL)),
+      std::move(accessed_by_lens));
 #endif
 
 #if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
diff --git a/content/browser/devtools/devtools_url_loader_interceptor.cc b/content/browser/devtools/devtools_url_loader_interceptor.cc
index a33badf..10208e2 100644
--- a/content/browser/devtools/devtools_url_loader_interceptor.cc
+++ b/content/browser/devtools/devtools_url_loader_interceptor.cc
@@ -1642,8 +1642,7 @@
       GetContentClient()
           ->browser()
           ->ShouldIgnoreSameSiteCookieRestrictionsWhenTopLevel(
-              top_frame_origin,
-              create_loader_params_->request.url.SchemeIsCryptographic());
+              top_frame_origin, url_chain_.back());
   CHECK_EQ(create_loader_params_->request.url, url_chain_.back(),
            base::NotFatalUntil::M159);
   bool is_main_frame_navigation =
@@ -1817,8 +1816,8 @@
   bool should_treat_as_first_party =
       GetContentClient()
           ->browser()
-          ->ShouldIgnoreSameSiteCookieRestrictionsWhenTopLevel(
-              top_frame_origin, request.url.SchemeIsCryptographic());
+          ->ShouldIgnoreSameSiteCookieRestrictionsWhenTopLevel(top_frame_origin,
+                                                               request.url);
   bool is_main_frame_navigation =
       request.trusted_params.has_value() &&
       request.trusted_params->isolation_info.request_type() ==
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 7fb5b3d9..5edca9ca1 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -240,7 +240,7 @@
 
 bool ContentBrowserClient::ShouldIgnoreSameSiteCookieRestrictionsWhenTopLevel(
     const url::Origin& top_frame_origin,
-    bool is_embedded_origin_secure) {
+    const GURL& current_url_chain_entry) {
   return false;
 }
 
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index e2eeec5a..3430c99 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -562,15 +562,15 @@
       bool is_embedded_origin_secure);
 
   // Similar to the above. Returns whether SameSite cookie restrictions should
-  // be ignored when the site_for_cookies's origin is |top_frame_origin|.
-  // |is_embedded_origin_secure| refers to whether the origin that is embedded
-  // in a document with the given origin is secure.
+  // be ignored when the site_for_cookies's origin is |top_frame_origin|,
+  // for cookies relevant to |current_url_chain_entry|.
+  //
   // This is a separate function from the above because the allowed schemes can
   // be different, as SameSite restrictions and third-party cookie blocking are
   // related but have different semantics.
   virtual bool ShouldIgnoreSameSiteCookieRestrictionsWhenTopLevel(
       const url::Origin& top_frame_origin,
-      bool is_embedded_origin_secure);
+      const GURL& current_url_chain_entry);
 
   // Gets a user friendly display name for a given |site_url| to be used in the
   // CDM process name.
diff --git a/services/network/cookie_manager.cc b/services/network/cookie_manager.cc
index 24740b2..f4cb3ef 100644
--- a/services/network/cookie_manager.cc
+++ b/services/network/cookie_manager.cc
@@ -378,13 +378,14 @@
   out->set_block_third_party_cookies(params.block_third_party_cookies);
   out->set_secure_origin_cookies_allowed_schemes(
       params.secure_origin_cookies_allowed_schemes);
-  std::vector<url::Origin> filtered_origins;
-  for (const auto& origin : params.secure_origin_cookies_allowed_origins) {
-    if (!origin.opaque()) {
-      filtered_origins.push_back(origin);
+  std::vector<std::pair<url::Origin, std::vector<net::SchemefulSite>>>
+      filtered_origins;
+  for (const auto& kv : params.secure_origin_cookies_allowed_origins) {
+    if (!kv.first.opaque()) {
+      filtered_origins.emplace_back(kv.first, kv.second);
     }
   }
-  out->set_secure_origin_cookies_allowed_origins(filtered_origins);
+  out->set_secure_origin_cookies_allowed_origins(std::move(filtered_origins));
   out->set_matching_scheme_cookies_allowed_schemes(
       params.matching_scheme_cookies_allowed_schemes);
   out->set_third_party_cookies_allowed_schemes(
diff --git a/services/network/cookie_settings.cc b/services/network/cookie_settings.cc
index e01b731..4df9a1b 100644
--- a/services/network/cookie_settings.cc
+++ b/services/network/cookie_settings.cc
@@ -178,7 +178,7 @@
       !site_for_cookies.IsNull()) {
     return true;
   }
-  return secure_origin_cookies_allowed_origins_.contains(top_level_origin);
+  return HasSecureOriginException(top_level_origin, url);
 }
 
 bool CookieSettings::IsCookieAccessible(
@@ -218,8 +218,7 @@
             first_party_url.scheme())) {
       return true;
     }
-    if (secure_origin_cookies_allowed_origins_.contains(
-            url::Origin::Create(first_party_url))) {
+    if (HasSecureOriginException(url::Origin::Create(first_party_url), url)) {
       return true;
     }
   }
@@ -310,6 +309,17 @@
   return false;
 }
 
+bool CookieSettings::HasSecureOriginException(
+    const url::Origin& first_party_origin,
+    const GURL& url) const {
+  auto it = secure_origin_cookies_allowed_origins_.find(first_party_origin);
+  if (it == secure_origin_cookies_allowed_origins_.end()) {
+    return false;
+  }
+  net::SchemefulSite url_site(url);
+  return std::ranges::contains(it->second, url_site);
+}
+
 const std::vector<content_settings::HostIndexedContentSettings>&
 CookieSettings::GetHostIndexedContentSettings(ContentSettingsType type) const {
   CHECK(IsValidType(type)) << static_cast<int>(type);
@@ -437,11 +447,13 @@
 }
 
 void CookieSettings::set_secure_origin_cookies_allowed_origins(
-    const std::vector<url::Origin>& secure_origin_cookies_allowed_origins) {
+    std::vector<std::pair<url::Origin, std::vector<net::SchemefulSite>>>
+        secure_origin_cookies_allowed_origins) {
   secure_origin_cookies_allowed_origins_.clear();
-  for (const auto& origin : secure_origin_cookies_allowed_origins) {
-    if (!origin.opaque()) {
-      secure_origin_cookies_allowed_origins_.insert(origin);
+  for (auto& kv : secure_origin_cookies_allowed_origins) {
+    if (!kv.first.opaque()) {
+      secure_origin_cookies_allowed_origins_.insert_or_assign(
+          kv.first, std::move(kv.second));
     }
   }
 }
diff --git a/services/network/cookie_settings.h b/services/network/cookie_settings.h
index d4d2c13..8eb361b 100644
--- a/services/network/cookie_settings.h
+++ b/services/network/cookie_settings.h
@@ -25,7 +25,7 @@
 #include "net/cookies/cookie_util.h"
 #include "net/first_party_sets/first_party_set_metadata.h"
 #include "services/network/public/cpp/session_cookie_delete_predicate.h"
-#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
+#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
 
 class GURL;
 
@@ -65,7 +65,8 @@
 
   // Cannot be an opaque origin. Opaque origins will be ignored.
   void set_secure_origin_cookies_allowed_origins(
-      const std::vector<url::Origin>& secure_origin_cookies_allowed_origins);
+      std::vector<std::pair<url::Origin, std::vector<net::SchemefulSite>>>
+          secure_origin_cookies_allowed_origins);
 
   void set_matching_scheme_cookies_allowed_schemes(
       const std::vector<std::string>& matching_scheme_cookies_allowed_schemes) {
@@ -217,12 +218,16 @@
   // Returns true if at least one content settings is session only.
   bool HasSessionOnlyOrigins() const;
 
+  bool HasSecureOriginException(const url::Origin& first_party_origin,
+                                const GURL& url) const;
+
   // Returns true if user blocks 3PC or 3PCD is on.
   bool block_third_party_cookies_ =
       net::cookie_util::IsForceThirdPartyCookieBlockingEnabled();
 
   std::set<std::string, std::less<>> secure_origin_cookies_allowed_schemes_;
-  absl::flat_hash_set<url::Origin> secure_origin_cookies_allowed_origins_;
+  absl::flat_hash_map<url::Origin, std::vector<net::SchemefulSite>>
+      secure_origin_cookies_allowed_origins_;
   std::set<std::string, std::less<>> matching_scheme_cookies_allowed_schemes_;
   std::set<std::string, std::less<>> third_party_cookies_allowed_schemes_;
 
diff --git a/services/network/public/mojom/cookie_manager.mojom b/services/network/public/mojom/cookie_manager.mojom
index 993124a..ebdc6e89 100644
--- a/services/network/public/mojom/cookie_manager.mojom
+++ b/services/network/public/mojom/cookie_manager.mojom
@@ -29,9 +29,13 @@
   // embedded frames.
   array<string> secure_origin_cookies_allowed_schemes;
 
-  // Origins that unconditionally allow cookies from secure origins.
+  // Top-level origins that unconditionally allow cookies from secure origins.
+  // The key is the top-level origin to check for, the values are
+  // permitted sites to access cookies for (and will be checked in order).
+  //
   // Note: Opaque origins are not allowed and will be ignored.
-  array<url.mojom.Origin> secure_origin_cookies_allowed_origins;
+  map<url.mojom.Origin, array<SchemefulSite>>
+      secure_origin_cookies_allowed_origins;
 
   // Schemes that unconditionally allow cookies from the same scheme.
   array<string> matching_scheme_cookies_allowed_schemes;
diff --git a/services/network/cookie_settings_unittest.cc b/services/network/cookie_settings_unittest.cc
index 0e1931f..7e0b340 100644
--- a/services/network/cookie_settings_unittest.cc
+++ b/services/network/cookie_settings_unittest.cc
@@ -889,6 +889,89 @@
             CONTENT_SETTING_BLOCK);
 }
 
+TEST_F(CookieSettingsTest, SetSecureOriginCookiesAllowedOrigins) {
+  url::ScopedSchemeRegistryForTests scoped_registry;
+  url::AddStandardScheme(kChromeScheme, url::SCHEME_WITH_HOST);
+
+  CookieSettings settings;
+  settings.set_secure_origin_cookies_allowed_origins(
+      {{url::Origin::Create(GURL("chrome://foo")),
+        {net::SchemefulSite(GURL("https://foo.com")),
+         net::SchemefulSite(GURL("https://bar.com"))}}});
+  settings.set_block_third_party_cookies(true);
+
+  EXPECT_EQ(settings.GetCookieSetting(
+                /*url=*/GURL("https://foo.com"),
+                /*site_for_cookies=*/net::SiteForCookies(),
+                /*first_party_url=*/GURL("chrome://foo"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_ALLOW);
+  EXPECT_TRUE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("https://foo.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://foo"))));
+
+  // Not applicable for http://
+  EXPECT_EQ(settings.GetCookieSetting(
+                /* url=*/GURL("http://foo.com"),
+                /* site_for_cookies=*/net::SiteForCookies(),
+                /* first_party_url=*/GURL("chrome://foo"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_BLOCK);
+  EXPECT_FALSE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("http://foo.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://foo"))));
+
+  // It is schemeful-site level.
+  EXPECT_EQ(settings.GetCookieSetting(
+                /*url=*/GURL("https://www.foo.com"),
+                /*site_for_cookies=*/net::SiteForCookies(),
+                /*first_party_url=*/GURL("chrome://foo"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_ALLOW);
+  EXPECT_TRUE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("https://www.foo.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://foo"))));
+
+  // Wrong first-party.
+  EXPECT_EQ(settings.GetCookieSetting(
+                /* url=*/GURL("https://foo.com"),
+                /* site_for_cookies=*/net::SiteForCookies(),
+                /* first_party_url=*/GURL("chrome://bar"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_BLOCK);
+  EXPECT_FALSE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("https://foo.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://bar"))));
+
+  // bar.com is OK
+  EXPECT_EQ(settings.GetCookieSetting(
+                /* url=*/GURL("https://bar.com"),
+                /* site_for_cookies=*/net::SiteForCookies(),
+                /* first_party_url=*/GURL("chrome://foo"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_ALLOW);
+  EXPECT_TRUE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("https://bar.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://foo"))));
+
+  // baz.com is not
+  EXPECT_EQ(settings.GetCookieSetting(
+                /* url=*/GURL("https://baz.com"),
+                /* site_for_cookies=*/net::SiteForCookies(),
+                /* first_party_url=*/GURL("chrome://foo"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_BLOCK);
+  EXPECT_FALSE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("https://baz.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://foo"))));
+}
+
 TEST_F(CookieSettingsTest, GetCookieSettingWithThirdPartyCookiesAllowedScheme) {
   CookieSettings settings;
   settings.set_third_party_cookies_allowed_schemes({"chrome-extension"});
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/services/network/cookie_settings_unittest.cc b/services/network/cookie_settings_unittest.cc
index 0e1931f..7e0b340 100644
--- a/services/network/cookie_settings_unittest.cc
+++ b/services/network/cookie_settings_unittest.cc
@@ -889,6 +889,89 @@
             CONTENT_SETTING_BLOCK);
 }
 
+TEST_F(CookieSettingsTest, SetSecureOriginCookiesAllowedOrigins) {
+  url::ScopedSchemeRegistryForTests scoped_registry;
+  url::AddStandardScheme(kChromeScheme, url::SCHEME_WITH_HOST);
+
+  CookieSettings settings;
+  settings.set_secure_origin_cookies_allowed_origins(
+      {{url::Origin::Create(GURL("chrome://foo")),
+        {net::SchemefulSite(GURL("https://foo.com")),
+         net::SchemefulSite(GURL("https://bar.com"))}}});
+  settings.set_block_third_party_cookies(true);
+
+  EXPECT_EQ(settings.GetCookieSetting(
+                /*url=*/GURL("https://foo.com"),
+                /*site_for_cookies=*/net::SiteForCookies(),
+                /*first_party_url=*/GURL("chrome://foo"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_ALLOW);
+  EXPECT_TRUE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("https://foo.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://foo"))));
+
+  // Not applicable for http://
+  EXPECT_EQ(settings.GetCookieSetting(
+                /* url=*/GURL("http://foo.com"),
+                /* site_for_cookies=*/net::SiteForCookies(),
+                /* first_party_url=*/GURL("chrome://foo"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_BLOCK);
+  EXPECT_FALSE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("http://foo.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://foo"))));
+
+  // It is schemeful-site level.
+  EXPECT_EQ(settings.GetCookieSetting(
+                /*url=*/GURL("https://www.foo.com"),
+                /*site_for_cookies=*/net::SiteForCookies(),
+                /*first_party_url=*/GURL("chrome://foo"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_ALLOW);
+  EXPECT_TRUE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("https://www.foo.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://foo"))));
+
+  // Wrong first-party.
+  EXPECT_EQ(settings.GetCookieSetting(
+                /* url=*/GURL("https://foo.com"),
+                /* site_for_cookies=*/net::SiteForCookies(),
+                /* first_party_url=*/GURL("chrome://bar"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_BLOCK);
+  EXPECT_FALSE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("https://foo.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://bar"))));
+
+  // bar.com is OK
+  EXPECT_EQ(settings.GetCookieSetting(
+                /* url=*/GURL("https://bar.com"),
+                /* site_for_cookies=*/net::SiteForCookies(),
+                /* first_party_url=*/GURL("chrome://foo"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_ALLOW);
+  EXPECT_TRUE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("https://bar.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://foo"))));
+
+  // baz.com is not
+  EXPECT_EQ(settings.GetCookieSetting(
+                /* url=*/GURL("https://baz.com"),
+                /* site_for_cookies=*/net::SiteForCookies(),
+                /* first_party_url=*/GURL("chrome://foo"),
+                net::CookieSettingOverrides()),
+            CONTENT_SETTING_BLOCK);
+  EXPECT_FALSE(settings.ShouldIgnoreSameSiteRestrictions(
+      /*url=*/GURL("https://baz.com"),
+      /*site_for_cookies=*/net::SiteForCookies(),
+      /*top_level_origin=*/url::Origin::Create(GURL("chrome://foo"))));
+}
+
 TEST_F(CookieSettingsTest, GetCookieSettingWithThirdPartyCookiesAllowedScheme) {
   CookieSettings settings;
   settings.set_third_party_cookies_allowed_schemes({"chrome-extension"});
Loading diff…

Original Bug Report

reported by vm...@google.com

SameSite cookie bypass via CookieAccessDelegate in URLRequestHttpJob

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: URLRequestHttpJob computes force_ignore_site_for_cookies by OR-ing a per-hop CookieAccessDelegate::ShouldIgnoreSameSiteRestrictions check over the NetworkDelegate decision. Because CookieAccessDelegate only inspects the current hop URL and lacks destination restrictions for origin grants such as chrome-untrusted://lens, requests gain universal SameSite=Strict and third-party cookie bypass across origin boundaries. This potentially allows a compromised untrusted WebUI renderer or redirected WebUI request to send and set SameSite=Strict cookies against arbitrary HTTPS origins.

Affected files:

  • net/url_request/url_request_http_job.cc
  • services/network/cookie_settings.cc
  • chrome/browser/net/profile_network_context_service.cc
  • net/device_bound_sessions/session.cc
  • net/cookies/cookie_access_delegate.h
  • services/network/cookie_access_delegate_impl.cc

Estimated timestamp from git blame: 2026-04-08

Summary

URLRequestHttpJob determines whether to bypass SameSite cookie restrictions (force_ignore_site_for_cookies) by consulting two mechanisms: ShouldForceIgnoreSiteForCookies(*request_) (via NetworkDelegate), and CookieAccessDelegate::ShouldIgnoreSameSiteRestrictions(...). The latter is evaluated per-hop without destination domain restrictions for origin grants, and its boolean result is OR-ed over the NetworkDelegate path. Consequently, origins granted SameSite access (notably chrome-untrusted://lens) can send and receive SameSite=Strict cookies against any HTTPS origin.

Root Cause Analysis

In net/url_request/url_request_http_job.cc:806-814 (AddCookieHeaderAndStart) and 1033-1041 (SaveCookiesAndNotifyHeadersComplete), the check is implemented as follows:

bool force_ignore_site_for_cookies =
    ShouldForceIgnoreSiteForCookies(*request_);
if (cookie_store->cookie_access_delegate() &&
    cookie_store->cookie_access_delegate()->ShouldIgnoreSameSiteRestrictions(
        request_->url(), request_->site_for_cookies(),
        request_->isolation_info().top_frame_origin().value_or(
            url::Origin()))) {
  force_ignore_site_for_cookies = true;
}

The delegate routes to CookieSettings::ShouldIgnoreSameSiteRestrictions (services/network/cookie_settings.cc:179-192):

bool CookieSettings::ShouldIgnoreSameSiteRestrictions(
    const GURL& url,
    const net::SiteForCookies& site_for_cookies,
    const url::Origin& top_level_origin) const {
  if (!url.SchemeIsCryptographic()) {
    return false;
  }
  if (secure_origin_cookies_allowed_schemes_.contains(
          top_level_origin.scheme()) &&
      !site_for_cookies.IsNull()) {
    return true;
  }
  return secure_origin_cookies_allowed_origins_.contains(top_level_origin);
}
  1. Unbounded Destination Grant: In chrome/browser/net/profile_network_context_service.cc:1137-1143, desktop builds append chrome-untrusted://lens to secure_origin_cookies_allowed_origins. In CookieSettings::ShouldIgnoreSameSiteRestrictions, Branch B only checks url.SchemeIsCryptographic() and secure_origin_cookies_allowed_origins_.contains(top_level_origin). There is no check verifying that the requested url belongs to an intended Google/Lens domain.
  2. Third-Party Cookie & Privacy Mode Bypass: CookieSettings::ShouldAlwaysAllowCookies (services/network/cookie_settings.cc:226-238) uses the same secure_origin_cookies_allowed_origins_ set to return true for all cryptographic URLs, leading CookieSettingsBase::GetCookieSettingInternal (components/content_settings/core/common/cookie_settings_base.cc:433-444) to return CONTENT_SETTING_ALLOW.
  3. Inclusive Sinks: Setting force_ignore_site_for_cookies = true forces cookie_util::ComputeSameSiteContextForRequest and ComputeSameSiteContextForResponse (net/cookies/cookie_util.cc:946-947, 997-998) to return MakeInclusive() (SAME_SITE_STRICT) and MakeInclusiveForSet() (SAME_SITE_LAX), bypassing SameSite restrictions for both reading and writing cookies.
  4. Redirect Chain Obliviousness: For chrome:// WebUI requests (Branch A), IsolationInfo::CreateForRedirect (net/base/isolation_info.cc:304-306) preserves top_frame_origin. A new URLRequestHttpJob evaluates ShouldIgnoreSameSiteRestrictions only on the target hop URL, ignoring intermediate cross-site transitions.

Potential Trigger Steps

(Note: These are potential steps based on code analysis; our automated tooling does not execute dynamic exploits).

Scenario A: Compromised chrome-untrusted://lens Renderer (Cross-Origin CSRF)

  1. A user triggers Google Lens on untrusted web content, opening the chrome-untrusted://lens side panel.
  2. Per Chromium’s security model (docs/webui/chrome_untrusted.md:19, 41), chrome-untrusted:// processes untrusted network content and is assumed to be compromisable, with the requirement that a compromised renderer should be no more privileged than an ordinary web renderer.
  3. Assuming renderer compromise of the Lens process, the attacker issues a subresource request: fetch("https://victim.example/endpoint", { method: "POST", mode: "no-cors", credentials: "include", body: ... }).
  4. In the network service, the request carries browser-bound IsolationInfo with top_frame_origin = chrome-untrusted://lens.
  5. CookieSettings::ShouldAlwaysAllowCookies bypasses 3PC blocking, and ShouldIgnoreSameSiteRestrictions evaluates to true for https://victim.example.
  6. URLRequestHttpJob::AddCookieHeaderAndStart sets force_ignore_site_for_cookies = true, attaching the user’s SameSite=Strict and SameSite=Lax cookies for victim.example (blind CSRF).
  7. In URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete, response Set-Cookie headers with SameSite=Strict are accepted and stored.

Scenario B: Cross-Site Redirect Re-Acquisition

  1. A chrome:// WebUI page (e.g., NTP) initiates a credentialed fetch to an endpoint.
  2. If the endpoint responds with an HTTP 302 redirecting to a cross-site target (https://victim.example), URLRequest::Redirect creates a new URLRequestHttpJob with the original top_frame_origin.
  3. The delegate check on the new hop re-grants force_ignore_site_for_cookies = true, attaching victim.example’s SameSite=Strict cookies despite the cross-site redirect.

Suggested Fix

  1. Restrict Destination URLs: Update CookieSettings::ShouldIgnoreSameSiteRestrictions and CookieSettings::ShouldAlwaysAllowCookies so that origin grants in secure_origin_cookies_allowed_origins_ require destination URLs to match an explicit destination allowlist (e.g., restricting Lens grants to *.google.com or *.gstatic.com).
  2. Make Delegate Redirect-Chain-Aware: Pass the full request_->url_chain() into CookieAccessDelegate::ShouldIgnoreSameSiteRestrictions and verify that all hops satisfy the authorization constraints before setting force_ignore_site_for_cookies = true.
  3. Migrate Untrusted WebUI: Address crbug.com/483614998 to eliminate the special SameSite exception for chrome-untrusted://lens.

Evaluated with Chrome root at commit: 796d95f11afe18561709217227313319d6660322


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