Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Workers
DescriptionIncorrect authorization in Workers
ComponentWorkers
Bug ClassLogic Error
Tracker500467033
Fix commit2b4918335a9c (chromium/src) +159/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Background

`SharedWorker`
A script context shared by multiple documents that runs in the browser’s worker host and can open its own network connections such as WebSockets.
Storage Access API
A mechanism that lets an embedded third-party frame request first-party storage/cookie access, causing its SharedWorker to be spawned with a first-party blink::StorageKey override while it still logically lives in a third-party context.
`net::IsolationInfo` / `net::SiteForCookies`
The isolation record whose SiteForCookies field tells the network service whether a request is in a first-party context and therefore eligible to send SameSite=Strict/Lax cookies.
`DoesRequireCrossSiteRequestForCookies()`
A SharedWorkerInstance predicate that reports whether the worker must be treated as cross-site for cookie purposes despite any first-party storage-key override.

Root Cause Analysis

When SharedWorkerHost::CreateWebSocketConnector built the WebSocketConnectorImpl, it derived the connection’s net::IsolationInfo by calling storage_key.ToPartialNetIsolationInfo() directly, which populates a non-null SiteForCookies straight from the (possibly overridden) storage key. For a worker created in a third-party context via the Storage Access API, that storage key is a first-party override, so the resulting IsolationInfo falsely marked the WebSocket handshake as a first-party request. This violated the invariant that a context requiring cross-site cookie semantics must present a null SiteForCookies, which standard fetches already honored by consulting DoesRequireCrossSiteRequestForCookies() but the WebSocket path did not. As a result the network service attached SameSite=Strict/Lax cookies to the handshake that should have been withheld in a third-party context.

The fix routes isolation-info construction through the new ComputeIsolationInfoForWebSocket(), which rebuilds the IsolationInfo with net::SiteForCookies() (null) whenever instance_.DoesRequireCrossSiteRequestForCookies() is true, restoring the same cross-site cookie restriction the fetch path enforces.

Key insight
The core mistake was deriving SiteForCookies for the WebSocket connection solely from the worker’s storage key while ignoring the DoesRequireCrossSiteRequestForCookies() signal, so a first-party storage-key override in a genuinely third-party context leaked SameSite cookies; the fix clears SiteForCookies to null in exactly that case so the network service no longer treats the handshake as first-party.

Attack Path

  1. Establish third-party context A malicious top-level site embeds a target-origin third-party frame that obtains first-party access through the Storage Access API.
  2. Spawn a SharedWorker The third-party frame creates a SharedWorker, which is spawned with a first-party blink::StorageKey override yet remains logically third-party.
  3. Open a WebSocket The worker opens a WebSocket back to its origin, whose isolation info is built via storage_key.ToPartialNetIsolationInfo() with a non-null SiteForCookies.
  4. SameSite cookies leak The network service, seeing a first-party SiteForCookies, attaches the origin’s SameSite=Strict/Lax cookies to the handshake even though the request originated in a third-party context.

Impact Assessment

An attacker operating a page that embeds a victim origin can cause that origin’s SameSite=Strict/Lax cookies to be transmitted on a WebSocket handshake initiated from a third-party context, defeating the CSRF/cross-site protection those cookies are meant to provide. The cookies are attached by the network service to a connection whose isolation was decided in the browser-process worker host, and the effect is authenticated cross-site access to the victim endpoint. Preconditions are that the victim origin sets SameSite-restricted cookies and that the SharedWorker is created in a third-party context with cross-site cookie semantics (e.g. via a Storage Access API grant).

Changed Functions

FunctionChangeNotes
TEST_F
content/browser/worker_host/shared_worker_host_unittest.cc
modified

Files Changed

  • content/browser/worker_host/shared_worker_host.cc
  • content/browser/worker_host/shared_worker_host.h
  • content/browser/worker_host/shared_worker_host_unittest.cc

