UnveilTech

UnveilScan Blog

← All articles

Try UnveilScan free

Cross-origin isolation: COOP, COEP, CORP

Posted 2026-04-29 · 7 min read · WEBadvanced

In 2018 Spectre demonstrated that a malicious page sharing a process with a sensitive page could read its memory via timing side-channels. Browsers' answer: cross-origin isolation. Three headers — COOP, COEP, CORP — work together to put the page in a browsing-context-group of one, where Spectre-class attacks have nowhere to land. Required for SharedArrayBuffer, high-resolution timers, and a few other "powerful features." Mostly invisible until you need it.

The three headers

HeaderWhat it does
Cross-Origin-Opener-Policy (COOP)"My window has no shared browsing context with windows it didn't open." Set on top-level documents. Value: same-origin.
Cross-Origin-Embedder-Policy (COEP)"Every cross-origin resource I embed must opt in via CORP or CORS." Value: require-corp.
Cross-Origin-Resource-Policy (CORP)"This resource agrees to be embedded by these origins." Set per-resource. Values: same-site, same-origin, cross-origin.

COOP and COEP go on your top-level document. CORP goes on every static asset, image, font, JS bundle. When all three line up, the browser flips self.crossOriginIsolated === true and unlocks SharedArrayBuffer, performance.now() at full resolution, and a few other gated capabilities.

The threat model in plain terms

Without COOP, opening example.com via window.open gives the opener a handle (window.opener) to your tab. Most apps null this out defensively. With COOP same-origin, the browser severs that link automatically. The cross-origin opener no longer has a usable handle to your window, your COOP-protected tab no longer has a handle to its opener.

Without COEP, your origin can <img src="https://example.com/secret.png"> and use Spectre-style timing to extract pixel data. With COEP require-corp, the image must explicitly opt in to being embedded (via CORP) — the resource owner now controls who embeds it.

The third-party gotcha

Most CDNs serve assets without CORP headers (the default is "no opinion," which fails COEP require-corp checks). Once you turn on COEP, every cross-origin image, font, script, iframe that doesn't ship CORP breaks. fonts.googleapis.com doesn't ship CORP. Your analytics provider's pixel doesn't. Stripe.js's iframe might.

Two escape hatches:

Deployment order

Don't try to do all three at once. The right order:

  1. CORP everywhere first. Add Cross-Origin-Resource-Policy: same-origin to all your static assets. No app behavior changes — same-origin requests don't care about CORP. Confirms your asset pipeline can set headers.
  2. COOP next. Cross-Origin-Opener-Policy: same-origin. Catches window.opener dependencies. The mistake here: SSO popups that postMessage back to the opener. With COOP same-origin those won't work — switch to same-origin-allow-popups.
  3. COEP last. Cross-Origin-Embedder-Policy: require-corp or credentialless. This is the one that breaks third-party embeds. Roll out behind a feature flag. Use Reporting API to collect the violations first.

Reporting API for COEP

Cross-Origin-Embedder-Policy-Report-Only: require-corp; report-to="coep-endpoint"
Reporting-Endpoints: coep-endpoint="https://example.com/coep-reports"

Browser sends a JSON report for every blocked sub-resource. After a week, your endpoint has a complete inventory of cross-origin resources without CORP. Self-host them or negotiate with the providers.

Who actually needs this

You need cross-origin isolation if you use:

You should consider it as defense-in-depth even if you don't, especially if your app handles secrets in JS context (financial dashboards, password managers, identity providers).

You should NOT do it if your app is fundamentally an aggregator of cross-origin embeds — news sites with 30 ads from different networks, marketplace pages with seller-uploaded content from a different domain. The retrofit cost exceeds the security benefit.

The simplest correct deployment

# For a same-origin SPA serving static assets from the same domain:
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Embedder-Policy "credentialless" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;

# Verify in the page:
<script>
  if (self.crossOriginIsolated) console.log('COI active');
  else console.warn('COI failed');
</script>

What we report

Our headers checker reports COOP, COEP, CORP presence. We don't currently emit findings for absence — these are advanced posture markers, not basic hygiene. We do flag inconsistent values (COEP set but CORP missing on serve assets) as INFO. The scoring impact is small; this is informational for teams that already passed all the basics.

Inventory your headers

Free Basic scan reports all security headers. Extended adds CSP maturity grading and report-uri reachability checks.

Run a scan