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
| Header | What it controls | When to set it |
|---|---|---|
| COOP | Whether other windows/popups can access your window | Always — protects against tabnapping |
| COEP | Whether your page can load subresources without consent | Only if you need SharedArrayBuffer or crossOriginIsolated |
| CORP | Who 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.
same-origin-allow-popups (recommended for most sites)
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 type | Recommended value |
|---|---|
| Marketing site / blog | same-origin-allow-popups |
| SaaS with embeds (YouTube, Stripe, Intercom) | same-origin-allow-popups |
| SPA with multithreaded WebAssembly | same-origin |
| Video editor / in-browser game | same-origin |
| E-commerce with popup checkout | same-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-originheader (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 onapp.example.comandwww.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 timers —
performance.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:
- Configure the CDN to add
Cross-Origin-Resource-Policy: cross-originto responses (most have this option). - Load images with
crossorigin="anonymous"and ensure the CDN returnsAccess-Control-Allow-Origin: *. - Use the looser
COEP: credentiallessinstead ofrequire-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
Marketing site / blog (recommended)
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
- CSP — orthogonal. CSP controls what you load, COOP/COEP control isolation terms. See the CSP guide for Google services.
- X-Frame-Options — about whether you can be embedded in an iframe. A different category than COOP.
- Referrer-Policy — unrelated.
- HSTS — unrelated. See the complete HTTP security headers guide.
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
credentiallesssupport - 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.