Audit Directions

  • StorageKey-derived isolation info
    Flag any code that builds net::IsolationInfo or SiteForCookies directly from a blink::StorageKey (e.g. ToPartialNetIsolationInfo()) without also consulting DoesRequireCrossSiteRequestForCookies(), since a first-party override can hide a third-party context.
  • Non-fetch subresource paths
    Review other worker-initiated connection types (WebTransport, EventSource, direct sockets) for the same divergence from the standard fetch path’s cross-site cookie handling.
  • Feature-flag-gated security fixes
    Track features::kRestrictSharedWorkerWebSocketCrossSiteCookies, which is disabled by default, so the restriction is not actually active until the flag is enabled and any audit must account for the unpatched default behavior.
From 2b4918335a9c2e165284b65c2ddd1b83bd4f3c09 Mon Sep 17 00:00:00 2001
From: Yoshisto Yanagisawa <[email protected]>
Date: Tue, 21 Apr 2026 18:34:46 -0700
Subject: [PATCH] Restrict SameSite cookies for WebSockets in third-party contexts

This CL fixes a security vulnerability where SharedWorkers in a
third-party context (e.g. created via the Storage Access API) could
bypass SameSite=Strict/Lax cookie restrictions when establishing
WebSocket connections.

When a SharedWorker is spawned with a Storage Access API grant, it uses
a first-party StorageKey override. However, it still logically resides
in a third-party context. While standard fetches correctly consult
DoesRequireCrossSiteRequestForCookies() to restrict SameSite cookies,
the WebSocket creation path was incorrectly deriving IsolationInfo
directly from the storage key without this check.

Changes:
- Added a new internal feature flag
  kRestrictSharedWorkerWebSocketCrossSiteCookies (disabled by default)
  to safely roll out the fix.
- Added a UMA metric
  Content.SharedWorker.WebSocket.DoesRequireCrossSiteRequestForCookies
  to measure the prevalence of such workers.
- Updated SharedWorkerHost::CreateWebSocketConnector to clear the
  SiteForCookies in the IsolationInfo if the worker requires cross-site
  cookie semantics and the feature flag is enabled.
- Refactored IsolationInfo computation into a new testable method
  ComputeIsolationInfoForWebSocket().
- Added unit tests to verify the UMA recording and the logic.

Bug: 500467033
Change-Id: I97cf322ce63b6b65cc161f3b77b3fbf82fed0e28
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7753799
Reviewed-by: Rakina Zata Amni <[email protected]>
Reviewed-by: Tarun Bansal <[email protected]>
Reviewed-by: Hiroki Nakagawa <[email protected]>
Commit-Queue: Yoshisato Yanagisawa <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1618601}
---

diff --git a/content/browser/worker_host/shared_worker_host.cc b/content/browser/worker_host/shared_worker_host.cc
index ba78a88..6793a4e 100644
--- a/content/browser/worker_host/shared_worker_host.cc
+++ b/content/browser/worker_host/shared_worker_host.cc
@@ -34,6 +34,7 @@
 #include "content/browser/worker_host/shared_worker_content_settings_proxy_impl.h"
 #include "content/browser/worker_host/shared_worker_service_impl.h"
 #include "content/browser/worker_host/worker_script_fetcher.h"
+#include "content/common/features.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -43,7 +44,6 @@
 #include "content/public/browser/service_worker_context.h"
 #include "content/public/common/child_process_id_util.h"
 #include "content/public/common/content_client.h"
-#include "content/public/common/content_features.h"
 #include "net/base/isolation_info.h"
 #include "net/cookies/site_for_cookies.h"
 #include "services/metrics/public/cpp/delegating_ukm_recorder.h"
@@ -690,7 +690,7 @@
       std::make_unique<WebSocketConnectorImpl>(
           GlobalRenderFrameHostId(GetProcessHost()->GetID(),
                                   IPC::mojom::kRoutingIdNone),
-          storage_key.origin(), storage_key.ToPartialNetIsolationInfo(),
+          storage_key.origin(), ComputeIsolationInfoForWebSocket(),
           worker_client_security_state_->Clone(),
           // TODO(crbug.com/492462310): Pass network_restrictions_id so
           // Connection-Allowlist is enforced for shared worker WebSocket
@@ -699,6 +699,32 @@
       std::move(receiver));
 }
 
