Three HTTP headers — Cross-Origin-Opener-Policy (COOP), Cross-Origin-Embedder-Policy (COEP) and Cross-Origin-Resource-Policy (CORP) — together form a mechanism called cross-origin isolation. Configured correctly, they protect your site against Spectre-style attacks and tabnapping, and unlock SharedArrayBuffer and high-resolution timers. Configured incorrectly, they silently break YouTube embeds, Stripe Checkout, and Google Sign-In. This guide shows how to set them up right.

TL;DR — three headers in 60 seconds

HeaderWhat it controlsWhen to set it
COOPWhether other windows/popups can access your windowAlways — protects against tabnapping
COEPWhether your page can load subresources without consentOnly if you need SharedArrayBuffer or crossOriginIsolated
CORPWho can load your resources (images, scripts)On resources you host for other sites

For a typical marketing site / blog, this is enough:

Cross-Origin-Opener-Policy: same-origin-allow-popups

For a web app that needs SharedArrayBuffer (multithreaded WebAssembly, FFmpeg.wasm, games, video editors):

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Why these headers exist

Spectre (2018)

In January 2018 the Spectre vulnerability was disclosed — a side-channel attack exploiting speculative execution in CPUs. It allowed reading memory from other processes, including other browser tabs. For browsers, this was catastrophic: JavaScript from an ad frame could theoretically read passwords from another tab.

The response was Site Isolation (Chrome) and eventually cross-origin isolation at the HTTP level. Pages that want access to high-resolution timers and shared memory (SharedArrayBuffer) must prove they are isolated from potentially hostile origins.

Tabnapping and opener exploitation

When you open a window via <a target="_blank"> or window.open(), the new window gets access to window.opener. Without protection, this reference lets a malicious page rewrite the parent window’s content (window.opener.location = 'phishing.com'). This is classic tabnapping.

rel="noopener" on links solves the problem for individual links, but COOP solves it at the whole-page level, regardless of how the window was opened. Cross-origin headers are one of many defensive layers — incidents like the 2026 WordPress plugin supply-chain attack show that without defense-in-depth, a single vulnerability can open the door to hundreds of thousands of sites.

COOP — Cross-Origin-Opener-Policy

COOP decides who has access to your window object. Three valid values:

unsafe-none (default)

Cross-Origin-Opener-Policy: unsafe-none

No isolation. Any page that opens you via window.open() keeps its window.opener reference and can exploit it. Do not use in production — even if you never use popups, you’re leaving the door open to tabnapping.

Cross-Origin-Opener-Policy: same-origin-allow-popups
  • Inbound isolation: other-origin pages opening you lose access to your window.
  • Outbound isolation: popups you open keep normal communication with their opener.

This is the sweet spot. You get tabnapping protection without breaking OAuth, sharing, or payment popups. This is what we use on uper.pl.

same-origin (full isolation)

Cross-Origin-Opener-Policy: same-origin

Full isolation in both directions. No other-origin page has access to your window, and vice versa. This is required to reach crossOriginIsolated === true and unlock SharedArrayBuffer.

The cost: popups from other origins opened from your page immediately lose window.opener. This is exactly what breaks YouTube, Stripe, and Google Sign-In.

Decision table

Site typeRecommended value
Marketing site / blogsame-origin-allow-popups
SaaS with embeds (YouTube, Stripe, Intercom)same-origin-allow-popups
SPA with multithreaded WebAssemblysame-origin
Video editor / in-browser gamesame-origin
E-commerce with popup checkoutsame-origin-allow-popups

COEP — Cross-Origin-Embedder-Policy

COEP enforces that every resource loaded by your page from other origins explicitly consents to being embedded by you. This is the second half of the crossOriginIsolated requirement.

unsafe-none (default)

No requirements. Load anything from anywhere.

require-corp

Cross-Origin-Embedder-Policy: require-corp

Every cross-origin resource (image, script, iframe) must either:

  • Have a Cross-Origin-Resource-Policy: cross-origin header (explicit consent), or
  • Be loaded with CORS (Access-Control-Allow-Origin).

Otherwise the browser blocks it. This is strict — it will break most CDN and third-party integrations that don’t set CORP/CORS.

credentialless (newer, 2022+)

Cross-Origin-Embedder-Policy: credentialless

A looser variant: instead of requiring CORP/CORS, the browser simply loads resources without cookies or other credentials. For most use cases (images, fonts, public APIs) this is enough, and it requires far fewer changes from third-party providers.

Browser support: Chrome 96+, Firefox 119+. Safari only joined in 2024 — if you support older iOS, have a fallback.

