CVE-2026-113228
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forservices/network/cookie_manager.cc |
modified | |
GetHostIndexedContentSettingsservices/network/cookie_settings.cc |
modified | |
forservices/network/cookie_settings.cc |
modified | |
GURLservices/network/cookie_settings.h |
modified | |
TEST_Fservices/network/cookie_settings_unittest.cc |
modified |
Files Changed
content/public/browser/content_browser_client.cccontent/browser/devtools/devtools_url_loader_interceptor.ccservices/network/cookie_settings_unittest.ccchrome/browser/net/profile_network_context_service.cccontent/public/browser/content_browser_client.hservices/network/cookie_manager.ccservices/network/cookie_settings.hchrome/browser/chrome_content_browser_client.ccchrome/browser/chrome_content_browser_client.hservices/network/public/mojom/cookie_manager.mojomservices/network/cookie_settings.cc
Patch
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"});
Regression Test / PoC
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"});
Original Bug Report
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.ccservices/network/cookie_settings.ccchrome/browser/net/profile_network_context_service.ccnet/device_bound_sessions/session.ccnet/cookies/cookie_access_delegate.hservices/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);
}
- Unbounded Destination Grant: In
chrome/browser/net/profile_network_context_service.cc:1137-1143, desktop builds appendchrome-untrusted://lenstosecure_origin_cookies_allowed_origins. InCookieSettings::ShouldIgnoreSameSiteRestrictions, Branch B only checksurl.SchemeIsCryptographic()andsecure_origin_cookies_allowed_origins_.contains(top_level_origin). There is no check verifying that the requestedurlbelongs to an intended Google/Lens domain. - Third-Party Cookie & Privacy Mode Bypass:
CookieSettings::ShouldAlwaysAllowCookies(services/network/cookie_settings.cc:226-238) uses the samesecure_origin_cookies_allowed_origins_set to returntruefor all cryptographic URLs, leadingCookieSettingsBase::GetCookieSettingInternal(components/content_settings/core/common/cookie_settings_base.cc:433-444) to returnCONTENT_SETTING_ALLOW. - Inclusive Sinks: Setting
force_ignore_site_for_cookies = trueforcescookie_util::ComputeSameSiteContextForRequestandComputeSameSiteContextForResponse(net/cookies/cookie_util.cc:946-947, 997-998) to returnMakeInclusive()(SAME_SITE_STRICT) andMakeInclusiveForSet()(SAME_SITE_LAX), bypassing SameSite restrictions for both reading and writing cookies. - Redirect Chain Obliviousness: For
chrome://WebUI requests (Branch A),IsolationInfo::CreateForRedirect(net/base/isolation_info.cc:304-306) preservestop_frame_origin. A newURLRequestHttpJobevaluatesShouldIgnoreSameSiteRestrictionsonly 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)
- A user triggers Google Lens on untrusted web content, opening the
chrome-untrusted://lensside panel. - 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. - 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: ... }). - In the network service, the request carries browser-bound
IsolationInfowithtop_frame_origin = chrome-untrusted://lens. CookieSettings::ShouldAlwaysAllowCookiesbypasses 3PC blocking, andShouldIgnoreSameSiteRestrictionsevaluates totrueforhttps://victim.example.URLRequestHttpJob::AddCookieHeaderAndStartsetsforce_ignore_site_for_cookies = true, attaching the user’sSameSite=StrictandSameSite=Laxcookies forvictim.example(blind CSRF).- In
URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete, responseSet-Cookieheaders withSameSite=Strictare accepted and stored.
Scenario B: Cross-Site Redirect Re-Acquisition
- A
chrome://WebUI page (e.g., NTP) initiates a credentialed fetch to an endpoint. - If the endpoint responds with an HTTP 302 redirecting to a cross-site target (
https://victim.example),URLRequest::Redirectcreates a newURLRequestHttpJobwith the originaltop_frame_origin. - The delegate check on the new hop re-grants
force_ignore_site_for_cookies = true, attachingvictim.example’sSameSite=Strictcookies despite the cross-site redirect.
Suggested Fix
- Restrict Destination URLs: Update
CookieSettings::ShouldIgnoreSameSiteRestrictionsandCookieSettings::ShouldAlwaysAllowCookiesso that origin grants insecure_origin_cookies_allowed_origins_require destination URLs to match an explicit destination allowlist (e.g., restricting Lens grants to*.google.comor*.gstatic.com). - Make Delegate Redirect-Chain-Aware: Pass the full
request_->url_chain()intoCookieAccessDelegate::ShouldIgnoreSameSiteRestrictionsand verify that all hops satisfy the authorization constraints before settingforce_ignore_site_for_cookies = true. - Migrate Untrusted WebUI: Address
crbug.com/483614998to eliminate the special SameSite exception forchrome-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.