+net::IsolationInfo SharedWorkerHost::ComputeIsolationInfoForWebSocket() const {
+  const blink::StorageKey& storage_key = GetWorkerStorageKey();
+  net::IsolationInfo isolation_info = storage_key.ToPartialNetIsolationInfo();
+
+  base::UmaHistogramBoolean(
+      "Content.SharedWorker.WebSocket.DoesRequireCrossSiteRequestForCookies",
+      instance_.DoesRequireCrossSiteRequestForCookies());
+
+  if (instance_.DoesRequireCrossSiteRequestForCookies()) {
+    if (base::FeatureList::IsEnabled(
+            features::kRestrictSharedWorkerWebSocketCrossSiteCookies)) {
+      // If the worker requires cross-site cookie semantics (e.g. a worker in a
+      // third-party context or created via the Storage Access API), we must
+      // ensure that the SiteForCookies is null. This prevents the network
+      // service from incorrectly attaching SameSite=Strict/Lax cookies to the
+      // WebSocket handshake.
+      CHECK(!isolation_info.IsEmpty());
+      isolation_info = net::IsolationInfo::Create(
+          isolation_info.request_type(), *isolation_info.top_frame_origin(),
+          *isolation_info.frame_origin(), net::SiteForCookies(),
+          isolation_info.nonce());
+    }
+  }
+  return isolation_info;
+}
+
 void SharedWorkerHost::BindCacheStorage(
     mojo::PendingReceiver<blink::mojom::CacheStorage> receiver) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
diff --git a/content/browser/worker_host/shared_worker_host.h b/content/browser/worker_host/shared_worker_host.h
index a04e0df..a7c5aa8 100644
--- a/content/browser/worker_host/shared_worker_host.h
+++ b/content/browser/worker_host/shared_worker_host.h
@@ -230,6 +230,14 @@
   // be called right before deleting this instance.
   mojo::Remote<blink::mojom::SharedWorker> TerminateRemoteWorkerForTesting();
 
+  void set_client_security_state_for_testing(
+      network::mojom::ClientSecurityStatePtr client_security_state) {
+    worker_client_security_state_ = std::move(client_security_state);
+  }
+
+  // Computes the IsolationInfo used for WebSocket connections from this worker.
+  net::IsolationInfo ComputeIsolationInfoForWebSocket() const;
+
   base::WeakPtr<SharedWorkerHost> AsWeakPtr();
 
   net::NetworkIsolationKey GetNetworkIsolationKey() const;
diff --git a/content/browser/worker_host/shared_worker_host_unittest.cc b/content/browser/worker_host/shared_worker_host_unittest.cc
index 63eccc4..925de9e0 100644
--- a/content/browser/worker_host/shared_worker_host_unittest.cc
+++ b/content/browser/worker_host/shared_worker_host_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/unguessable_token.h"
 #include "content/browser/navigation_subresource_loader_params.h"
@@ -25,8 +26,8 @@
 #include "content/browser/worker_host/shared_worker_service_impl.h"
 #include "content/browser/worker_host/worker_script_fetcher.h"
 #include "content/browser/worker_host/worker_util.h"
+#include "content/common/features.h"
 #include "content/public/browser/shared_worker_instance.h"
-#include "content/public/common/content_features.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/mock_render_process_host.h"
 #include "content/public/test/test_browser_context.h"
@@ -567,4 +568,103 @@
   EXPECT_EQ(details->error_type, received_details->error_type);
 }
 