CORP — Cross-Origin-Resource-Policy

The opposite perspective. If you host resources (CDN, image API, fonts for other sites), CORP tells the browser who is allowed to load them.

Values

Cross-Origin-Resource-Policy: same-origin      # only your domain
Cross-Origin-Resource-Policy: same-site        # your domain and subdomains
Cross-Origin-Resource-Policy: cross-origin     # any site

When to use which

  • same-origin — private resources (user data APIs, dashboard images).
  • same-site — resources shared across subdomains (logo on app.example.com and www.example.com).
  • cross-origin — public resources that you want others to embed (images for embeds, public fonts, avatar CDN).

If you’re an API provider and your customers complain that their COEP-enabled sites stopped loading your images — you’re missing Cross-Origin-Resource-Policy: cross-origin.

The magic flag: self.crossOriginIsolated

When you set both:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

you get in JavaScript:

self.crossOriginIsolated === true

What this unlocks:

  • SharedArrayBuffer — shared memory across threads (Web Workers). Essential for multithreaded WebAssembly, FFmpeg.wasm, Three.js with Web Workers, emulators. Note: full isolation can degrade LCP because third-party resources need additional CORP/CORS headers and get blocked without them — which slows down first render in practice.
  • performance.measureUserAgentSpecificMemory() — precise memory usage measurement.
  • High-resolution timersperformance.now() without intentional coarsening (a Spectre mitigation).
  • Atomics.wait() on the main thread.

For a typical marketing site, none of this matters. For a WebAssembly application, it’s critical.

Real-world problems and fixes

YouTube embed stopped working

Symptom: the video renders, but clicking “Share”, sign-in, or fullscreen (in some browsers) doesn’t work.

Cause: you set Cross-Origin-Opener-Policy: same-origin. The YouTube player opens popups that immediately lose their opener reference.

Fix: switch to same-origin-allow-popups. If you need crossOriginIsolated (e.g. for WASM), move YouTube to a separate page without isolation.

Stripe Checkout popup doesn’t close after payment

Symptom: the user pays, the popup stays open, your site doesn’t know the transaction succeeded.

Cause: Stripe calls window.opener.postMessage() to your site after completion. With COOP: same-origin, the reference is null.

Fix: same-origin-allow-popups, or use Stripe Elements instead of Checkout (renders inside your iframe, not a popup).

Google Sign-In / OAuth callback doesn’t arrive

Symptom: the user signs in in the Google popup, the popup closes, but your app never receives the token.

Cause: same as Stripe — postMessage from the popup hits a null opener.

Fix: same-origin-allow-popups. Alternatively — use Google Identity Services (GIS) with FedCM, which uses a dedicated API instead of popups.

CDN images stop loading after enabling COEP

Symptom: after adding COEP: require-corp, all images from your external CDN (Cloudinary, imgix, Gravatar) fail to load.

Cause: the CDN doesn’t return a Cross-Origin-Resource-Policy: cross-origin header.

Fixes:

  1. Configure the CDN to add Cross-Origin-Resource-Policy: cross-origin to responses (most have this option).
  2. Load images with crossorigin="anonymous" and ensure the CDN returns Access-Control-Allow-Origin: *.
  3. Use the looser COEP: credentialless instead of require-corp.

Social sharing popup (Twitter/LinkedIn) behaves erratically

Symptom: the “Share on Twitter” popup opens, but interactions inside it are weird or crash.

Cause: COOP: same-origin on your site.

Fix: same-origin-allow-popups.

How to debug

1. Check isolation status

In the DevTools console:

self.crossOriginIsolated
// true  = you have full isolation
// false = you don't

2. DevTools → Application → Frames

Chrome DevTools → Application tab → Frames section → click your page. You’ll see:

  • Cross-Origin Isolated: yes/no
  • Exact COOP, COEP, CORP values
  • List of sub-frames and their isolation status

3. Violation reports

You can collect COOP violation reports:

Cross-Origin-Opener-Policy: same-origin; report-to="coop-endpoint"
Reporting-Endpoints: coop-endpoint="https://yourdomain.com/coop-report"

You’ll receive JSON describing which frame tried to do something and got blocked. Essential when debugging silent failures.

4. Browser console

COEP blocks show up in the console as:

A resource is blocked by OpaqueResponseBlocking, please check browser console for details.

Failed to load resource: net::ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep

Ready-made configurations

Cross-Origin-Opener-Policy: same-origin-allow-popups
Cross-Origin-Resource-Policy: same-site

No COEP. Protects against tabnapping, doesn’t break any integrations.

