← WebKit Silent-Fix Report — 2026-W23

e30ca29fe97301994a1d6c66ab56fcc1aff66d3f  Add cookie access validation to startDownload() and convertMainResourceLoadToDownload() to prevent CSRF

severity medium class CrossOrigin confidence 0.90 NetworkProcess Download exploitable-grade
Rupin Mittal Thu Jun 4 15:25:44 2026 -0700 full: e30ca29fe97301994a1d6c66ab56fcc1aff66d3f bug report ↗ view on GitHub ↗
Primitive: Missing first-party cookie access validation in download IPC handlers
Triage note: startDownload() and convertMainResourceLoadToDownload() did not validate request.firstPartyForCookies() against allowsFirstPartyForCookies(m_webProcessIdentifier); a compromised WebProcess could supply an arbitrary first-party origin to access cross-origin cookies during a download. The fix adds MESSAGE_CHECK(allowsFirstPartyForCookies(...) == Allow).
Contents

The bug at a glance

The unvalidated download IPCs are handled by the NetworkProcess but their arguments come from a WebContent process, so exploitation presupposes a renderer that can craft StartDownload/ConvertMainResourceLoadToDownload messages with a spoofed first-party origin — a compromised or coerced renderer. Given that, the flaw lets a download request be issued with an attacker-chosen firstPartyForCookies, causing the NetworkProcess to attach another origin’s first-party cookies to the request (a CSRF/cross-origin cookie-attachment issue). Confidentiality/integrity impact across the origin boundary behind a renderer-trust precondition fits CVSS 6.5 Medium.

When a page turns a load into a download, the WebProcess hands the NetworkProcess a ResourceRequest, and the NetworkProcess trusted the request’s firstPartyForCookies() verbatim. That field controls which origin’s cookies (and same-site policy) apply, so a compromised renderer could set it to a victim origin and have the NetworkProcess fire off a cookie-bearing, seemingly-first-party request it never authorized — a cross-site request forgery with real cookies attached. The fix adds MESSAGE_CHECK(allowsFirstPartyForCookies(...) == Allow) to both startDownload() and convertMainResourceLoadToDownload(), but only when the first-party field is non-empty, because a legitimate PolicyAction::Download path never registers the process in the cookie-access map and would otherwise crash the ASSERT_NOT_REACHED.

Root cause

The vulnerable handlers are NetworkConnectionToWebProcess::startDownload() and NetworkConnectionToWebProcess::convertMainResourceLoadToDownload() in the NetworkProcess. Both receive a WebCore::ResourceRequest originating from a WebContent process and pass it into the download manager. Neither validated request.firstPartyForCookies() against what the calling process is actually allowed to claim.

firstPartyForCookies() is the request’s top-level/first-party URL; the network stack uses it to decide which cookies to send and how SameSite rules apply. A download request built with this field pointing at, say, https://bank.example will be treated as a first-party navigation to that site and carry its cookies. Because the field is supplied by the (untrusted) WebProcess, a compromised renderer could set an arbitrary cross-site origin, and the NetworkProcess would dispatch a legitimate-looking, cookie-bearing request on the user’s behalf — classic CSRF, but with the network layer itself vouching for the origin.

The fix inserts, at the top of each handler, MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, request.firstPartyForCookies()) == NetworkProcess::AllowCookieAccess::Allow). allowsFirstPartyForCookies consults the NetworkProcess’s per-process map of origins each WebProcess is permitted to use as a first party; if the process was never granted that origin, the check fails and MESSAGE_CHECK terminates the misbehaving connection.

The subtlety the commit message calls out: the check is guarded by if (!request.firstPartyForCookies().isEmpty()). The direct-download policy path (PolicyAction::Download) does not register the WebProcess in the cookie-access map, so an unconditional check would hit the ASSERT_NOT_REACHED() inside allowsFirstPartyForCookies and crash on legitimate downloads (e.g. the _WKDownload.DownloadRequestOriginalURLDirectDownload test). Restricting enforcement to requests that actually carry a non-empty first-party origin preserves those flows while still blocking a renderer from smuggling in an arbitrary attacker-chosen domain.

Key code

First-party cookie access validation added to both download entry points (empty-origin guard preserves the direct-download path)

void NetworkConnectionToWebProcess::startDownload(DownloadID downloadID, const ResourceRequest& request, ...)
{
    if (!request.firstPartyForCookies().isEmpty())
        MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, request.firstPartyForCookies()) == NetworkProcess::AllowCookieAccess::Allow);

    protect(m_networkProcess->downloadManager())->startDownload(m_sessionID, downloadID, request, topOrigin, isNavigatingToAppBoundDomain, suggestedName, fromDownloadAttribute, frameID, pageID, webProcessIdentifier());
}