+TEST_F(SharedWorkerHostTest, CreateWebSocketConnector_SameOrigin) {
+  const GURL kWorkerUrl{"http://www.example.com/w.js"};
+  url::Origin worker_origin = url::Origin::Create(kWorkerUrl);
+
+  // Create a SharedWorkerInstance for a worker created in a same-origin
+  // context. This worker should not have any special cookie restrictions.
+  blink::StorageKey creator_storage_key =
+      blink::StorageKey::CreateFirstParty(worker_origin);
+  blink::StorageKey worker_storage_key =
+      blink::StorageKey::CreateFirstParty(worker_origin);
+
+  SharedWorkerInstance instance(
+      kWorkerUrl, blink::mojom::ScriptType::kClassic,
+      network::mojom::CredentialsMode::kSameOrigin, "name", creator_storage_key,
+      worker_storage_key, worker_origin,
+      blink::mojom::SharedWorkerCreationContextType::kSecure,
+      blink::mojom::SharedWorkerSameSiteCookies::kAll,
+      /*extended_lifetime=*/false);
+
+  auto host = std::make_unique<SharedWorkerHost>(
+      &service_, instance, site_instance_,
+      std::vector<network::mojom::ContentSecurityPolicyPtr>(),
+      base::MakeRefCounted<PolicyContainerHost>());
+  host->set_client_security_state_for_testing(
+      network::mojom::ClientSecurityState::New());
+
+  base::HistogramTester histogram_tester;
+  net::IsolationInfo isolation_info = host->ComputeIsolationInfoForWebSocket();
+  // SiteForCookies should NOT be null for same-origin workers.
+  EXPECT_FALSE(isolation_info.site_for_cookies().IsNull());
+
+  histogram_tester.ExpectUniqueSample(
+      "Content.SharedWorker.WebSocket.DoesRequireCrossSiteRequestForCookies",
+      false, 1);
+}
+
+TEST_F(SharedWorkerHostTest,
+       CreateWebSocketConnector_CrossSiteCookieRestrictions) {
+  const GURL kWorkerUrl{"http://www.example.com/w.js"};
+  url::Origin worker_origin = url::Origin::Create(kWorkerUrl);
+
+  blink::StorageKey creator_storage_key =
+      blink::StorageKey::CreateFirstParty(worker_origin);
+  blink::StorageKey worker_storage_key =
+      blink::StorageKey::CreateFirstParty(worker_origin);
+
+  // Create a SharedWorkerInstance that requires cross-site cookie restrictions.
+  // This simulates a worker in a third-party context (e.g. created via the
+  // Storage Access API), which explicitly requests no SameSite cookies.
+  SharedWorkerInstance instance(
+      kWorkerUrl, blink::mojom::ScriptType::kClassic,
+      network::mojom::CredentialsMode::kSameOrigin, "name", creator_storage_key,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/worker_host/shared_worker_host_unittest.cc b/content/browser/worker_host/shared_worker_host_unittest.cc
index 63eccc4..925de9e0 100644
--- a/content/browser/worker_host/shared_worker_host_unittest.cc
+++ b/content/browser/worker_host/shared_worker_host_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/unguessable_token.h"
 #include "content/browser/navigation_subresource_loader_params.h"
@@ -25,8 +26,8 @@
 #include "content/browser/worker_host/shared_worker_service_impl.h"
 #include "content/browser/worker_host/worker_script_fetcher.h"
 #include "content/browser/worker_host/worker_util.h"
+#include "content/common/features.h"
 #include "content/public/browser/shared_worker_instance.h"
-#include "content/public/common/content_features.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/mock_render_process_host.h"
 #include "content/public/test/test_browser_context.h"
@@ -567,4 +568,103 @@
   EXPECT_EQ(details->error_type, received_details->error_type);
 }
 
+TEST_F(SharedWorkerHostTest, CreateWebSocketConnector_SameOrigin) {
+  const GURL kWorkerUrl{"http://www.example.com/w.js"};
+  url::Origin worker_origin = url::Origin::Create(kWorkerUrl);
+
+  // Create a SharedWorkerInstance for a worker created in a same-origin
+  // context. This worker should not have any special cookie restrictions.
+  blink::StorageKey creator_storage_key =
+      blink::StorageKey::CreateFirstParty(worker_origin);
+  blink::StorageKey worker_storage_key =
+      blink::StorageKey::CreateFirstParty(worker_origin);
+
+  SharedWorkerInstance instance(
+      kWorkerUrl, blink::mojom::ScriptType::kClassic,
+      network::mojom::CredentialsMode::kSameOrigin, "name", creator_storage_key,
+      worker_storage_key, worker_origin,
+      blink::mojom::SharedWorkerCreationContextType::kSecure,
+      blink::mojom::SharedWorkerSameSiteCookies::kAll,
+      /*extended_lifetime=*/false);
+
+  auto host = std::make_unique<SharedWorkerHost>(
+      &service_, instance, site_instance_,
+      std::vector<network::mojom::ContentSecurityPolicyPtr>(),
+      base::MakeRefCounted<PolicyContainerHost>());
+  host->set_client_security_state_for_testing(
+      network::mojom::ClientSecurityState::New());
+
+  base::HistogramTester histogram_tester;
+  net::IsolationInfo isolation_info = host->ComputeIsolationInfoForWebSocket();
+  // SiteForCookies should NOT be null for same-origin workers.
+  EXPECT_FALSE(isolation_info.site_for_cookies().IsNull());
+
+  histogram_tester.ExpectUniqueSample(
+      "Content.SharedWorker.WebSocket.DoesRequireCrossSiteRequestForCookies",
+      false, 1);
+}
+
+TEST_F(SharedWorkerHostTest,
+       CreateWebSocketConnector_CrossSiteCookieRestrictions) {
+  const GURL kWorkerUrl{"http://www.example.com/w.js"};
+  url::Origin worker_origin = url::Origin::Create(kWorkerUrl);
+
+  blink::StorageKey creator_storage_key =
+      blink::StorageKey::CreateFirstParty(worker_origin);
+  blink::StorageKey worker_storage_key =
+      blink::StorageKey::CreateFirstParty(worker_origin);
+
+  // Create a SharedWorkerInstance that requires cross-site cookie restrictions.
+  // This simulates a worker in a third-party context (e.g. created via the
+  // Storage Access API), which explicitly requests no SameSite cookies.
+  SharedWorkerInstance instance(
+      kWorkerUrl, blink::mojom::ScriptType::kClassic,
+      network::mojom::CredentialsMode::kSameOrigin, "name", creator_storage_key,
+      worker_storage_key, worker_origin,
+      blink::mojom::SharedWorkerCreationContextType::kSecure,
+      blink::mojom::SharedWorkerSameSiteCookies::kNone,
+      /*extended_lifetime=*/false);
+
+  auto host = std::make_unique<SharedWorkerHost>(
+      &service_, instance, site_instance_,
+      std::vector<network::mojom::ContentSecurityPolicyPtr>(),
+      base::MakeRefCounted<PolicyContainerHost>());
+  host->set_client_security_state_for_testing(
+      network::mojom::ClientSecurityState::New());
+
+  {
+    // Case 1: The feature flag is disabled. The UMA should still be recorded,
+    // but the SiteForCookies should NOT be cleared.
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndDisableFeature(
+        features::kRestrictSharedWorkerWebSocketCrossSiteCookies);
+    base::HistogramTester histogram_tester;
+
+    net::IsolationInfo isolation_info =
+        host->ComputeIsolationInfoForWebSocket();
+    EXPECT_FALSE(isolation_info.site_for_cookies().IsNull());
+
+    histogram_tester.ExpectUniqueSample(
+        "Content.SharedWorker.WebSocket.DoesRequireCrossSiteRequestForCookies",
+        true, 1);
+  }
+
+  {
+    // Case 2: The feature flag is enabled. The SiteForCookies SHOULD be
+    // cleared.
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndEnableFeature(
+        features::kRestrictSharedWorkerWebSocketCrossSiteCookies);
+    base::HistogramTester histogram_tester;
+
+    net::IsolationInfo isolation_info =
+        host->ComputeIsolationInfoForWebSocket();
+    EXPECT_TRUE(isolation_info.site_for_cookies().IsNull());
+
+    histogram_tester.ExpectUniqueSample(
+        "Content.SharedWorker.WebSocket.DoesRequireCrossSiteRequestForCookies",
+        true, 1);
+  }
+}
+
 }  // namespace content
