How Safari Enforces CORS and COOP Security Headers

This article provides a direct overview of how Apple’s Safari web browser, powered by the WebKit engine, enforces Cross-Origin Resource Sharing (CORS) and Cross-Origin-Opener-Policy (COOP) headers. It breaks down the exact mechanisms Safari uses to validate cross-origin network requests through CORS, explains how the browser isolates top-level documents using COOP to prevent side-channel attacks, and highlights WebKit-specific nuances developers must handle when configuring these security policies.

Cross-Origin Resource Sharing (CORS) in Safari

Safari uses the WebKit layout engine to enforce the Same-Origin Policy (SOP) by default, which prevents a document or script loaded from one origin from interacting with resources from another origin. To allow legitimate cross-origin network requests, Safari relies on the strict evaluation of CORS headers.

1. Simple Requests

When Safari makes a "simple" cross-origin request (such as a basic GET or POST request with standard headers), it automatically appends the Origin header to the outgoing request.

When the server responds, Safari checks for the Access-Control-Allow-Origin header:

  • Match: If the header value matches the requesting origin (or is a wildcard * for non-credentialed requests), Safari passes the response data to the JavaScript environment.
  • Mismatch or Missing: If the header is missing or does not match, Safari blocks the JavaScript code from reading the response and logs a CORS error in the Web Inspector console, even though the network request itself succeeded at the HTTP level.

2. Preflight Requests

For complex operations—such as requests using PUT or DELETE methods, application/json content types, or custom headers—Safari enforces a mandatory preflight check.

  • Before sending the actual request, Safari dispatches an OPTIONS request to the server.
  • This request includes Access-Control-Request-Method and Access-Control-Request-Headers.
  • Safari waits for a 2xx status code response containing the corresponding Access-Control-Allow-Methods and Access-Control-Allow-Headers.
  • If the preflight fails or attempts an HTTP redirect, Safari immediately aborts the actual request.

3. Credentials and Intelligent Tracking Prevention (ITP)

When a fetch or XMLHttpRequest includes credentials (cookies, authorization headers, or TLS client certificates), Safari enforces stricter CORS rules:

  • The server must respond with Access-Control-Allow-Credentials: true.
  • Safari strictly forbids the use of the * wildcard in the Access-Control-Allow-Origin header when credentials are included. The exact origin must be echoed back.
  • The ITP Factor: Even if CORS headers are configured correctly for credentials, Safari’s Intelligent Tracking Prevention (ITP) blocks third-party cookies by default. Therefore, cross-origin credentialed requests will not send cookies in Safari unless the user has explicitly granted permission via the Storage Access API.

4. Safari-Specific CORS Strictness

Safari is notably stricter than other browsers regarding how it caches and processes certain CORS requests:

  • Media Elements: Safari strictly requires CORS headers on <video> and <audio> elements if they are drawn into a <canvas> or manipulated via the Web Audio API. Without the crossorigin attribute on the HTML tag and the corresponding server headers, Safari taints the canvas and blocks data extraction.
  • Vary Header: If a server dynamically generates the Access-Control-Allow-Origin header based on the request, Safari requires the server to include Vary: Origin in the response. Without this, Safari may serve a cached response with the wrong CORS header to a different origin, resulting in a failure.

Cross-Origin-Opener-Policy (COOP) in Safari

Introduced to Safari in version 15.2, the Cross-Origin-Opener-Policy (COOP) header is enforced by WebKit to provide process isolation for top-level documents. This prevents malicious cross-origin pages from exploiting side-channel attacks (like Spectre) or abusing the window.opener object.

1. Managing Browsing Context Groups

When a website opens a new window or tab using window.open() or an anchor tag with target="_blank", the two windows normally share a "browsing context group." This allows them to communicate synchronously via the window.opener DOM property. Safari enforces COOP by breaking this link and placing documents into separate browsing context groups (and often separate operating system processes) based on the header's value.

2. Enforcement of COOP Values

Safari only evaluates the COOP header on top-level documents; it completely ignores the header if it is present on iframes or subresources. Safari enforces the three standard COOP directives as follows:

  • unsafe-none (Default): Safari takes no action. The document stays in the same browsing context group as its opener, unless the opener itself has a strict COOP policy.
  • same-origin-allow-popups: Safari isolates the current document from any cross-origin window that opened it. However, if the current document opens a new popup using window.open(), Safari allows them to stay in the same group unless that new popup explicitly enforces its own COOP policy of same-origin.
  • same-origin: This is the strictest enforcement. Safari places the document into a dedicated browsing context group. It can only share this group with other documents that are from the exact same origin and also declare a same-origin COOP header. If a same-origin document opens a cross-origin popup, Safari sets window.opener to null in the popup, and the opener loses the ability to control or inspect the popup reference.

3. Enabling Cross-Origin Isolation

Safari uses COOP enforcement as a prerequisite for enabling powerful web features that pose security risks in shared memory environments, specifically SharedArrayBuffer and high-resolution timers (performance.now()).

To unlock these features, Safari requires the document to be in a "cross-origin isolated" state. Safari only grants this state if the server sends both of the following headers on the main document:

  1. Cross-Origin-Opener-Policy: same-origin
  2. Cross-Origin-Embedder-Policy: require-corp (or credentialless)

When Safari detects both headers, it isolates the page's process from other origins via COOP, ensures all subresources opt-in to being loaded via COEP (Cross-Origin-Embedder-Policy) or CORS, and subsequently sets the window.crossOriginIsolated property to true. If either header is missing or misconfigured, Safari blocks access to SharedArrayBuffer.