E-commerce with external checkout

Cross-Origin-Opener-Policy: same-origin-allow-popups
Cross-Origin-Resource-Policy: same-site

Identical. The key point — do not use same-origin, because it will kill Stripe/PayPal popups.

SPA with WebAssembly (SharedArrayBuffer)

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: same-origin

Plus: every external resource (CDN, fonts, images) must have Cross-Origin-Resource-Policy: cross-origin or CORS.

Public API / CDN provider

Cross-Origin-Resource-Policy: cross-origin

That’s it. You’re telling the world: “feel free to use me, even from isolated pages”.

Interaction with other headers

Frequently asked questions about COOP, COEP and CORP

What does same-origin-allow-popups mean in Cross-Origin-Opener-Policy?

It's a COOP value that isolates your page from inbound windows from other origins (tabnapping protection), but lets popups you open keep communication with your page. This is the best value for most marketing sites and apps using third-party integrations (YouTube, Stripe, Google Sign-In).

Why did my YouTube embed stop working after enabling COOP?

You probably set Cross-Origin-Opener-Policy: same-origin (full isolation). The YouTube player opens popups (Share, sign-in, fullscreen) that under full isolation lose their reference to your page, so communication between them fails. Switch to same-origin-allow-popups — you keep tabnapping protection and YouTube works again.

How do I enable SharedArrayBuffer in the browser?

You need to reach crossOriginIsolated === true. This requires setting two HTTP headers simultaneously: Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp. Additionally, every cross-origin resource you load (images, scripts, fonts) must have a Cross-Origin-Resource-Policy: cross-origin header or be loaded with CORS.

What is the difference between COOP, COEP and CORP?

COOP (Cross-Origin-Opener-Policy) protects your window from other pages — it governs tab and popup isolation. COEP (Cross-Origin-Embedder-Policy) enforces that resources you load from other origins explicitly consent. CORP (Cross-Origin-Resource-Policy) is the inverse of COEP — you send it on resources you host to tell the browser who is allowed to load them.

Does a regular blog site need COEP and CORP?

No. For a typical marketing site, blog, or shop, Cross-Origin-Opener-Policy: same-origin-allow-popups is enough. COEP and CORP are only needed when your app uses SharedArrayBuffer, multithreaded WebAssembly, or you host public resources for other sites.

What should I do when my image CDN breaks after enabling COEP?

Three options: (1) configure the CDN to add Cross-Origin-Resource-Policy: cross-origin to responses — most CDNs support this; (2) load images with crossorigin="anonymous" and ensure the CDN returns Access-Control-Allow-Origin: *; (3) use the looser Cross-Origin-Embedder-Policy: credentialless instead of require-corp — it doesn't require changes from third-party providers.

Does setting COOP break Google Analytics or Google Tag Manager?

No. GA4 and GTM communicate via fetch/XHR, not popups, so no COOP value will break them. Problems only appear with interactions that open new windows: Google Sign-In, Stripe Checkout, social sharing. See also the CSP guide for Google services.

What is credentialless in Cross-Origin-Embedder-Policy?

It's a newer, looser COEP value (Chrome 96+, Firefox 119+, Safari 2024+). Instead of requiring external resources to explicitly consent via CORP or CORS, the browser simply loads them without cookies or other credentials. For public resources (images, fonts, public APIs) this is enough, and it requires far fewer changes from third-party providers.

Check your cross-origin isolation with UPER SEO Auditor

Diagnosing silent COOP/COEP failures is painful — the symptoms are weird popup behavior and broken OAuth, not clean error messages. The Security tab in UPER SEO Auditor (Chrome extension) shows all three headers for the current page:

  • Cross-Origin-Opener-Policy — current value and whether it matches a sensible default
  • Cross-Origin-Embedder-Policy — detected or missing, including credentialless support
  • Cross-Origin-Resource-Policy — for resources served by the page itself

All three feed into a weighted 0-10 security score alongside HSTS, CSP, X-Frame-Options, Referrer-Policy and Permissions-Policy. Install it, open the Security tab, and you’ll see your real production headers without touching the terminal.

Summary

For 95% of sites, the only header from this trio you need is:

Cross-Origin-Opener-Policy: same-origin-allow-popups

It protects against tabnapping and doesn’t break anything. COEP and CORP are only needed if you host resources for others or your app uses SharedArrayBuffer.

If you already deployed full isolation (same-origin + require-corp) and suddenly notice silent popup failures — switch back to same-origin-allow-popups. The security cost is minimal, the functionality cost is huge.

Sources