Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in WebSockets
DescriptionInsufficient validation of untrusted input in WebSockets
ComponentWebSockets
Bug ClassLogic Error
Tracker483423893
Fix commite30962b8baf2 (chromium/src) +82/-170
CISA KEVNot listed
CreditedRichard Belisle
Disclosed2026-04-07

Files Changed

  • chrome/browser/net/websocket_browsertest.cc
  • components/private_ai/websocket_client.cc
  • content/browser/websockets/websocket_connector_impl.cc
  • content/browser/websockets/websocket_connector_impl.h
  • device/fido/cable/fido_tunnel_device.cc
  • device/fido/cable/v2_authenticator.cc
From e30962b8baf2af0d8f4a09e1d71a4403801d9524 Mon Sep 17 00:00:00 2001
From: Chris Fredrickson <[email protected]>
Date: Mon, 23 Feb 2026 08:12:52 -0800
Subject: [PATCH] Remove separate SiteForCookies arg from WebSocketConnector::Connect

This removes the SiteForCookies arg from WebSocketConnector::Connect,
since the impl already has an IsolationInfo which includes a
SiteForCookies that ought to be used instead. We also remove the
associated plumbing all the way to the callsites of
NetworkContext::CreateWebSocket, and update them accordingly.

Of those callsites, all but WebSocketConnectorImpl::Connect used
hardcoded SiteForCookies values that have been preserved or were
irrelevant (due to network::mojom::kWebSocketOptionBlockAllCookies).

Previously, WebSocketConnectorImpl::Connect used a value that was
dynamically computed by the renderer. Now,
WebSocketConnectorImpl::Connect uses the SiteForCookies from the
browser-supplied IsolationInfo (from RenderFrameHostImpl,
ServiceWorkerHost, DedicatedWorkerHost, or SharedWorkerHost, as
applicable).

Fixed: 483423893
Change-Id: I556f728dfd94f516b89fcabe87c2e38b66ec31ca
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7595833
Reviewed-by: Ken Buchanan <[email protected]>
Commit-Queue: Christian Dullweber <[email protected]>
Reviewed-by: Adam Rice <[email protected]>
Auto-Submit: Chris Fredrickson <[email protected]>
Commit-Queue: Chris Fredrickson <[email protected]>
Reviewed-by: Christian Dullweber <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1588717}
---

diff --git a/chrome/browser/net/websocket_browsertest.cc b/chrome/browser/net/websocket_browsertest.cc
index 3c2c81f..ed785fc 100644
--- a/chrome/browser/net/websocket_browsertest.cc
+++ b/chrome/browser/net/websocket_browsertest.cc
@@ -162,7 +162,6 @@
     content::RenderProcessHost* const process = frame->GetProcess();
 
     const std::vector<std::string> requested_protocols;
-    const net::SiteForCookies site_for_cookies;
     // The actual value of this doesn't actually matter, it just can't be empty,
     // to avoid a DCHECK.
     const net::IsolationInfo isolation_info =
@@ -171,10 +170,10 @@
     const url::Origin origin;
 
     process->GetStoragePartition()->GetNetworkContext()->CreateWebSocket(
-        url, requested_protocols, site_for_cookies,
-        net::StorageAccessApiStatus::kNone, isolation_info,
-        std::move(additional_headers), ToOriginatingProcessId(process->GetID()),
-        origin, network::mojom::ClientSecurityState::New(),
+        url, requested_protocols, net::StorageAccessApiStatus::kNone,
+        isolation_info, std::move(additional_headers),
+        ToOriginatingProcessId(process->GetID()), origin,
+        network::mojom::ClientSecurityState::New(),
         network::mojom::kWebSocketOptionNone,
         net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS),
         std::move(handshake_client),
