CVE-2026-79205
Overview
Background
- `NavigationURLLoaderImpl`
- the browser-process object that drives a navigation’s network load, building the URL loader factory and following server redirects.
- `allow-same-site-none-cookies` sandbox override
- a cookie-setting override (
net::CookieSettingOverride::kAllowSameSiteNoneCookiesInSandbox) that lets a sandboxed frame sendSameSite=Nonecookies when the frame’s origin matches each ancestor’s origin (or opaque precursor). - `ShouldAllowSameSiteNoneCookiesInSandbox`
- the predicate that decides whether that override applies, using the navigation’s tentative origin derived from the current request URL and the frame’s ancestor chain.
- Server redirect / `FollowRedirect`
- when a server responds 3xx, the same navigation loader is reused and
FollowRedirectupdates the request URL to the redirect target and restarts the load.
Root Cause Analysis
NavigationURLLoaderImpl evaluated ShouldAllowSameSiteNoneCookiesInSandbox a single time in its constructor, using the tentative origin derived from the initial request URL, and baked the resulting override into the network URLLoaderFactory via CreateNetworkLoaderFactory. On a server redirect the existing factory and loader were reused, so the override that was correct for the pre-redirect origin stayed in effect for the post-redirect request even when the new origin (e.g. after a.test → sub.a.test) would no longer pass the exact SchemeHostPort ancestor check. This violated the invariant that the sandbox SameSite=None cookie relaxation applies only when the target origin still matches every ancestor’s origin, letting SameSite=None cookies be attached to a cross-origin redirect target under third-party-cookie blocking.
The fix stores the value in allow_same_site_none_cookies_override_, passes it explicitly into CreateNetworkLoaderFactory, and in FollowRedirect recomputes the predicate for the updated URL; if it changed, it rebuilds the network factory with the new value and clears default_loader_used_ so Restart reissues the redirected request through the corrected factory. This is a no-op for navigations that never had the override.
FollowRedirect and rebuilds the loader factory when the decision flips.Attack Path
- Nest a sandboxed same-origin frame
An attacker page on
a.testembeds a sandboxeda.testiframe that in turn embeds a grandchild iframe, so the ancestor chain initially satisfies theSameSite=None-in-sandbox origin match. - Point the grandchild at a redirector
The grandchild navigates to an
a.testURL that server-redirects to a different origin such assub.a.test, still under the same navigation loader. - Reuse the stale override
Because the override was computed only from the initial
a.testorigin and the factory/loader were reused across the redirect, the redirected request tosub.a.teststill carrieskAllowSameSiteNoneCookiesInSandbox. - Send cookies that should be blocked
The redirected sandboxed context sends
SameSite=Nonecookies to the post-redirect origin even though its origin no longer matches each ancestor, bypassing third-party-cookie blocking for that request.
Impact Assessment
SameSite=None cookies to a redirected cross-origin destination that should have been denied them under third-party-cookie blocking, an incorrect-authorization/cookie-access issue decided in the browser process. Preconditions are a sandboxed iframe using the allow-same-site-none-cookies policy, a nested navigation, and a server redirect that changes the origin away from the ancestor match. The advisory classifies this as incorrect authorization in Network and reports no exploited-in-the-wild status; no memory-safety escalation is involved.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/loader/navigation_url_loader_impl.cc |
modified |
Files Changed
content/browser/content_security_policy_browsertest.cccontent/browser/loader/navigation_url_loader_impl.cccontent/browser/loader/navigation_url_loader_impl.h
Audit Directions
- Origin-derived decisions cached across redirectsAudit any security decision (cookie overrides, CORS/CORB state, isolation keys, permissions) computed from the initial request URL/origin and reused when a server redirect changes the origin.
- Factory/loader reuse on redirectReview code that rebuilds vs. reuses
URLLoaderFactory/loader objects onFollowRedirectto ensure per-origin parameters are re-derived rather than carried over. - SameSite/sandbox ancestor checksLook for other call sites of
ShouldAllowSameSiteNoneCookiesInSandboxand similar ancestor-origin predicates to confirm they are re-evaluated whenever the effective origin can change mid-navigation.
Patch
From 215b9995626a28eb38b01582a4b76cfc9e4f93a8 Mon Sep 17 00:00:00 2001
From: Keita Suzuki <suzukikeita@chromium.org>
Date: Mon, 06 Jul 2026 02:11:03 -0700
Subject: [PATCH] Re-evaluate sandbox SameSite=None cookie override on redirect
NavigationURLLoaderImpl computed the allow-same-site-none-cookies
sandbox cookie-setting override once in the constructor, using the
navigation's tentative origin derived from the initial request URL, and
baked it into the navigation URLLoaderFactory. On a server redirect the
existing factory and loader were reused so the override could remain in
effect even though the post-redirect origin would no longer pass the
SchemeHostPort-exact ancestor check.
Track the override value used to build the network factory and recompute
it in FollowRedirect() once the request URL has been updated. If the
value changes, recreate the network factory with the new value and clear
default_loader_used_ so Restart() resets the loader and the redirected
request is issued through the new factory. This is a no-op for
navigations that never had the override.
Add content_browsertests covering a sandboxed a.test iframe whose
grandchild navigates to a.test and is redirected to sub.a.test under
third-party-cookie blocking (verifying the override is not applied),
as well as when redirected to another valid URL on a.test (verifying
the override is preserved).
TAG=agy
CONV=3216e3b4-2222-4094-83a1-01bcd0318e0a
Bug: 518065628
Change-Id: I316ce02b5e387e78312eb46ceb1903151cc22d8e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8033640
Commit-Queue: Keita Suzuki <suzukikeita@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1657046}
---
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 2fab6d58..d004578 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -2164,9 +2164,12 @@
std::move(device_bound_session_observer),
std::move(accept_ch_frame_observer));
+ allow_same_site_none_cookies_override_ =
+ ShouldAllowSameSiteNoneCookiesInSandbox(*frame_tree_node);
network_loader_factory_ = CreateNetworkLoaderFactory(
browser_context_, storage_partition_, frame_tree_node,
- ukm::SourceIdObj::FromInt64(ukm_source_id_), &bypass_redirect_checks_);
+ ukm::SourceIdObj::FromInt64(ukm_source_id_), &bypass_redirect_checks_,
+ allow_same_site_none_cookies_override_);
}
// static
@@ -2250,7 +2253,8 @@
StoragePartitionImpl* storage_partition,
FrameTreeNode* frame_tree_node,
const ukm::SourceIdObj& ukm_id,
- bool* bypass_redirect_checks) {
+ bool* bypass_redirect_checks,
+ bool allow_same_site_none_cookies_override) {
mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>
header_client;
@@ -2282,7 +2286,7 @@
devtools_params.agent_host(), devtools_cookie_overrides);
net::CookieSettingOverrides cookie_overrides;
- if (ShouldAllowSameSiteNoneCookiesInSandbox(*frame_tree_node)) {
+ if (allow_same_site_none_cookies_override) {
// Include a CookieSettingOverride in the UrlLoaderFactoryParams for the
// frame's SharedURLLoaderFactory if the frame contains the
// `allow-same-site-none-cookies` value in its sandbox policy.
@@ -2363,6 +2367,27 @@
resource_request_->navigation_redirect_chain.push_back(
redirect_info_.new_url);
+ // The decision to apply the SameSite=None sandbox override depends on the
+ // navigation's tentative origin, which may change after a redirect. Recreate
+ // the network factory and reset the loader if the decision changes so the
+ // override is not applied to the redirected request.
+ if (FrameTreeNode* frame_tree_node =
+ FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
+ frame_tree_node && frame_tree_node->navigation_request()) {
+ const bool allow_same_site_none_cookies_override =
+ ShouldAllowSameSiteNoneCookiesInSandbox(*frame_tree_node);
+ if (allow_same_site_none_cookies_override !=
+ allow_same_site_none_cookies_override_) {
+ allow_same_site_none_cookies_override_ =
+ allow_same_site_none_cookies_override;
+ network_loader_factory_ = CreateNetworkLoaderFactory(
+ browser_context_, storage_partition_, frame_tree_node,
+ ukm::SourceIdObj::FromInt64(ukm_source_id_), &bypass_redirect_checks_,
+ allow_same_site_none_cookies_override_);
+ default_loader_used_ = false;
+ }
+ }
+
if (base::FeatureList::IsEnabled(
network::features::kOffloadAcceptCHFrameCheck)) {
const url::Origin new_origin = url::Origin::Create(resource_request().url);
diff --git a/content/browser/loader/navigation_url_loader_impl.h b/content/browser/loader/navigation_url_loader_impl.h
index 005ae43..8dcbce48 100644
--- a/content/browser/loader/navigation_url_loader_impl.h
+++ b/content/browser/loader/navigation_url_loader_impl.h
@@ -168,7 +168,8 @@
StoragePartitionImpl* storage_partition,
FrameTreeNode* frame_tree_node,
const ukm::SourceIdObj& ukm_id,
- bool* bypass_redirect_checks);
+ bool* bypass_redirect_checks,
+ bool allow_same_site_none_cookies_override);
void BindAndInterceptNonNetworkURLLoaderFactoryReceiver(
const GURL& url,
@@ -316,6 +317,11 @@
scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory_;
+ // Whether `network_loader_factory_` was created with the cookie-setting
+ // override that allows SameSite=None cookies in sandboxed contexts. Tracked
+ // so the factory can be recreated if the value changes after a redirect.
+ bool allow_same_site_none_cookies_override_ = false;
+
SubresourceLoaderParams subresource_loader_params_;
std::vector<std::unique_ptr<NavigationLoaderInterceptor>> interceptors_;
diff --git a/content/browser/content_security_policy_browsertest.cc b/content/browser/content_security_policy_browsertest.cc
index 76d835f72..fdcb1922 100644
--- a/content/browser/content_security_policy_browsertest.cc
+++ b/content/browser/content_security_policy_browsertest.cc
@@ -11,6 +11,7 @@
#include "base/memory/raw_ref.h"
#include "base/notreached.h"
#include "base/path_service.h"
+#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
@@ -447,6 +448,7 @@
namespace {
constexpr std::string_view kHostA = "a.test";
+constexpr std::string_view kHostSubA = "sub.a.test";
constexpr std::string_view kHostB = "b.test";
constexpr std::string_view kTopLevelPath = "/top-level.html";
@@ -795,4 +797,94 @@
EXPECT_EQ(FetchWithCredentials(grandchild_iframe, grandchild_iframe_url), "");
}
+IN_PROC_BROWSER_TEST_P(AllowSameSiteNoneCookiesContentSecurityPolicyBrowserTest,
+ NestedIframeNavigationCrossOriginRedirect) {
+ GURL top_level = https_server()->GetURL(kHostA, kTopLevelPath);
+ GURL middle_iframe_url = https_server()->GetURL(kHostA, kCrossSiteIframePath);
+ GURL grandchild_target_url =
+ https_server()->GetURL(kHostSubA, kCrossSiteIframePath);
+ GURL grandchild_redirect_url = https_server()->GetURL(
+ kHostA, "/server-redirect?" +
+ base::EscapeQueryParamValue(grandchild_target_url.spec(),
+ /*use_plus=*/false));
+
+ ASSERT_TRUE(SetCookie(web_contents()->GetBrowserContext(),
+ https_server()->GetURL(kHostSubA, kTopLevelPath),
+ "foo=bar;SameSite=None;Secure;"));
+
+ // Top a.test embeds a sandboxed a.test iframe which then embeds an inner
+ // iframe that navigates to a.test and is server-redirected to sub.a.test.
+ ASSERT_TRUE(NavigateToURL(shell(), top_level));
+ ASSERT_TRUE(
+ ExecJs(web_contents()->GetPrimaryMainFrame(),
+ JsReplace(R"(document.body.innerHTML =
+ '<iframe id="middle" src=$1 sandbox=$2></iframe>';)",
+ middle_iframe_url.spec(), sandbox_iframe_policy())));
+ WaitForLoadStop(web_contents());
+ RenderFrameHost* middle_iframe = ChildFrameAt(shell(), 0);
+
+ ASSERT_TRUE(ExecJs(middle_iframe, JsReplace(R"(document.body.innerHTML =
+ '<iframe id="grandchild" src=$1></iframe>';)",
+ grandchild_redirect_url.spec())));
+ WaitForLoadStop(web_contents());
+ RenderFrameHost* grandchild_iframe = ChildFrameAt(middle_iframe, 0);
+ ASSERT_TRUE(grandchild_iframe);
+ ASSERT_EQ(grandchild_iframe->GetLastCommittedURL(), grandchild_target_url);
+
+ // The override does not apply to the redirected navigation request because
+ // the grandchild's origin no longer matches each ancestor's origin (or
+ // precursor) after the redirect to sub.a.test.
+ EXPECT_FALSE(grandchild_iframe->GetCookieSettingOverrides().Has(
+ net::CookieSettingOverride::kAllowSameSiteNoneCookiesInSandbox));
+ EXPECT_EQ(EvalJs(grandchild_iframe, "document.body.textContent"), "");
+ EXPECT_EQ(FetchWithCredentials(grandchild_iframe, grandchild_target_url), "");
+}
+
+IN_PROC_BROWSER_TEST_P(AllowSameSiteNoneCookiesContentSecurityPolicyBrowserTest,
+ NestedIframeNavigationSameOriginRedirect) {
+ GURL top_level = https_server()->GetURL(kHostA, kTopLevelPath);
+ GURL middle_iframe_url = https_server()->GetURL(kHostA, kCrossSiteIframePath);
+ GURL grandchild_target_url =
+ https_server()->GetURL(kHostA, kCrossSiteIframePath);
+ GURL grandchild_redirect_url = https_server()->GetURL(
+ kHostA, "/server-redirect?" +
+ base::EscapeQueryParamValue(grandchild_target_url.spec(),
+ /*use_plus=*/false));
+
+ ASSERT_TRUE(SetCookie(web_contents()->GetBrowserContext(),
+ https_server()->GetURL(kHostA, kTopLevelPath),
+ "foo=bar;SameSite=None;Secure;"));
+
+ // Top a.test embeds a sandboxed a.test iframe which then embeds an inner
+ // iframe that navigates to a.test and is server-redirected to another
+ // a.test URL.
+ ASSERT_TRUE(NavigateToURL(shell(), top_level));
+ ASSERT_TRUE(
+ ExecJs(web_contents()->GetPrimaryMainFrame(),
+ JsReplace(R"(document.body.innerHTML =
+ '<iframe id="middle" src=$1 sandbox=$2></iframe>';)",
+ middle_iframe_url.spec(), sandbox_iframe_policy())));
+ WaitForLoadStop(web_contents());
+ RenderFrameHost* middle_iframe = ChildFrameAt(shell(), 0);
+
+ ASSERT_TRUE(ExecJs(middle_iframe, JsReplace(R"(document.body.innerHTML =
+ '<iframe id="grandchild" src=$1></iframe>';)",
+ grandchild_redirect_url.spec())));
+ WaitForLoadStop(web_contents());
+ RenderFrameHost* grandchild_iframe = ChildFrameAt(middle_iframe, 0);
+ ASSERT_TRUE(grandchild_iframe);
+ ASSERT_EQ(grandchild_iframe->GetLastCommittedURL(), grandchild_target_url);
+
+ // The override applies to the redirected navigation request because the
+ // grandchild's origin matches each ancestor's origin (or precursor) both
+ // before and after the redirect on a.test.
+ EXPECT_EQ(grandchild_iframe->GetCookieSettingOverrides().Has(
+ net::CookieSettingOverride::kAllowSameSiteNoneCookiesInSandbox),
+ include_allow_same_site_none_cookies());
+ EXPECT_EQ(EvalJs(grandchild_iframe, "document.body.textContent"),
+ include_allow_same_site_none_cookies() ? "foo=bar" : "");
+ EXPECT_EQ(FetchWithCredentials(grandchild_iframe, grandchild_target_url),
+ include_allow_same_site_none_cookies() ? "foo=bar" : "");
+}
+
} // namespace content
Regression Test / PoC
diff --git a/content/browser/content_security_policy_browsertest.cc b/content/browser/content_security_policy_browsertest.cc
index 76d835f72..fdcb1922 100644
--- a/content/browser/content_security_policy_browsertest.cc
+++ b/content/browser/content_security_policy_browsertest.cc
@@ -11,6 +11,7 @@
#include "base/memory/raw_ref.h"
#include "base/notreached.h"
#include "base/path_service.h"
+#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_feature_list.h"
@@ -447,6 +448,7 @@
namespace {
constexpr std::string_view kHostA = "a.test";
+constexpr std::string_view kHostSubA = "sub.a.test";
constexpr std::string_view kHostB = "b.test";
constexpr std::string_view kTopLevelPath = "/top-level.html";
@@ -795,4 +797,94 @@
EXPECT_EQ(FetchWithCredentials(grandchild_iframe, grandchild_iframe_url), "");
}
+IN_PROC_BROWSER_TEST_P(AllowSameSiteNoneCookiesContentSecurityPolicyBrowserTest,
+ NestedIframeNavigationCrossOriginRedirect) {
+ GURL top_level = https_server()->GetURL(kHostA, kTopLevelPath);
+ GURL middle_iframe_url = https_server()->GetURL(kHostA, kCrossSiteIframePath);
+ GURL grandchild_target_url =
+ https_server()->GetURL(kHostSubA, kCrossSiteIframePath);
+ GURL grandchild_redirect_url = https_server()->GetURL(
+ kHostA, "/server-redirect?" +
+ base::EscapeQueryParamValue(grandchild_target_url.spec(),
+ /*use_plus=*/false));
+
+ ASSERT_TRUE(SetCookie(web_contents()->GetBrowserContext(),
+ https_server()->GetURL(kHostSubA, kTopLevelPath),
+ "foo=bar;SameSite=None;Secure;"));
+
+ // Top a.test embeds a sandboxed a.test iframe which then embeds an inner
+ // iframe that navigates to a.test and is server-redirected to sub.a.test.
+ ASSERT_TRUE(NavigateToURL(shell(), top_level));
+ ASSERT_TRUE(
+ ExecJs(web_contents()->GetPrimaryMainFrame(),
+ JsReplace(R"(document.body.innerHTML =
+ '<iframe id="middle" src=$1 sandbox=$2></iframe>';)",
+ middle_iframe_url.spec(), sandbox_iframe_policy())));
+ WaitForLoadStop(web_contents());
+ RenderFrameHost* middle_iframe = ChildFrameAt(shell(), 0);
+
+ ASSERT_TRUE(ExecJs(middle_iframe, JsReplace(R"(document.body.innerHTML =
+ '<iframe id="grandchild" src=$1></iframe>';)",
+ grandchild_redirect_url.spec())));
+ WaitForLoadStop(web_contents());
+ RenderFrameHost* grandchild_iframe = ChildFrameAt(middle_iframe, 0);
+ ASSERT_TRUE(grandchild_iframe);
+ ASSERT_EQ(grandchild_iframe->GetLastCommittedURL(), grandchild_target_url);
+
+ // The override does not apply to the redirected navigation request because
+ // the grandchild's origin no longer matches each ancestor's origin (or
+ // precursor) after the redirect to sub.a.test.
+ EXPECT_FALSE(grandchild_iframe->GetCookieSettingOverrides().Has(
+ net::CookieSettingOverride::kAllowSameSiteNoneCookiesInSandbox));
+ EXPECT_EQ(EvalJs(grandchild_iframe, "document.body.textContent"), "");
+ EXPECT_EQ(FetchWithCredentials(grandchild_iframe, grandchild_target_url), "");
+}
+
+IN_PROC_BROWSER_TEST_P(AllowSameSiteNoneCookiesContentSecurityPolicyBrowserTest,
+ NestedIframeNavigationSameOriginRedirect) {
+ GURL top_level = https_server()->GetURL(kHostA, kTopLevelPath);
+ GURL middle_iframe_url = https_server()->GetURL(kHostA, kCrossSiteIframePath);
+ GURL grandchild_target_url =
+ https_server()->GetURL(kHostA, kCrossSiteIframePath);
+ GURL grandchild_redirect_url = https_server()->GetURL(
+ kHostA, "/server-redirect?" +
+ base::EscapeQueryParamValue(grandchild_target_url.spec(),
+ /*use_plus=*/false));
+
+ ASSERT_TRUE(SetCookie(web_contents()->GetBrowserContext(),
+ https_server()->GetURL(kHostA, kTopLevelPath),
+ "foo=bar;SameSite=None;Secure;"));
+
+ // Top a.test embeds a sandboxed a.test iframe which then embeds an inner
+ // iframe that navigates to a.test and is server-redirected to another
+ // a.test URL.
+ ASSERT_TRUE(NavigateToURL(shell(), top_level));
+ ASSERT_TRUE(
+ ExecJs(web_contents()->GetPrimaryMainFrame(),
+ JsReplace(R"(document.body.innerHTML =
+ '<iframe id="middle" src=$1 sandbox=$2></iframe>';)",
+ middle_iframe_url.spec(), sandbox_iframe_policy())));
+ WaitForLoadStop(web_contents());
+ RenderFrameHost* middle_iframe = ChildFrameAt(shell(), 0);
+
+ ASSERT_TRUE(ExecJs(middle_iframe, JsReplace(R"(document.body.innerHTML =
+ '<iframe id="grandchild" src=$1></iframe>';)",
+ grandchild_redirect_url.spec())));
+ WaitForLoadStop(web_contents());
+ RenderFrameHost* grandchild_iframe = ChildFrameAt(middle_iframe, 0);
+ ASSERT_TRUE(grandchild_iframe);
+ ASSERT_EQ(grandchild_iframe->GetLastCommittedURL(), grandchild_target_url);
+
+ // The override applies to the redirected navigation request because the
+ // grandchild's origin matches each ancestor's origin (or precursor) both
+ // before and after the redirect on a.test.
+ EXPECT_EQ(grandchild_iframe->GetCookieSettingOverrides().Has(
+ net::CookieSettingOverride::kAllowSameSiteNoneCookiesInSandbox),
+ include_allow_same_site_none_cookies());
+ EXPECT_EQ(EvalJs(grandchild_iframe, "document.body.textContent"),
+ include_allow_same_site_none_cookies() ? "foo=bar" : "");
+ EXPECT_EQ(FetchWithCredentials(grandchild_iframe, grandchild_target_url),
+ include_allow_same_site_none_cookies() ? "foo=bar" : "");
+}
+
} // namespace content
Original Bug Report
SameSite Cookie Override Bypass via Redirects in Sandboxed Navigation
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 potential logic vulnerability in NavigationURLLoaderImpl fails to re-evaluate sandbox cookie overrides on redirects. This potentially allows a sandboxed iframe to bypass origin-level cookie restrictions when redirected to same-site, cross-origin targets.
Affected files:
content/browser/loader/navigation_url_loader_impl.cccontent/browser/renderer_host/render_frame_host_impl.cccomponents/content_settings/core/common/cookie_settings_base.cc
Estimated timestamp from git blame: 2025-01-23
Summary
A potential security logic vulnerability in the cookie sandbox-override mechanism allows sandboxed iframes to bypass origin-level cookie restrictions when redirected to a same-site, cross-origin destination. This issue stems from NavigationURLLoaderImpl evaluating the cookie override exactly once when the navigation begins, and subsequently reusing the same URLLoaderFactory (and its overrides) across subsequent redirects without recheck.
Root Cause Analysis
There are two potential core logic errors:
- Use of Stale State during Start: In
content/browser/loader/navigation_url_loader_impl.cc,ShouldAllowSameSiteNoneCookiesInSandbox(lines 487–502) evaluates whether to grant the cookie overridekAllowSameSiteNoneCookiesInSandboxbased onframe_tree_node.current_frame_host(). This queries the sandbox configuration (active_sandbox_flags()) of the currently committed (old) document rather than the pending/new navigation state. - Persistence across Redirects: When the navigation starts,
ShouldAllowSameSiteNoneCookiesInSandboxperforms an origin-level check usingAncestorsAllowSameSiteNoneCookiesOverride(...)on the initial request URL origin. If this initial destination is same-origin with the frame ancestors, the override is granted and baked into theURLLoaderFactoryparameters. During redirects, the same factory is reused. While some overrides (likekStorageAccessGrantEligible) are explicitly removed on cross-origin redirects insideURLLoader::FollowRedirect(defined inservices/network/url_loader.cc:1039-1046),kAllowSameSiteNoneCookiesInSandboxis not. However, the Network Service re-verification check (CookieSettingsBase::IsAllowedBySandboxValueinsidecomponents/content_settings/core/common/cookie_settings_base.cc:336) only enforces a site-level match (SchemefulSite::IsSameSite) on the redirect target URL against the top-level site. This effectively downgrades the browser’s origin-level ancestor gate to a site-level gate.
Potential Reproduction Steps
(Note: These are potential steps, as our testing tools do not run active execution environments).
- Set a
SameSite=None; Securecookie forhttps://sub.a.example. - Load a top-level page
https://a.example/top.htmlwhich embeds a sandboxed iframe:<iframe sandbox="allow-scripts allow-same-site-none-cookies" src="https://a.example/middle.html">. - Inside
middle.html(which has an opaque origin with precursora.example), embed an inner iframe pointing tohttps://a.example/redirect. https://a.example/redirectresponds with an HTTP302redirecting tohttps://sub.a.example/target.- Observe if the request to
sub.a.examplecarries theSameSite=Nonecookie. If the navigation had gone directly tosub.a.examplefrom the same sandboxed context, the cookie would have been blocked becausesub.a.exampleis cross-origin to the parent’s precursora.example(failing the origin-level ancestor check).
Suggested Fix
To address this potential issue, consider:
- Ensuring that
URLLoader::FollowRedirectinservices/network/url_loader.ccremovesnet::CookieSettingOverride::kAllowSameSiteNoneCookiesInSandboxfrom the request overrides when the redirect is cross-origin (matching the behavior ofkStorageAccessGrantEligible). - Modifying
ShouldAllowSameSiteNoneCookiesInSandboxto evaluate sandbox constraints against the pending navigation state/target rather than relying on the initial URL and stale frame host state.
Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040
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.