Loading diff…

Original Bug Report

reported by [email protected]

Bypass of SameSite cookie restrictions via WebSockets in Storage Access API SharedWorkers

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 without the security team.

Overview: SharedWorkers created via the Storage Access API (SAA) can potentially bypass cross-site cookie restrictions when establishing WebSocket connections. The browser process fails to apply the DoesRequireCrossSiteRequestForCookies flag during WebSocket initialization, relying solely on an overridden first-party storage key. As a result, SameSite=Strict and Lax cookies are incorrectly attached to the WebSocket handshake, enabling potential CSRF and tracking in third-party contexts.

Affected files:

  • content/browser/worker_host/shared_worker_host.cc
  • content/browser/storage_access/storage_access_handle.cc
  • services/network/websocket_factory.cc
  • net/websockets/websocket_stream.cc
  • content/public/browser/shared_worker_instance.h

Estimated timestamp from git blame: 2026-04-06

Description

There is a potential vulnerability in how the browser handles WebSocket connections originating from SharedWorkers created via the Storage Access API (SAA). This flaw allows a third-party context to bypass SameSite=Strict and SameSite=Lax cookie protections.

When a SharedWorker is spawned through an SAA grant, it is assigned a first-party StorageKey override to provide unpartitioned storage access. However, because it is still logically embedded in a third-party context, the renderer explicitly sets its same_site_cookies parameter to kNone. This correctly causes SharedWorkerInstance::DoesRequireCrossSiteRequestForCookies() to evaluate to true in the browser process.

