Cross-Origin Isolation with COOP and COEP

Some powerful browser features — SharedArrayBuffer, performance.now() at high resolution, and Atomics.wait() — are locked behind cross-origin isolation. A page becomes cross-origin isolated only when it sets two response headers together: Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy. Turning them on frequently surfaces new CORS and CORP requirements on subresources that loaded fine before, which is where this overlaps with everyday cross-origin debugging.

This page is part of CORS & Related Security Header Interactions, which explains how CORS sits alongside the other cross-origin security mechanisms.

The Symptom This Resolves

You enable the isolation headers and a previously-working cross-origin resource suddenly fails, or SharedArrayBuffer is still undefined:

A cross-origin resource at https://cdn.example.com/lib.js was blocked because
Cross-Origin-Embedder-Policy: require-corp was set and the resource did not supply
a Cross-Origin-Resource-Policy header or a valid CORS response.

Or, checking in the console:

self.crossOriginIsolated   // false — isolation not achieved
typeof SharedArrayBuffer    // "undefined"

Root Cause

After the Spectre side-channel attacks, browsers restricted shared memory and precise timers, because together they let a malicious page infer bytes across the origin boundary. Those features are now gated behind the crossOriginIsolated state, defined in the HTML standard, which is true only when the document sends both Cross-Origin-Opener-Policy: same-origin (severing the opener relationship with cross-origin windows) and Cross-Origin-Embedder-Policy: require-corp (requiring every embedded cross-origin resource to explicitly opt in). Under require-corp, a cross-origin subresource must either send Cross-Origin-Resource-Policy: cross-origin or be fetched in CORS mode with a valid Access-Control-Allow-Origin — otherwise it is blocked, even if it loaded before isolation was enabled.

Prerequisite State

Step-by-Step

Step 1 — Set the opener policy

add_header Cross-Origin-Opener-Policy "same-origin" always;

Step 2 — Set the embedder policy

add_header Cross-Origin-Embedder-Policy "require-corp" always;

Step 3 — Opt in each cross-origin subresource

For a resource you control on another origin, add a CORP header:

# On the CDN / asset origin
add_header Cross-Origin-Resource-Policy "cross-origin" always;

For a resource loaded via a tag, request it in CORS mode so a valid Access-Control-Allow-Origin satisfies require-corp:

<script src="https://cdn.example.com/lib.js" crossorigin="anonymous"></script>
<img src="https://cdn.example.com/logo.png" crossorigin="anonymous" alt="Logo">

Step 4 — Confirm isolation

console.log(self.crossOriginIsolated);  // should log true

Verification

Security Boundary Note

require-corp is a hardening feature — do not weaken subresource policies just to silence a block. Prefer adding Cross-Origin-Resource-Policy: cross-origin only to assets that are genuinely safe to embed anywhere (public fonts, images, scripts). Do not set Cross-Origin-Resource-Policy: cross-origin on responses that contain user- or tenant-scoped data, and do not switch a credentialed API to crossorigin mode with a wildcard origin — the wildcard-plus-credentials prohibition still applies inside an isolated context.

Common Mistakes

Issue Technical impact Mitigation
Setting only COEP or only COOP crossOriginIsolated stays false; features stay locked Set both headers on the document
Cross-origin subresource with neither CORP nor CORS Resource blocked under require-corp; page breaks Add Cross-Origin-Resource-Policy or load with crossorigin
Adding Cross-Origin-Resource-Policy: cross-origin to private data Any page can embed sensitive responses Use it only on genuinely public assets

FAQ

Why do I need cross-origin isolation for SharedArrayBuffer?

SharedArrayBuffer and high-resolution timers were restricted after the Spectre side-channel attacks, because precise timers plus shared memory let a malicious page infer data across origins. Browsers now gate them behind the crossOriginIsolated state, which is only true when the document sets both Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. Without isolation, SharedArrayBuffer is unavailable and timers are coarsened.

How does COEP interact with CORS?

Cross-Origin-Embedder-Policy: require-corp forces every cross-origin subresource to opt in, either by sending Cross-Origin-Resource-Policy: cross-origin or by loading in CORS mode with a valid Access-Control-Allow-Origin. A cross-origin image or script sending neither is blocked once require-corp is set. Enabling isolation can therefore surface CORS/CORP requirements on resources that previously had none.

What is the difference between CORP and CORS?

Cross-Origin-Resource-Policy (CORP) is a response header by which a resource declares who may embed it: same-origin, same-site, or cross-origin. CORS governs whether a script may read a response. Under COEP require-corp, a subresource satisfies the embedder either by sending a permissive CORP header or by being fetched with CORS. CORP is a simpler opt-in for resources safe to embed anywhere.