CVE-2026-87531
Overview
Background
- `CorsURLLoader`
- the network-service loader in
services/network/cors/cors_url_loader.ccthat enforces CORS by filtering/tagging responses before forwarding them to the requesting client. - `FetchResponseType`
- the enum classifying a response as
kBasic(same-origin),kCors,kOpaque, orkOpaqueRedirect; onlykBasicis fully trusted to expose all response metadata cross-origin. - `URLResponseHead::was_cookie_in_request`
- a response-head field derived from the request’s cookie jar (not the server’s reply) that records whether a cookie was sent with the request.
- Sibling reset fields
device_bound_session_usageanddid_use_server_http_auth, request-side metadata thatCorsURLLoaderalready scrubs (tokUnknown/false) for non-kBasicresponses to avoid leaking request state cross-origin.
Root Cause Analysis
For non-kBasic responses (opaque and CORS-filtered), CorsURLLoader scrubbed request-derived metadata before forwarding the response_head to the client, but the scrubbing block reset only device_bound_session_usage and did_use_server_http_auth and left was_cookie_in_request untouched. Because was_cookie_in_request is derived from the request’s cookie jar rather than the server response (per the commit message), passing it through unchanged violated the invariant that cross-origin (opaque/CORS-filtered) responses must not surface request-side state to the requesting context.
The fix adds response_head->was_cookie_in_request = false; in the same non-kBasic scrubbing block at all three forwarding sites, so the field is cleared for opaque and CORS-filtered responses while same-origin (kBasic) responses still surface the real value. The added CorsURLLoaderTest cases confirm the intended behavior: kBasic keeps was_cookie_in_request == true, while kOpaque, kCors, and kOpaqueRedirect all report false.
was_cookie_in_request) was added to URLResponseHead but not to the set of fields CorsURLLoader zeroes for non-kBasic responses, so it leaked past the CORS filter; the fix simply extends that reset to cover the field, matching its two siblings.Attack Path
- Issue a cross-origin request
An attacker page makes a no-cors or CORS request to a victim origin (e.g.
https://other.example.com/foo.png), which yields a non-kBasic(kOpaque/kCors) response as shown by theWasCookieInRequestCrossOriginNoCorsand...Corstests. - Cookie state populates the field
Because
was_cookie_in_requestis derived from the request’s cookie jar (per the commit message), the field reflects whether the browser attached a cookie to that cross-origin request, independent of the server’s reply. - Unscrubbed value reaches the client
Before the patch,
CorsURLLoaderforwarded theresponse_headto the client withwas_cookie_in_requestunchanged for non-kBasicresponses, so the requesting (cross-origin) context could observe whether a cookie was sent — including on redirect-forwarding paths (WasCookieInRequestCrossOriginNoCorsRedirect,WasCookieInRequestManualRedirect). - Infer victim login/cookie state The attacker uses the leaked bit as an oracle for whether the user has cookies for the victim origin, i.e. a cross-origin login/session-presence side channel. (inferred — the diff proves only that the bit was exposed, not a specific downstream exploitation technique.)
Impact Assessment
(inferred), and exploited-in-the-wild status is not stated by the advisory.Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fservices/network/cors/cors_url_loader_unittest.cc |
modified |
Files Changed
services/network/cors/cors_url_loader.ccservices/network/cors/cors_url_loader_unittest.cc
Audit Directions
- Incomplete metadata scrub listsWherever a loader/filter resets a group of request-derived response-head fields for non-trusted response types, audit every field added to
URLResponseHead(or similar structs) to confirm it was also added to each scrub site, not just some. - Response-type gating on request-derived dataLook for any request-side state (cookie, auth, session, device-bound) surfaced on
URLResponseHeadand verify it is cleared forkOpaque/kCors/kOpaqueRedirectand only exposed forkBasic. - Multiple forwarding paths sharing one invariantWhen the same reset must hold across
OnReceiveResponseand everyOnReceiveRedirectpath, check that all forwarding sites apply it consistently, since a field can be scrubbed in one path and leaked in another.
Patch
From 0e2e868410f4043eefa8de364a5b5f092a3c513b Mon Sep 17 00:00:00 2001
From: Kenichi Ishibashi <bashi@chromium.org>
Date: Thu, 30 Jul 2026 00:20:19 -0700
Subject: [PATCH] Clear was_cookie_in_request for non-basic responses
URLResponseHead.was_cookie_in_request is derived from the request's
cookie jar rather than the server response. CorsURLLoader was passing it
through to the client unchanged for opaque and CORS-filtered responses,
alongside the two sibling fields it already resets
(device_bound_session_usage and did_use_server_http_auth).
Reset the field to false whenever the response type is not kBasic, in
OnReceiveResponse and both OnReceiveRedirect forwarding paths, matching
the treatment of the sibling fields. Same-origin requests still surface
the real value.
Add CorsURLLoaderTest coverage mirroring the existing
DidUseServerHttpAuth* cases.
Note that this affects the following Blink UseCounter.
- WebFeature::kUndeferrableThirdPartySubresourceRequestWithCookie
Bug: 533597592
Change-Id: I15ba2c635618d1674aed2119e556b869b6d8c714
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8172883
Commit-Queue: Kenichi Ishibashi <bashi@chromium.org>
Reviewed-by: Takashi Toyoshima <toyoshim@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1670874}
---
diff --git a/services/network/cors/cors_url_loader.cc b/services/network/cors/cors_url_loader.cc
index 313c1cb..33b672a 100644
--- a/services/network/cors/cors_url_loader.cc
+++ b/services/network/cors/cors_url_loader.cc
@@ -689,6 +689,7 @@
response_head->device_bound_session_usage =
mojom::DeviceBoundSessionUsage::kUnknown;
response_head->did_use_server_http_auth = false;
+ response_head->was_cookie_in_request = false;
}
forwarding_client_->OnReceiveResponse(
@@ -768,6 +769,7 @@
response_head->device_bound_session_usage =
mojom::DeviceBoundSessionUsage::kUnknown;
response_head->did_use_server_http_auth = false;
+ response_head->was_cookie_in_request = false;
forwarding_client_->OnReceiveRedirect(censored_redirect_info,
std::move(response_head));
return;
@@ -847,6 +849,7 @@
response_head->device_bound_session_usage =
mojom::DeviceBoundSessionUsage::kUnknown;
response_head->did_use_server_http_auth = false;
+ response_head->was_cookie_in_request = false;
}
forwarding_client_->OnReceiveRedirect(redirect_info,
std::move(response_head));
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index 4032e4c..41414ce 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -703,6 +703,136 @@
EXPECT_FALSE(client().response_head()->did_use_server_http_auth);
}
+TEST_F(CorsURLLoaderTest, WasCookieInRequestSameOrigin) {
+ const GURL origin("https://example.com");
+ const GURL url("https://example.com/foo.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kNoCors);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 200 OK\nContent-Type: image/png\n");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveResponse(std::move(response));
+ NotifyLoaderClientOnComplete(net::OK);
+
+ RunUntilComplete();
+
+ ASSERT_TRUE(client().has_received_response());
+ EXPECT_EQ(mojom::FetchResponseType::kBasic,
+ client().response_head()->response_type);
+ EXPECT_TRUE(client().response_head()->was_cookie_in_request);
+}
+
+TEST_F(CorsURLLoaderTest, WasCookieInRequestCrossOriginNoCors) {
+ const GURL origin("https://example.com");
+ const GURL url("https://other.example.com/foo.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kNoCors);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 200 OK\nContent-Type: image/png\n");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveResponse(std::move(response));
+ NotifyLoaderClientOnComplete(net::OK);
+
+ RunUntilComplete();
+
+ ASSERT_TRUE(client().has_received_response());
+ EXPECT_EQ(mojom::FetchResponseType::kOpaque,
+ client().response_head()->response_type);
+ EXPECT_FALSE(client().response_head()->was_cookie_in_request);
+}
+
+TEST_F(CorsURLLoaderTest, WasCookieInRequestCrossOriginCors) {
+ const GURL origin("https://example.com");
+ const GURL url("https://other.example.com/foo.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kCors);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 200 OK\nContent-Type: image/png\n");
+ response->headers->SetHeader("Access-Control-Allow-Origin",
+ "https://example.com");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveResponse(std::move(response));
+ NotifyLoaderClientOnComplete(net::OK);
+
+ RunUntilComplete();
+
+ ASSERT_TRUE(client().has_received_response());
+ EXPECT_EQ(mojom::FetchResponseType::kCors,
+ client().response_head()->response_type);
+ EXPECT_FALSE(client().response_head()->was_cookie_in_request);
+}
+
+TEST_F(CorsURLLoaderTest, WasCookieInRequestSameOriginRedirect) {
+ const GURL origin("https://example.com");
+ const GURL url("https://example.com/foo.png");
+ const GURL new_url("https://example.com/bar.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kNoCors);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 301 Moved Permanently\n");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveRedirect(CreateRedirectInfo(301, "GET", new_url),
+ std::move(response));
+ RunUntilRedirectReceived();
+
+ ASSERT_TRUE(client().has_received_redirect());
+ EXPECT_EQ(mojom::FetchResponseType::kBasic,
+ client().response_head()->response_type);
+ EXPECT_TRUE(client().response_head()->was_cookie_in_request);
+}
+
+TEST_F(CorsURLLoaderTest, WasCookieInRequestCrossOriginNoCorsRedirect) {
+ const GURL origin("https://example.com");
+ const GURL url("https://other.example.com/foo.png");
+ const GURL new_url("https://other.example.com/bar.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kNoCors);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 301 Moved Permanently\n");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveRedirect(CreateRedirectInfo(301, "GET", new_url),
+ std::move(response));
+ RunUntilRedirectReceived();
+
+ ASSERT_TRUE(client().has_received_redirect());
+ EXPECT_EQ(mojom::FetchResponseType::kOpaque,
+ client().response_head()->response_type);
+ EXPECT_FALSE(client().response_head()->was_cookie_in_request);
+}
+
+TEST_F(CorsURLLoaderTest, WasCookieInRequestManualRedirect) {
+ const GURL origin("https://example.com");
+ const GURL url("https://example.com/foo.png");
+ const GURL new_url("https://example.com/bar.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kNavigate,
+ mojom::RedirectMode::kManual,
+ mojom::CredentialsMode::kInclude);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 301 Moved Permanently\n");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveRedirect(CreateRedirectInfo(301, "GET", new_url),
+ std::move(response));
+ RunUntilRedirectReceived();
+
+ ASSERT_TRUE(client().has_received_redirect());
+ EXPECT_EQ(mojom::FetchResponseType::kOpaqueRedirect,
+ client().response_head()->response_type);
+ EXPECT_FALSE(client().response_head()->was_cookie_in_request);
+}
+
TEST_F(CorsURLLoaderTest,
CrossOriginRequestFetchRequestWithCorsModeButMismatchedCorsHeader) {
const GURL origin("https://example.com");
Regression Test / PoC
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index 4032e4c..41414ce 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -703,6 +703,136 @@
EXPECT_FALSE(client().response_head()->did_use_server_http_auth);
}
+TEST_F(CorsURLLoaderTest, WasCookieInRequestSameOrigin) {
+ const GURL origin("https://example.com");
+ const GURL url("https://example.com/foo.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kNoCors);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 200 OK\nContent-Type: image/png\n");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveResponse(std::move(response));
+ NotifyLoaderClientOnComplete(net::OK);
+
+ RunUntilComplete();
+
+ ASSERT_TRUE(client().has_received_response());
+ EXPECT_EQ(mojom::FetchResponseType::kBasic,
+ client().response_head()->response_type);
+ EXPECT_TRUE(client().response_head()->was_cookie_in_request);
+}
+
+TEST_F(CorsURLLoaderTest, WasCookieInRequestCrossOriginNoCors) {
+ const GURL origin("https://example.com");
+ const GURL url("https://other.example.com/foo.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kNoCors);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 200 OK\nContent-Type: image/png\n");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveResponse(std::move(response));
+ NotifyLoaderClientOnComplete(net::OK);
+
+ RunUntilComplete();
+
+ ASSERT_TRUE(client().has_received_response());
+ EXPECT_EQ(mojom::FetchResponseType::kOpaque,
+ client().response_head()->response_type);
+ EXPECT_FALSE(client().response_head()->was_cookie_in_request);
+}
+
+TEST_F(CorsURLLoaderTest, WasCookieInRequestCrossOriginCors) {
+ const GURL origin("https://example.com");
+ const GURL url("https://other.example.com/foo.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kCors);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 200 OK\nContent-Type: image/png\n");
+ response->headers->SetHeader("Access-Control-Allow-Origin",
+ "https://example.com");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveResponse(std::move(response));
+ NotifyLoaderClientOnComplete(net::OK);
+
+ RunUntilComplete();
+
+ ASSERT_TRUE(client().has_received_response());
+ EXPECT_EQ(mojom::FetchResponseType::kCors,
+ client().response_head()->response_type);
+ EXPECT_FALSE(client().response_head()->was_cookie_in_request);
+}
+
+TEST_F(CorsURLLoaderTest, WasCookieInRequestSameOriginRedirect) {
+ const GURL origin("https://example.com");
+ const GURL url("https://example.com/foo.png");
+ const GURL new_url("https://example.com/bar.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kNoCors);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 301 Moved Permanently\n");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveRedirect(CreateRedirectInfo(301, "GET", new_url),
+ std::move(response));
+ RunUntilRedirectReceived();
+
+ ASSERT_TRUE(client().has_received_redirect());
+ EXPECT_EQ(mojom::FetchResponseType::kBasic,
+ client().response_head()->response_type);
+ EXPECT_TRUE(client().response_head()->was_cookie_in_request);
+}
+
+TEST_F(CorsURLLoaderTest, WasCookieInRequestCrossOriginNoCorsRedirect) {
+ const GURL origin("https://example.com");
+ const GURL url("https://other.example.com/foo.png");
+ const GURL new_url("https://other.example.com/bar.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kNoCors);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 301 Moved Permanently\n");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveRedirect(CreateRedirectInfo(301, "GET", new_url),
+ std::move(response));
+ RunUntilRedirectReceived();
+
+ ASSERT_TRUE(client().has_received_redirect());
+ EXPECT_EQ(mojom::FetchResponseType::kOpaque,
+ client().response_head()->response_type);
+ EXPECT_FALSE(client().response_head()->was_cookie_in_request);
+}
+
+TEST_F(CorsURLLoaderTest, WasCookieInRequestManualRedirect) {
+ const GURL origin("https://example.com");
+ const GURL url("https://example.com/foo.png");
+ const GURL new_url("https://example.com/bar.png");
+ CreateLoaderAndStart(origin, url, mojom::RequestMode::kNavigate,
+ mojom::RedirectMode::kManual,
+ mojom::CredentialsMode::kInclude);
+ RunUntilCreateLoaderAndStartCalled();
+
+ auto response = mojom::URLResponseHead::New();
+ response->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 301 Moved Permanently\n");
+ response->was_cookie_in_request = true;
+ NotifyLoaderClientOnReceiveRedirect(CreateRedirectInfo(301, "GET", new_url),
+ std::move(response));
+ RunUntilRedirectReceived();
+
+ ASSERT_TRUE(client().has_received_redirect());
+ EXPECT_EQ(mojom::FetchResponseType::kOpaqueRedirect,
+ client().response_head()->response_type);
+ EXPECT_FALSE(client().response_head()->was_cookie_in_request);
+}
+
TEST_F(CorsURLLoaderTest,
CrossOriginRequestFetchRequestWithCorsModeButMismatchedCorsHeader) {
const GURL origin("https://example.com");
Original Bug Report
Cross-origin cookie state leak via URLResponseHead.was_cookie_in_request
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: The URLResponseHead.was_cookie_in_request field is derived from client-side cookie-jar state but is not cleared during cross-origin response sanitization in the network process. A compromised renderer can potentially read this field directly from the Mojo URLResponseHead struct for opaque cross-origin or ORB-blocked responses. This creates a potential 1-bit cross-origin oracle that leaks cookie existence and allows cookie path-prefix enumeration.
Affected files:
services/network/cors/cors_url_loader.ccservices/network/url_loader_util.ccservices/network/orb/orb_api.cc
Estimated timestamp from git blame: 2020-09-24
Description
When a network response is processed, url_loader_util::BuildResponseHead() populates the was_cookie_in_request field in URLResponseHead to indicate whether a cookie was sent with the corresponding request:
// services/network/url_loader_util.cc
response->was_cookie_in_request = std::ranges::any_of(
url_request.maybe_sent_cookies(),
[](const auto& cookie_with_access_result) {
return cookie_with_access_result.access_result.status.IsInclude();
});
Under Chrome’s post-compromise Site Isolation security model, a compromised renderer must not be able to obtain sensitive cross-origin data. To prevent cross-origin leaks, CorsURLLoader sanitizes certain client-side-state fields (such as did_use_server_http_auth and device_bound_session_usage) when forwarding non-kBasic (cross-origin) responses to the renderer:
// services/network/cors/cors_url_loader.cc
if (response_head->response_type != mojom::FetchResponseType::kBasic) {
response_head->device_bound_session_usage =
mojom::DeviceBoundSessionUsage::kUnknown;
response_head->did_use_server_http_auth = false;
}
However, was_cookie_in_request is not cleared by this block. The identical two-field-only sanitization also repeats on both redirect forwarding paths in cors_url_loader.cc (at kOpaqueRedirect early-returns and normal redirects). Furthermore, ORB’s blocked-response header strip (SanitizeBlockedResponseHeaders in services/network/orb/orb_api.cc) also leaves the field intact.
Consequently, even if a cross-origin response is opaque or blocked by ORB, the was_cookie_in_request bit is forwarded untouched to the renderer process over Mojo inside the URLResponseHead structure.
Potential Impact
A compromised renderer can potentially abuse this behavior to obtain a 1-bit oracle of whether a specific SameSite=None cookie exists in the user’s profile for any target cross-origin host. This leads to:
- Cross-origin visit/login-state disclosure: Verifying if an unpartitioned cookie exists for a specific site reveals if the user has previously authenticated to/visited that site.
- Cookie-
Pathprefix enumeration: Probing different resource paths allows the attacker to determine the restricted path of a cookie via path-matching results (disclosing sensitive site context).
This vulnerability is relevant under configurations where cross-site cookies are allowed (e.g., prior to full 3PCD rollout, under enterprise policies, per-site cookie exceptions, Related Website Sets, or Storage Access API grants).
Potential Step-by-Step Exploitation Scenario
Note: The following are suggested/potential steps, as our tooling agent does not currently have the ability to run code or construct a live proof-of-concept.
- A victim has previously logged into
https://victim.com, which set an unpartitionedSameSite=None; Secure; Path=/admincookie. - The user visits an attacker-controlled site,
https://evil.com, which runs in its own sandboxed renderer process but is assumed to be compromised (post-compromise threat model). - The compromised renderer constructs a cross-origin subresource request via Mojo targeting
https://victim.com/admin/probe.pngwithmode=kNoCorsandcredentials_mode=kInclude. - The network process validates and executes the request. Since credentials are included and third-party cookies are allowed for the pair, the unpartitioned cookie is attached.
url_request.maybe_sent_cookies()records this, andBuildResponseHead()setswas_cookie_in_request = true. - The response is returned. Since it is cross-origin,
CorsURLLoadermarks the response type askOpaqueand sanitizes the response head, but leaveswas_cookie_in_request == trueuntouched. - The compromised renderer intercepts the
OnReceiveResponseMojo message and directly inspectsURLResponseHead.was_cookie_in_request. Observingtruereveals the cookie’s existence. - The attacker repeats this fetch targeting
https://victim.com/other/probe.pngand observeswas_cookie_in_request == false, thereby learning both the login status and the specific cookie path restriction (Path=/admin).
Suggested Fix
Sanitize the was_cookie_in_request field in CorsURLLoader whenever the response is cross-origin. Specifically, clear it alongside did_use_server_http_auth inside services/network/cors/cors_url_loader.cc across all three sanitization paths:
if (response_head->response_type != mojom::FetchResponseType::kBasic) {
response_head->device_bound_session_usage =
mojom::DeviceBoundSessionUsage::kUnknown;
response_head->did_use_server_http_auth = false;
response_head->was_cookie_in_request = false; // Fix
}
Additionally, as a defense-in-depth measure, clear the field in ORB’s response sanitization in services/network/orb/orb_api.cc:
void SanitizeBlockedResponseHeaders(network::mojom::URLResponseHead& response) {
response.content_length = 0;
response.was_cookie_in_request = false; // Fix
if (response.headers)
RemoveAllHttpResponseHeaders(response.headers);
}
Evaluated with Chrome root at commit: b5b015ea5f690560237d1f0cff1405844cd12b8d
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.