diff --git a/components/private_ai/websocket_client.cc b/components/private_ai/websocket_client.cc
index 27bbf8de0..34b7579e 100644
--- a/components/private_ai/websocket_client.cc
+++ b/components/private_ai/websocket_client.cc
@@ -159,8 +159,7 @@
       "X-WebChannel-Content-Type", "application/x-protobuf"));
 
   network_context_->CreateWebSocket(
-      service_url_, requested_protocols, net::SiteForCookies(),
-      net::StorageAccessApiStatus::kNone,
+      service_url_, requested_protocols, net::StorageAccessApiStatus::kNone,
       net::IsolationInfo::CreateForInternalRequest(
           url::Origin::Create(service_url_)),
       std::move(additional_headers), network::OriginatingProcessId::browser(),
diff --git a/content/browser/websockets/websocket_connector_impl.cc b/content/browser/websockets/websocket_connector_impl.cc
index 4b2ef187..7438fed 100644
--- a/content/browser/websockets/websocket_connector_impl.cc
+++ b/content/browser/websockets/websocket_connector_impl.cc
@@ -76,7 +76,6 @@
 void WebSocketConnectorImpl::Connect(
     const GURL& url,
     const std::vector<std::string>& requested_protocols,
-    const net::SiteForCookies& site_for_cookies,
     const std::optional<std::string>& user_agent,
     net::StorageAccessApiStatus storage_access_api_status,
     mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
@@ -96,11 +95,11 @@
     GetContentClient()->browser()->CreateWebSocket(
         frame,
         base::BindOnce(ConnectCalledByContentBrowserClient, requested_protocols,
-                       site_for_cookies, storage_access_api_status,
-                       isolation_info_, frame_id_, origin_,
-                       client_security_state_->Clone(), options,
+                       storage_access_api_status, isolation_info_, frame_id_,
+                       origin_, client_security_state_->Clone(), options,
                        std::move(throttling_profile_id)),
-        url, site_for_cookies, user_agent, std::move(handshake_client));
+        url, isolation_info_.site_for_cookies(), user_agent,
+        std::move(handshake_client));
     return;
   }
   std::vector<network::mojom::HttpHeaderPtr> headers;
@@ -121,9 +120,8 @@
                     frame_id_);
 
   storage_partition->GetNetworkContext()->CreateWebSocket(
-      url, requested_protocols, site_for_cookies, storage_access_api_status,
-      isolation_info_, std::move(headers),
-      ToOriginatingProcessId(frame_id_.child_id), origin_,
+      url, requested_protocols, storage_access_api_status, isolation_info_,
+      std::move(headers), ToOriginatingProcessId(frame_id_.child_id), origin_,
       client_security_state_->Clone(), options,
       net::MutableNetworkTrafficAnnotationTag(kTrafficAnnotation),
       std::move(handshake_client),
@@ -133,7 +131,6 @@
 
 void WebSocketConnectorImpl::ConnectCalledByContentBrowserClient(
     const std::vector<std::string>& requested_protocols,
-    const net::SiteForCookies& site_for_cookies,
     net::StorageAccessApiStatus storage_access_api_status,
     const net::IsolationInfo& isolation_info,
     const content::GlobalRenderFrameHostId& frame_id,
@@ -155,10 +152,9 @@
     return;
   }
   process->GetStoragePartition()->GetNetworkContext()->CreateWebSocket(
-      url, requested_protocols, site_for_cookies, storage_access_api_status,
-      isolation_info, std::move(additional_headers),
-      ToOriginatingProcessId(frame_id.child_id), origin,
-      std::move(client_security_state), options,
+      url, requested_protocols, storage_access_api_status, isolation_info,
+      std::move(additional_headers), ToOriginatingProcessId(frame_id.child_id),
+      origin, std::move(client_security_state), options,
       net::MutableNetworkTrafficAnnotationTag(kTrafficAnnotation),
       std::move(handshake_client),
       process->GetStoragePartition()->CreateURLLoaderNetworkObserverForFrame(
diff --git a/content/browser/websockets/websocket_connector_impl.h b/content/browser/websockets/websocket_connector_impl.h
index 162168e..8c13410 100644
--- a/content/browser/websockets/websocket_connector_impl.h
+++ b/content/browser/websockets/websocket_connector_impl.h
@@ -44,7 +44,6 @@
   // WebSocketConnector implementation
   void Connect(const GURL& url,
                const std::vector<std::string>& requested_protocols,
-               const net::SiteForCookies& site_for_cookies,
                const std::optional<std::string>& user_agent,
                net::StorageAccessApiStatus storage_access_api_status,
                mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
@@ -55,7 +54,6 @@
  private:
   static void ConnectCalledByContentBrowserClient(
       const std::vector<std::string>& requested_protocols,
-      const net::SiteForCookies& site_for_cookies,
       net::StorageAccessApiStatus storage_access_api_status,
       const net::IsolationInfo& isolation_info,
       const content::GlobalRenderFrameHostId& frame_id,
diff --git a/device/fido/cable/fido_tunnel_device.cc b/device/fido/cable/fido_tunnel_device.cc
index dac3d31..2acd6519 100644
--- a/device/fido/cable/fido_tunnel_device.cc
+++ b/device/fido/cable/fido_tunnel_device.cc
@@ -143,8 +143,8 @@
       base::BindRepeating(&FidoTunnelDevice::OnTunnelData,
                           base::Unretained(this)));
   network_context_factory.Run()->CreateWebSocket(
-      url, {kCableWebSocketProtocol}, net::SiteForCookies(),
-      net::StorageAccessApiStatus::kNone, net::IsolationInfo(),
+      url, {kCableWebSocketProtocol}, net::StorageAccessApiStatus::kNone,
+      net::IsolationInfo(),
       /*additional_headers=*/{}, network::OriginatingProcessId::browser(),
       url::Origin::Create(url), network::mojom::ClientSecurityState::New(),
       network::mojom::kWebSocketOptionBlockAllCookies,
@@ -201,10 +201,10 @@
   headers.emplace_back(
       network::mojom::HttpHeader::New(kCableSignalConnectionHeader, "true"));
   network_context_factory.Run()->CreateWebSocket(
-      url, {kCableWebSocketProtocol}, net::SiteForCookies(),
-      net::StorageAccessApiStatus::kNone, net::IsolationInfo(),
-      std::move(headers), network::OriginatingProcessId::browser(),
-      url::Origin::Create(url), network::mojom::ClientSecurityState::New(),
+      url, {kCableWebSocketProtocol}, net::StorageAccessApiStatus::kNone,
+      net::IsolationInfo(), std::move(headers),
+      network::OriginatingProcessId::browser(), url::Origin::Create(url),
+      network::mojom::ClientSecurityState::New(),
       network::mojom::kWebSocketOptionBlockAllCookies,
       net::MutableNetworkTrafficAnnotationTag(kTrafficAnnotation),
       websocket_client_->BindNewHandshakeClientPipe(),
diff --git a/device/fido/cable/v2_authenticator.cc b/device/fido/cable/v2_authenticator.cc
index e7abce10..7dcf5af 100644
--- a/device/fido/cable/v2_authenticator.cc
+++ b/device/fido/cable/v2_authenticator.cc
@@ -379,7 +379,7 @@
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
     network_context_factory_.Run()->CreateWebSocket(
-        target_, {device::kCableWebSocketProtocol}, net::SiteForCookies(),
+        target_, {device::kCableWebSocketProtocol},
         net::StorageAccessApiStatus::kNone, net::IsolationInfo(),
         /*additional_headers=*/{}, network::OriginatingProcessId::browser(),
         url::Origin::Create(target_),
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/net/websocket_browsertest.cc b/chrome/browser/net/websocket_browsertest.cc
index 3c2c81f..ed785fc 100644
--- a/chrome/browser/net/websocket_browsertest.cc
+++ b/chrome/browser/net/websocket_browsertest.cc
@@ -162,7 +162,6 @@
     content::RenderProcessHost* const process = frame->GetProcess();
 
     const std::vector<std::string> requested_protocols;
-    const net::SiteForCookies site_for_cookies;
     // The actual value of this doesn't actually matter, it just can't be empty,
     // to avoid a DCHECK.
     const net::IsolationInfo isolation_info =
@@ -171,10 +170,10 @@
     const url::Origin origin;
 
     process->GetStoragePartition()->GetNetworkContext()->CreateWebSocket(
-        url, requested_protocols, site_for_cookies,
-        net::StorageAccessApiStatus::kNone, isolation_info,
-        std::move(additional_headers), ToOriginatingProcessId(process->GetID()),
-        origin, network::mojom::ClientSecurityState::New(),
+        url, requested_protocols, net::StorageAccessApiStatus::kNone,
+        isolation_info, std::move(additional_headers),
+        ToOriginatingProcessId(process->GetID()), origin,
+        network::mojom::ClientSecurityState::New(),
         network::mojom::kWebSocketOptionNone,
         net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS),
         std::move(handshake_client),
diff --git a/net/websockets/websocket_channel_test.cc b/net/websockets/websocket_channel_test.cc
index d339cf1..ca4f651 100644
--- a/net/websockets/websocket_channel_test.cc
+++ b/net/websockets/websocket_channel_test.cc
@@ -763,7 +763,6 @@
       const GURL& new_socket_url,
       const std::vector<std::string>& requested_subprotocols,
       const url::Origin& new_origin,
-      const SiteForCookies& new_site_for_cookies,
       StorageAccessApiStatus new_storage_access_api_status,
       const IsolationInfo& new_isolation_info,
       const HttpRequestHeaders& additional_headers,
@@ -773,7 +772,6 @@
       std::unique_ptr<WebSocketStream::ConnectDelegate> new_connect_delegate) {
     socket_url = new_socket_url;
     origin = new_origin;
-    site_for_cookies = new_site_for_cookies;
     storage_access_api_status = new_storage_access_api_status;
     isolation_info = new_isolation_info;
     url_request_context = new_url_request_context;
@@ -783,7 +781,6 @@
 
   GURL socket_url;
   url::Origin origin;
-  SiteForCookies site_for_cookies;
   StorageAccessApiStatus storage_access_api_status;
   IsolationInfo isolation_info;
   raw_ptr<URLRequestContext> url_request_context;
@@ -828,9 +825,9 @@
         CreateEventInterface(), connect_data_.url_request_context.get());
     channel_->SendAddChannelRequestForTesting(
         connect_data_.socket_url, connect_data_.requested_subprotocols,
-        connect_data_.origin, connect_data_.site_for_cookies,
-        net::StorageAccessApiStatus::kNone, connect_data_.isolation_info,
-        HttpRequestHeaders(), TRAFFIC_ANNOTATION_FOR_TESTS,
+        connect_data_.origin, net::StorageAccessApiStatus::kNone,
+        connect_data_.isolation_info, HttpRequestHeaders(),
+        TRAFFIC_ANNOTATION_FOR_TESTS,
         base::BindOnce(&WebSocketStreamCreationCallbackArgumentSaver::Create,
                        base::Unretained(&connect_data_.argument_saver)));
   }
@@ -866,8 +863,7 @@
     ConnectData()
         : url_request_context(CreateTestURLRequestContextBuilder()->Build()),
           socket_url("ws://ws/"),
-          origin(url::Origin::Create(GURL("http://ws"))),
-          site_for_cookies(SiteForCookies::FromUrl(GURL("http://ws/"))) {
+          origin(url::Origin::Create(GURL("http://ws"))) {
       this->isolation_info =
           IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin,
                                 origin, SiteForCookies::FromOrigin(origin));
@@ -882,8 +878,6 @@
     std::vector<std::string> requested_subprotocols;
     // Origin of the request
     url::Origin origin;
-    // First party for cookies for the request.
-    net::SiteForCookies site_for_cookies;
     // Whether the calling context has opted into the Storage Access API.
     StorageAccessApiStatus storage_access_api_status =
         StorageAccessApiStatus::kNone;
@@ -1008,8 +1002,6 @@
 TEST_F(WebSocketChannelTest, EverythingIsPassedToTheCreatorFunction) {
   connect_data_.socket_url = GURL("ws://example.com/test");
   connect_data_.origin = url::Origin::Create(GURL("http://example.com"));
-  connect_data_.site_for_cookies =
-      SiteForCookies::FromUrl(GURL("http://example.com/"));
   connect_data_.isolation_info = net::IsolationInfo::Create(
       IsolationInfo::RequestType::kOther, connect_data_.origin,
       connect_data_.origin, SiteForCookies::FromOrigin(connect_data_.origin));
@@ -1025,8 +1017,6 @@
 
   EXPECT_EQ(connect_data_.socket_url, actual.socket_url);
   EXPECT_EQ(connect_data_.origin.Serialize(), actual.origin.Serialize());
-  EXPECT_TRUE(
-      connect_data_.site_for_cookies.IsEquivalent(actual.site_for_cookies));
   EXPECT_EQ(connect_data_.storage_access_api_status,
             actual.storage_access_api_status);
   EXPECT_TRUE(
diff --git a/net/websockets/websocket_end_to_end_test.cc b/net/websockets/websocket_end_to_end_test.cc
index 3a56887..f95c0d7 100644
--- a/net/websockets/websocket_end_to_end_test.cc
+++ b/net/websockets/websocket_end_to_end_test.cc
@@ -419,8 +419,6 @@
       InitialiseContext();
     }
     url::Origin origin = url::Origin::Create(GURL("http://localhost"));
-    net::SiteForCookies site_for_cookies =
-        net::SiteForCookies::FromOrigin(origin);
     IsolationInfo isolation_info =
         IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin,
                               origin, SiteForCookies::FromOrigin(origin));
@@ -428,9 +426,8 @@
     channel_ = std::make_unique<WebSocketChannel>(std::move(event_interface),
                                                   context_.get());
     channel_->SendAddChannelRequest(
-        GURL(socket_url), sub_protocols_, origin, site_for_cookies,
-        StorageAccessApiStatus::kNone, isolation_info, HttpRequestHeaders(),
-        TRAFFIC_ANNOTATION_FOR_TESTS);
+        GURL(socket_url), sub_protocols_, origin, StorageAccessApiStatus::kNone,
+        isolation_info, HttpRequestHeaders(), TRAFFIC_ANNOTATION_FOR_TESTS);
   }
 
   // Send the connect request to |socket_url| and wait for a response. Returns
diff --git a/net/websockets/websocket_stream_cookie_test.cc b/net/websockets/websocket_stream_cookie_test.cc
index 99b0eed..bcfc4d8 100644
--- a/net/websockets/websocket_stream_cookie_test.cc
+++ b/net/websockets/websocket_stream_cookie_test.cc
@@ -54,7 +54,6 @@
  public:
   void CreateAndConnect(const GURL& url,
                         const url::Origin& origin,
-                        const SiteForCookies& site_for_cookies,
                         const IsolationInfo& isolation_info,
                         const WebSocketExtraHeaders& cookie_header,
                         const std::string& response_body) {
@@ -63,7 +62,7 @@
             url.GetPath(), url.GetHost(), origin, cookie_header,
             /*send_additional_request_headers=*/{}, /*extra_headers=*/{}),
         response_body);
-    CreateAndConnectStream(url, NoSubProtocols(), origin, site_for_cookies,
+    CreateAndConnectStream(url, NoSubProtocols(), origin,
                            StorageAccessApiStatus::kNone, isolation_info,
                            HttpRequestHeaders(), nullptr);
   }
@@ -148,7 +147,6 @@
   const GURL url(GetParam().url);
   const GURL cookie_url(GetParam().cookie_url);
   const url::Origin origin = url::Origin::Create(GURL(GetParam().url));
-  const SiteForCookies site_for_cookies = SiteForCookies::FromOrigin(origin);
   const IsolationInfo isolation_info =
       IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin, origin,
                             SiteForCookies::FromOrigin(origin));
@@ -171,8 +169,8 @@
   ASSERT_TRUE(is_called);
   ASSERT_TRUE(set_cookie_result);
 
-  CreateAndConnect(url, origin, site_for_cookies, isolation_info,
-                   GetParam().cookie_header, WebSocketStandardResponse(""));
+  CreateAndConnect(url, origin, isolation_info, GetParam().cookie_header,
+                   WebSocketStandardResponse(""));
   WaitUntilConnectDone();
   EXPECT_FALSE(has_failed());
 }
@@ -185,7 +183,6 @@
   const GURL url(GetParam().url);
   const GURL cookie_url(GetParam().cookie_url);
   const url::Origin origin = url::Origin::Create(GURL(GetParam().url));
-  const SiteForCookies site_for_cookies = SiteForCookies::FromOrigin(origin);
   const IsolationInfo isolation_info =
       IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin, origin,
                             SiteForCookies::FromOrigin(origin));