While standard subresource fetches correctly respect this flag (via URLLoaderFactoryParamsHelper), the WebSocket creation path ignores it. As a result, the WebSocket handshake is treated by the network service as a first-party request, and restricted SameSite cookies are incorrectly attached.

Root Cause Analysis

In content/browser/worker_host/shared_worker_host.cc, the CreateWebSocketConnector method derives the IsolationInfo for the WebSocket connection directly from the worker’s storage key:

void SharedWorkerHost::CreateWebSocketConnector(
    mojo::PendingReceiver<blink::mojom::WebSocketConnector> receiver) {
  // ...
  const blink::StorageKey& storage_key = GetWorkerStorageKey();

  mojo::MakeSelfOwnedReceiver(
      std::make_unique<WebSocketConnectorImpl>(
          // ...
          storage_key.origin(), storage_key.ToPartialNetIsolationInfo(),
          // ...

Because an SAA worker uses a first-party storage key override, ToPartialNetIsolationInfo() returns an IsolationInfo with a first-party SiteForCookies. Since the initiator (derived from storage_key.origin()) also matches this first-party context, the network service (net::cookie_util::ComputeSameSiteContext) computes the context as SAME_SITE_STRICT.

The code fails to check instance_.DoesRequireCrossSiteRequestForCookies(), which would otherwise indicate that this worker is explicitly forbidden from sending SameSite=Strict/Lax cookies.

Potential Steps to Reproduce

Note: Our tooling agent does not have the ability to run code, so these are suggested steps outlining how an attacker would likely trigger this vulnerability based on static analysis.

  1. An attacker controls https://tracker.com, which sets a protected cookie: Set-Cookie: session=secret; SameSite=Strict; Secure.
  2. The attacker embeds an iframe to https://tracker.com/embed inside a top-level page they control, such as https://news.com.
  3. Within the tracker.com iframe, the attacker executes document.requestStorageAccess({SharedWorker: true}) to obtain an SAA grant.
  4. The iframe uses the obtained handle to spawn a worker: handle.SharedWorker('/saa-worker.js').
  5. Inside /saa-worker.js, the attacker initiates a WebSocket connection: new WebSocket('wss://tracker.com/api').
  6. Because of the missing check, the WebSocket Upgrade request will incorrectly include the session=secret (SameSite=Strict) cookie, bypassing the cross-site boundary.

Suggested Fix

In SharedWorkerHost::CreateWebSocketConnector, the IsolationInfo should be adjusted to clear the SiteForCookies if the worker requires cross-site cookie semantics. For example:

  const blink::StorageKey& storage_key = GetWorkerStorageKey();
  net::IsolationInfo isolation_info = storage_key.ToPartialNetIsolationInfo();

  if (instance_.DoesRequireCrossSiteRequestForCookies()) {
    isolation_info = net::IsolationInfo::Create(
        isolation_info.request_type(), isolation_info.top_frame_origin(),
        isolation_info.frame_origin(), net::SiteForCookies(),
        isolation_info.nonce());
  }

  mojo::MakeSelfOwnedReceiver(
      std::make_unique<WebSocketConnectorImpl>(
          GlobalRenderFrameHostId(GetProcessHost()->GetID(),
                                  IPC::mojom::kRoutingIdNone),
          storage_key.origin(), isolation_info,
          // ...

This mirrors the behavior enforced on standard fetch() subresource requests for SAA workers, ensuring that the network service will only attach SameSite=None cookies to the WebSocket handshake.

Evaluated with Chrome root at commit: 137d451a126685dd5010e6609db9f6d4a78d8234


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker