COOP, COEP, CORP: the cross-origin headers your app probably skipped

Open your app in a tab and another site in the next tab over. By default, those two pages share more than you'd expect. A page you didn't write, opened from a link in yours or holding a reference to your window, can poke at your browsing context: count how long a request took, watch when a frame loads, hold a handle to your window object. None of it reads your data directly, but each signal is a side channel, and side channels are how a determined attacker reconstructs what they can't see.

Three response headers close those gaps. They have ungainly names, Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy, and Cross-Origin-Resource-Policy, and most apps set none of them. For a static brochure site, that's fine. For an app that runs a real login and holds a session, it's a defense layer left off.

What each header actually walls off

These three do related but distinct jobs, and it helps to keep them straight rather than treating them as one block to copy-paste.

Cross-Origin-Opener-Policy (COOP) severs the link between your page and any window that opened it or that it opened. Without it, a page reached through window.open or a target=_blank link keeps a live reference to yours, which is the handle used in tabnabbing and a building block in cross-window leak attacks. Cross-Origin-Opener-Policy: same-origin cuts that reference so the other context can't reach into yours.

Cross-Origin-Resource-Policy (CORP) lets a response say who is allowed to embed it. Set Cross-Origin-Resource-Policy: same-origin on a sensitive endpoint and a foreign page can no longer pull it into an <img>, <script>, or <link> to probe it. It's the per-response companion to the broader policy headers.

Cross-Origin-Embedder-Policy (COEP) is the strict one, and the one to add carefully. require-corp tells the browser to refuse any cross-origin resource that hasn't explicitly opted in via CORP or CORS. Set COOP and COEP together and the page becomes cross-origin isolated, which is what unlocks powerful APIs like SharedArrayBuffer and high-resolution timers, and what fully shuts the Spectre-style measurement attacks those timers would otherwise enable. COEP can break third-party embeds that haven't set their own headers, so it's the one to roll out behind a report-only pass first.

request
GET /dashboard HTTP/1.1 Host: app.yoursite.com Cookie: session=...
response
HTTP/1.1 200 OK
Set-Cookie: session=...; HttpOnly; Secure
Content-Security-Policy: default-src 'self'
(no COOP, no COEP, no CORP)
A session cookie and a CSP say this is a real app surface. The three isolation headers that would harden its window are missing.

Why this fires on your app and not on a marketing page

The honest reason most sites don't have these headers is that most sites don't need them, and firing on every site that omits them would be noise. A static landing page with no login and no sensitive state loses nothing by skipping cross-origin isolation. So the bar for flagging it has to be higher than "the header is absent."

The signal that you're looking at a surface where it matters is whether the page is doing real security work. A session-shaped cookie in the Set-Cookie response, or a CSP already in place, both mark an app that handles auth and holds state, exactly the place a cross-window side channel is worth something. That's the gate: on an HTTPS response that carries a session cookie or a CSP and still ships none of COOP, COEP, or CORP, the headers are a missing layer worth naming. On a plain brochure page, nothing fires.

It's a low-severity finding, defense-in-depth rather than an open door. You won't get breached tomorrow because COOP is missing. But on an app holding live sessions, it's a cheap layer to add, and "cheap and you already do the rest" is exactly the kind of gap worth closing.

# app response headers
Set-Cookie: session=...; HttpOnly; Secure
Content-Security-Policy: default-src 'self'
Add COOP and CORP first (safe almost everywhere); stage COEP behind report-only because it can break third-party embeds.

Checking what your app surface actually serves

Response headers are public by definition, so whether your app ships COOP, COEP, and CORP is something anyone can read on a single request, no login required. SurfaceCheckr looks at your HTTPS response, decides whether it's a real application surface by the session-cookie-or-CSP signal, and if it is, lists which of the three isolation headers are missing. It deliberately stays quiet on a plain static site, because flagging a missing defense layer somewhere it adds nothing is the kind of false positive that makes a report worth less. On an app holding live sessions, it's the layer worth knowing you skipped.

For the headers that protect every site, not just app surfaces, the small security headers article covers nosniff and Referrer-Policy, and the CORS misconfiguration piece covers the related question of which origins can read your responses.

Find it before someone else does.

Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.