@@ -203,8 +200,7 @@
   CookieStore* store =
       url_request_context_host_.GetURLRequestContext()->cookie_store();
 
-  CreateAndConnect(url, origin, site_for_cookies, isolation_info,
-                   /*cookie_header=*/{}, response);
+  CreateAndConnect(url, origin, isolation_info, /*cookie_header=*/{}, response);
   WaitUntilConnectDone();
   EXPECT_FALSE(has_failed()) << failure_message();
diff --git a/net/websockets/websocket_stream_test.cc b/net/websockets/websocket_stream_test.cc
index 7915234..1ba83c3 100644
--- a/net/websockets/websocket_stream_test.cc
+++ b/net/websockets/websocket_stream_test.cc
@@ -112,10 +112,6 @@
   return url::Origin::Create(GURL(kOrigin));
 }
 
-static net::SiteForCookies SiteForCookies() {
-  return net::SiteForCookies::FromOrigin(Origin());
-}
-
 static IsolationInfo CreateIsolationInfo() {
   url::Origin origin = Origin();
   return IsolationInfo::Create(IsolationInfo::RequestType::kOther, origin,
@@ -196,8 +192,7 @@
               WebSocketExtraHeadersToString(extra_response_headers)) +
               additional_data_);
       CreateAndConnectStream(socket_url, sub_protocols, Origin(),
-                             SiteForCookies(), storage_access_api_status,
-                             CreateIsolationInfo(),
+                             storage_access_api_status, CreateIsolationInfo(),
                              WebSocketExtraHeadersToHttpRequestHeaders(
                                  send_additional_request_headers),
                              std::move(timer_));
