[6] DedicatedWorker WebSocket handshake skips ITP third-party cookie blocking
ITP stripped third-party cookies from a worker's fetch but not from its WebSocket handshake — the same cross-site cookies rode the upgrade request.
Rated Low because the diff closes a privacy gap where a DedicatedWorker WebSocket handshake carried third-party cookies that ITP intends to strip; the impact is cross-site tracking/identity correlation reachable from ordinary web content, not memory corruption or cross-origin script access, and it only matters when third-party cookie blocking is enabled.
DedicatedWorker WebSocket connections were not subjected to ITP third-party cookie blocking, unlike DedicatedWorker fetch requests which already carried isInitiatedByDedicatedWorker through NetworkResourceLoadParameters. The patch threads a new isInitiatedByDedicatedWorker flag from the worker thread through WorkerThreadableWebSocketChannel::Bridge::initialize() — where it is derived via is<DedicatedWorkerGlobalScope>(scope) — into the WebSocketTaskCocoa constructor, where it replaces the previous shouldBlockCookies() call with thirdPartyCookieBlockingDecisionForRequest(..., isInitiatedByDedicatedWorker), so the same policy applied to worker fetch requests now applies to worker WebSocket handshakes.
Source/WebCore/Modules/websockets/WorkerThreadableWebSocketChannel.cpp
Source/WebKit/NetworkProcess/cocoa/WebSocketTaskCocoa.mm
LayoutTests/http/tests/websocket/tests/hybi/resources/websocket-blocked-sending-cookie-as-third-party-worker.js
Patch Details
The patch threads a new IsInitiatedByDedicatedWorker enum (enum class IsInitiatedByDedicatedWorker : bool { No, Yes }) from the worker thread down to the Cocoa network WebSocket task. In Bridge::initialize() the flag is derived via is<DedicatedWorkerGlobalScope>(scope) and captured into the cross-thread task posted to the loader. It is passed through mainThreadInitialize() → Peer::create()/Peer::Peer() → ThreadableWebSocketChannel::create(Document&, ...) → SocketProvider::createWebSocketChannel(). On the WebKit side the same parameter is added to the CreateSocketChannel IPC message and threaded through NetworkConnectionToWebProcess::createSocketChannel(), NetworkSocketChannel, NetworkSession::createWebSocketTask(), and NetworkSessionCocoa::createWebSocketTask() → WebSocketTask. The load-bearing change is in the WebSocketTask (Cocoa) constructor, where the unconditional shouldBlockCookies() call is replaced with thirdPartyCookieBlockingDecisionForRequest(..., isInitiatedByDedicatedWorker). The bulk of the diff is mechanical signature plumbing across Curl/Soup/Legacy/Empty implementations.
Inconsistent enforcement of a privacy policy across sibling request paths: the worker-origin signal that gates third-party cookie blocking was carried for fetch but dropped for WebSocket handshakes.
Background
ITP (Intelligent Tracking Prevention) is a WebKit privacy feature that, among other things, blocks cookies on cross-site (third-party) requests for tracker-classified domains or when third-party cookie blocking is enabled. A DedicatedWorker is a worker tied to a single owning document; is<DedicatedWorkerGlobalScope>(scope) distinguishes it from shared/service workers. A WebSocket handshake is the initial HTTP Upgrade request a new WebSocket(url) issues; like any HTTP request it can carry cookies for the target origin. Because workers run off the main thread, WebSocket setup is marshalled to the main/loader thread via Bridge::initialize() → postTaskToLoader → mainThreadInitialize, which builds a main-thread Peer that owns the real ThreadableWebSocketChannel. thirdPartyCookieBlockingDecisionForRequest and shouldBlockCookies are two NetworkProcess decision routines for whether a request should send cookies; the former takes the worker-origination signal as input so it can apply the same policy used for worker fetches.
Analysis
This is a logic error / privacy-policy bypass — an ITP third-party cookie-blocking gap, not a memory-safety issue. Before the fix, the WebSocket connection path originating from a DedicatedWorker did not propagate the fact that the request was worker-initiated down to the network layer. The Cocoa WebSocketTask constructor decided third-party cookie handling with a plain shouldBlockCookies() call that lacked the worker context the equivalent fetch path already carried.
ITP's third-party cookie-blocking decision depends on whether the request is a first-party or third-party context and whether it was initiated by a worker. Without the worker signal, the WebSocket handshake to a cross-origin endpoint from inside a DedicatedWorker was evaluated under a policy that did not block third-party cookies, so the handshake carried cookies the policy intends to strip. The added layout test exercises exactly this: first-party cookies are set on localhost/127.0.0.1, then a cross-origin WebSocket is opened from a worker expecting no cookies. From an attacker-embedded page, set first-party cookies on a target origin, then from a DedicatedWorker open a cross-origin WebSocket to that origin while third-party cookie blocking is enabled; before the fix the handshake carried the target's cookies to the cross-origin endpoint, which can log them to correlate identity across sites.
This vulnerability weakens the privacy boundary enforced by ITP third-party cookie blocking, not memory safety. The affected assumption is that, when third-party cookie blocking is enabled, a cross-origin handshake initiated from a page or its workers does not carry the third party's cookies. Before the fix that held for DedicatedWorker fetch but was violated for DedicatedWorker WebSocket handshakes, enabling cross-site tracking/identity correlation ITP is designed to prevent. No code execution or cross-origin script access results.
Privacy and security policies in WebKit are frequently re-derived independently along parallel request paths (fetch vs WebSocket vs WebTransport vs EventSource, and document vs worker vs service-worker origination). When a new contextual signal is introduced for one path, sibling paths silently retain the old, weaker decision until each is individually plumbed.
Note: The load-bearing WebSocketTaskCocoa.mm change, the existing fetch propagation via NetworkResourceLoadParameters, and the policy applied by thirdPartyCookieBlockingDecisionForRequest are attributed to the commit message; the visible diff shows the plumbing while the behavioral cookie-blocking change lives in the truncated Cocoa hunk. The parity gap and its fix shape are consistently supported by the patch.
Audit directions
- A privacy/security signal carried on one request path (fetch) but dropped on a sibling path (WebSocket/WebTransport/EventSource). Audit the other long-lived connection initiators for the same worker-origination gap — check whether
WebTransportandEventSourcesetup from aDedicatedWorkerGlobalScopepropagate any equivalent ofisInitiatedByDedicatedWorkerinto their NetworkProcess cookie decisions. Start atSocketProvider::initializeWebTransportSessionand the EventSource loader path, comparing their cookie-policy call sites againstthirdPartyCookieBlockingDecisionForRequest. - Remaining call sites of
shouldBlockCookies(in the NetworkProcess. Compare each againstthirdPartyCookieBlockingDecisionForRequest(to determine which decision points still lack the worker-origination input. Verify each protocol's task constructor (Cocoa/Curl/SoupcreateWebSocketTaskand analogous WebTransport tasks) receives and honors the same provenance flag. - Cross-thread marshalling that loses request provenance. Audit
postTaskToLoader/mainThreadInitialize-style hops in WebCore worker subsystems to confirm origin/worker-type context computed on the worker thread (viais<DedicatedWorkerGlobalScope>) is captured into the cross-thread lambda rather than re-derived on the main thread where the scope type may be unavailable. Start withWorkerThreadableWebSocketChannel::Bridgeand similar Bridge classes inModules/.