void NetworkConnectionToWebProcess::convertMainResourceLoadToDownload(...)
{
    RELEASE_ASSERT(RunLoop::isMain());

    if (!request.firstPartyForCookies().isEmpty())
        MESSAGE_CHECK(m_networkProcess->allowsFirstPartyForCookies(m_webProcessIdentifier, request.firstPartyForCookies()) == NetworkProcess::AllowCookieAccess::Allow);

    if (!mainResourceLoadIdentifier) {
        protect(m_networkProcess->downloadManager())->startDownload(m_sessionID, downloadID, request, topOrigin, isNavigatingToAppBoundDomain);
        return;
    }
    // ...
}

Patch walkthrough

  • Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp (startDownload) — Adds a guarded MESSAGE_CHECK at the very start of startDownload(): when request.firstPartyForCookies() is non-empty, it asserts allowsFirstPartyForCookies(m_webProcessIdentifier, request.firstPartyForCookies()) == AllowCookieAccess::Allow before forwarding to the download manager. A renderer that supplies a first-party origin it was not granted has its connection killed by the message check.
  • Source/WebKit/NetworkProcess/NetworkConnectionToWebProcess.cpp (convertMainResourceLoadToDownload) — Adds the identical guarded MESSAGE_CHECK immediately after the RELEASE_ASSERT(RunLoop::isMain()) and before the request is handed to the download manager (both the !mainResourceLoadIdentifier direct-start branch and the resource-load-conversion branch). Same non-empty-first-party guard, same Allow requirement.

Background

firstPartyForCookies — The request’s first-party (top-level) URL. The cookie store uses it to decide which cookies to attach and to evaluate SameSite; a spoofed value makes a cross-site request look first-party and carry that origin’s cookies.

allowsFirstPartyForCookies — NetworkProcess method that checks a per-WebProcess map of origins a process is permitted to use as first party, returning AllowCookieAccess::Allow or not. It contains an ASSERT_NOT_REACHED() for processes absent from the map, which is why the new check must be guarded.

PolicyAction::Download — The navigation policy outcome that turns a load directly into a download. This path does not add the WebProcess to the first-party cookie-access map, so an unconditional check on an empty first-party origin would crash legitimate direct downloads.

MESSAGE_CHECK — WebKit IPC-hardening macro that terminates the offending connection when its condition is false, used to enforce that a WebProcess cannot assert privileges (here, a first-party origin) it does not hold.

Vulnerability window

  1. Missing validationstartDownload() and convertMainResourceLoadToDownload() accepted a WebProcess-supplied ResourceRequest and used its firstPartyForCookies() without checking the process was entitled to that origin.
  2. Impact — A compromised WebProcess could set an arbitrary cross-site first-party origin, causing the NetworkProcess to issue cookie-bearing, first-party-looking requests the user never initiated (CSRF with real cookies).
  3. Report — Filed as webkit.org/b/314298 / rdar://173378006: add cookie access validation to the two download entry points to prevent CSRF.
  4. Fix + test-breakage handling — Rupin Mittal added the MESSAGE_CHECK to both handlers, then scoped it behind !firstPartyForCookies().isEmpty() after finding it crashed the _WKDownload.DownloadRequestOriginalURLDirectDownload test via ASSERT_NOT_REACHED() in allowsFirstPartyForCookies on the direct-download path.
  5. Ship — Landed as 314583@main; branch-landed as 305413.882 on safari-7624 (rdar://173378006).

Triggering

No proof of concept is included in or reconstructable from the patch — it adds only the message checks and ships no test. A demonstration would require a compromised WebProcess capable of sending a StartDownload or ConvertMainResourceLoadToDownload IPC with a ResourceRequest whose firstPartyForCookies() is set to a victim origin the process was never granted; the patch supplies no such primitive, so we mark it unavailable rather than fabricate one. The pre-fix observable would be a download request carrying the victim origin’s first-party cookies; post-fix the connection is terminated by MESSAGE_CHECK.

Exploitation

  1. Precondition: renderer control of download IPC — The attacker needs a compromised (or otherwise coerced) WebContent process able to send StartDownload/ConvertMainResourceLoadToDownload with a crafted ResourceRequest. This is a post-compromise cross-origin escalation, not an unauthenticated remote bug.
  2. Spoof the first-party origin — Set request.firstPartyForCookies() to a victim site the process has no cookie access to, targeting the download URL at that site so the NetworkProcess treats the request as first-party to the victim.
  3. Issue the cookie-bearing request — Pre-fix, the NetworkProcess dispatches the download with the victim origin’s cookies attached and SameSite treated as first-party, forging an authenticated cross-site request; the fix terminates the connection instead.

Detection & hunting

For defenders and SOC / detection engineers:

  • MESSAGE_CHECK terminations on download IPCs
  • Download requests with mismatched first-party origin
  • Direct-download ASSERT interactions

Audit directions

  • Other WebProcess-supplied firstPartyForCookies consumers
  • Empty-origin guard soundness
  • Cookie-access map population

Before / after

Loading diff…