@@ -331,8 +326,7 @@
     EXPECT_FALSE(request->is_pending());
 
     CreateAndConnectStream(socket_url, sub_protocols, Origin(),
-                           SiteForCookies(), storage_access_api_status,
-                           CreateIsolationInfo(),
+                           storage_access_api_status, CreateIsolationInfo(),
                            WebSocketExtraHeadersToHttpRequestHeaders(
                                send_additional_request_headers),
                            std::move(timer_));
@@ -360,8 +354,7 @@
                                  extra_request_headers),
         response_body);
     CreateAndConnectStream(socket_url, sub_protocols, Origin(),
-                           SiteForCookies(), storage_access_api_status,
-                           CreateIsolationInfo(),
+                           storage_access_api_status, CreateIsolationInfo(),
                            WebSocketExtraHeadersToHttpRequestHeaders(
                                send_additional_request_headers),
                            nullptr);
@@ -388,9 +381,8 @@
                                  /*extra_headers=*/{}),
         WebSocketStandardResponse(extra_response_headers));
     CreateAndConnectStream(socket_url, sub_protocols, Origin(),
-                           SiteForCookies(), storage_access_api_status,
-                           CreateIsolationInfo(), HttpRequestHeaders(),
-                           nullptr);
+                           storage_access_api_status, CreateIsolationInfo(),
+                           HttpRequestHeaders(), nullptr);
   }
 
   // Like CreateAndConnectStandard(), but take raw mock data.
@@ -404,7 +396,7 @@
     ASSERT_EQ(BASIC_HANDSHAKE_STREAM, stream_type_);
 
     AddRawExpectations(std::move(socket_data));
-    CreateAndConnectStream(GURL(url), sub_protocols, Origin(), SiteForCookies(),
+    CreateAndConnectStream(GURL(url), sub_protocols, Origin(),
                            storage_access_api_status, CreateIsolationInfo(),
                            additional_headers, std::move(timer_));
   }
diff --git a/services/network/test/test_network_context.h b/services/network/test/test_network_context.h
index 278c242e..e561dcc 100644
--- a/services/network/test/test_network_context.h
+++ b/services/network/test/test_network_context.h
@@ -198,7 +198,6 @@
   void CreateWebSocket(
       const GURL& url,
       const std::vector<std::string>& requested_protocols,
-      const net::SiteForCookies& site_for_cookies,
       net::StorageAccessApiStatus storage_access_api_status,
       const net::IsolationInfo& isolation_info,
       std::vector<mojom::HttpHeaderPtr> additional_headers,
diff --git a/services/network/websocket_factory_unittest.cc b/services/network/websocket_factory_unittest.cc
index 2732347..31e0099 100644
--- a/services/network/websocket_factory_unittest.cc
+++ b/services/network/websocket_factory_unittest.cc
@@ -83,9 +83,9 @@
     // WebSocket objects are owned by the factory and will be deleted
     // asynchronously.
     factory_->CreateWebSocket(
-        url, requested_protocols, net::SiteForCookies(),
-        net::StorageAccessApiStatus::kNone, net::IsolationInfo(), {},
-        network::OriginatingProcessId::browser(), url::Origin::Create(url),
+        url, requested_protocols, net::StorageAccessApiStatus::kNone,
+        net::IsolationInfo(), {}, network::OriginatingProcessId::browser(),
+        url::Origin::Create(url),
         /*client_security_state=*/nullptr, /*options=*/0,
         TRAFFIC_ANNOTATION_FOR_TESTS, std::move(handshake_client),
         mojo::NullRemote(), mojo::NullRemote(), mojo::NullRemote(),
diff --git a/third_party/blink/renderer/modules/websockets/websocket_channel_impl_test.cc b/third_party/blink/renderer/modules/websockets/websocket_channel_impl_test.cc
index adb2bed7..0ff005f 100644
--- a/third_party/blink/renderer/modules/websockets/websocket_channel_impl_test.cc
+++ b/third_party/blink/renderer/modules/websockets/websocket_channel_impl_test.cc
@@ -198,19 +198,16 @@
       ConnectArgs(
           const KURL& url,
           const Vector<String>& protocols,
-          const net::SiteForCookies& site_for_cookies,
           const String& user_agent,
           mojo::PendingRemote<network::mojom::blink::WebSocketHandshakeClient>
               handshake_client)
           : url(url),
             protocols(protocols),
-            site_for_cookies(site_for_cookies),
             user_agent(user_agent),
             handshake_client(std::move(handshake_client)) {}
 
... (truncated)
Loading diff…

Original Bug Report

reported by [email protected]

WebSocketConnector.Connect() passes renderer-controlled site_for_cookies and storage_access_api_status unvalidated, enabling third-party cookie blocking bypass

WebSocketConnector.Connect() takes site_for_cookies and storage_access_api_status as renderer-supplied Mojo parameters. Neither is validated against browser-side state before being passed through to the network stack.

Data flow (zero validation at every hop): Renderer → WebSocketConnectorImpl::Connect() (websocket_connector_impl.cc:123) → NetworkContext::CreateWebSocket() (network_context.cc:1948) → websocket_stream.cc:143 url_request_->set_site_for_cookies(site_for_cookies) → websocket_stream.cc:148-154 sets kStorageAccessGrantEligible override

A compromised renderer spoofs site_for_cookies to the target origin and storage_access_api_status to kAccessViaAPI, then opens a cross-site WebSocket. The network stack treats the connection as first-party with a Storage Access grant, bypassing third-party cookie blocking. SameSite=None cookies from the target are included in the handshake.

SameSite=Lax/Strict cookies are NOT affected — the browser-controlled initiator_origin still reflects the true cross-site context.

The codebase already has a browser-controlled alternative ready: network_context.mojom:1409-1411: “Currently, |isolation_info|’s SiteForCookies field is ignored, but it will eventually replace the |site_for_cookies| parameter.”

Fix: use isolation_info.SiteForCookies() and derive storage_access_api_status from browser-side permission state.

Related: RestrictedCookieManager::ValidateAccessToCookiesAt() has the same trust issue — site_for_cookies mismatches are LOG(ERROR) only per crbug.com/402207912.

VERSION Chrome Version: 143.0.7499.192 stable Operating System: Linux (Kali 6.18.3) — bug is platform-independent

REPRODUCTION CASE See attached ws-cookie-bypass-poc.py.

Prerequisites:

  • Chromium 143+ installed (or set CHROMIUM=/path/to/chrome)
  • openssl CLI (for cert generation)
  • Python 3 + websockets (pip install websockets)
  • /etc/hosts: 127.0.0.1 example.test target-domain.example
  • Run: python3 ws-cookie-bypass-poc.py

Output: Phase 1 (control, unmodified forward): 0 cookies — correct Phase 2 (spoofed SFC + SAA status): session_none=LEAKED_NONE — bypass

The PoC uses MojoInterfaceInterceptor to capture a real Connect() message, modifies site_for_cookies and storage_access_api_status in-place, and forwards through a new WebSocketConnector pipe. Requires –enable-blink-features=MojoJS to simulate compromised renderer Mojo access.

Reporter Credit: Richard Belisle

View on issue tracker