# CORP vs COEP, two sides of cross-origin isolation (/en/blog/corp-vs-coep)



These two look similar and get mixed up constantly, but they sit on opposite ends
of the same request. Cross-Origin-Resource-Policy (CORP) is something a resource
sends about itself. Cross-Origin-Embedder-Policy (COEP) is something the embedding
page sends about what it will accept. Once you know which side you are on, the
confusion clears up.

The short version: [CORP](/en/docs/web-security/security-headers/cross-origin-resource-policy)
is a response header a resource sets to declare who may load it. [COEP](/en/docs/web-security/policies/cross-origin-embedder-policy)
is a header the embedding page sets to declare that it only loads resources that
opted in. They are two halves of the same mechanism, not competitors, and they
are designed to be used together.

## CORP vs COEP, the one-line difference [#corp-vs-coep-the-one-line-difference]

CORP is set **by the resource** and answers "who may load me." COEP is set **by
the page** and says "I only load resources that opted in."

Everything else follows from that. A CDN, an image host, a font server, each of
those is a resource, so CORP is theirs to send. The page pulling those resources
in is the embedder, so COEP is the embedder's to send. The resource declares its
own reachability; the page declares its own admission rule.

## Cross-Origin-Resource-Policy, set by the resource [#cross-origin-resource-policy-set-by-the-resource]

CORP is a response header the browser reads when another origin tries to load your
resource through a no-cors request (a plain `<img>`, `<script>`, `<audio>`, or a
stylesheet without `crossorigin`). It was first proposed back in 2012 as a header
called `From-Origin`, then revived in 2018 after Spectre made cross-origin data in
the wrong process a real leak. It has exactly three values.

```http
Cross-Origin-Resource-Policy: same-origin
```

* `same-origin`: only the exact same origin (scheme, host, and port) gets the
  resource. The default-deny choice.
* `same-site`: the same registrable domain, so subdomains are allowed. This is
  what a `cdn.example.com` serving `www.example.com` needs; `same-origin` would
  break it. One caveat: `same-site` on an HTTPS resource does not admit a plain
  HTTP page from the same site, so a mixed-scheme setup still fails the check.
* `cross-origin`: any origin may load it. This exists mainly so genuinely public
  assets stay loadable, including by COEP-isolated pages.

When the header is absent, the browser behaves as if `cross-origin` were set, so
any site can embed your asset. Setting a value is opting **out** of that. If the
browser sees a stricter value than the request allows, the load fails as a network
error. Note that the request is still sent and the server still answers; the
browser discards the response body instead of handing it to the page. In Chromium
the failure shows up in DevTools as `net::ERR_BLOCKED_BY_RESPONSE`.

CORP protects against a few distinct things. It keeps your resource out of an
attacker page's process, which is the [Spectre](https://spectreattack.com/)-class
side-channel motivation it was revived for. It blocks cross-site script inclusion
(XSSI). And in browsers that enforce it, which is the vast majority, it prevents
your assets from being displayed on other sites. That is embedding control, not
bandwidth protection: the bytes still transfer before the browser strips them, and
non-browser clients ignore the header entirely.

## Cross-Origin-Embedder-Policy, set by the page [#cross-origin-embedder-policy-set-by-the-page]

COEP is the other half. A page sends `require-corp` to say it will only load a
cross-origin subresource if that resource explicitly opted in.

```http
Cross-Origin-Embedder-Policy: require-corp
```

Here is the part that trips people up. Under `require-corp`, a **missing or
malformed CORP header is treated as `same-origin`.** A cross-origin asset that
sends no CORP at all defaults to same-origin from the embedder's point of view,
so the page cannot load it. That default is exactly what blocks unlabeled
third-party assets once you turn COEP on. Chromium even tracks it as its own
block reason, distinct from a plain CORP mismatch: the console shows
`ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep`. A resource opts in
either by sending `Cross-Origin-Resource-Policy: cross-origin` or by being
fetched through a real CORS request (the `crossorigin` attribute on the tag).

COEP also applies to iframes, beyond plain subresources. Under `require-corp`, an
embedded cross-origin document has to send a compatible COEP header of its own,
or the frame is blocked. CORP on the frame's response is not enough; the framed
document must state its own embedder policy.

## How they combine for cross-origin isolation [#how-they-combine-for-cross-origin-isolation]

The reason to care about both at once is cross-origin isolation. When a page sets
`Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy:
require-corp` together, the browser marks it cross-origin isolated. That state,
readable in JavaScript as `self.crossOriginIsolated`, is the gate for
`SharedArrayBuffer` and unthrottled high-resolution timers. [COOP](/en/docs/web-security/policies/cross-origin-opener-policy)
handles the window side (it puts the page in its own browsing context group and severs
`window.opener` links to cross-origin pages), and COEP handles the subresource
side. Both are required.

This gate is also why most sites that send CORP started doing so. Browsers
disabled `SharedArrayBuffer` at the start of 2018 after Spectre. Firefox brought
it back in 2020, gated behind cross-origin isolation; Chrome re-enabled it on
desktop earlier without that gate, then required isolation on every platform in
2021\.
Anything that needs shared memory today (wasm threads, ffmpeg.wasm, in-browser
video editors) must send COEP, and COEP in turn forces every cross-origin asset
the page loads to carry CORP or pass CORS. You cannot reach the isolated state
if your own assets and your CDN stay silent. The
[COOP and COEP guide to cross-origin isolation](/en/blog/coop-coep-cross-origin-isolation)
walks through the full setup.

### The Access-Control-Allow-Origin gotcha [#the-access-control-allow-origin-gotcha]

A common wrong fix is to add `Access-Control-Allow-Origin` to a resource and
expect `require-corp` to be satisfied. It is not. Sending
`Access-Control-Allow-Origin` on a no-cors response does nothing for COEP. To
satisfy `require-corp` the element has to issue a real CORS request (the
`crossorigin` attribute), or the resource has to send a CORP header. A CORS
response header on a request that was never made in CORS mode is ignored.

### credentialless, the lower-friction option [#credentialless-the-lower-friction-option]

`Cross-Origin-Embedder-Policy: credentialless` is a looser alternative. Instead
of requiring every cross-origin resource to send CORP, it strips credentials (no
cookies, no client certificates) from cross-origin no-cors subresource requests,
so the responses cannot be personalized data worth protecting. It still reaches
cross-origin isolation. Two limits: it only changes no-cors subresources, so
CORS-mode requests follow the `require-corp` rules and cross-origin iframes still
need their own COEP header. And support is partial: `credentialless` works in
Chromium browsers and Firefox but not Safari, so `require-corp` remains the
interoperable choice.

### Test it with Report-Only before you enforce [#test-it-with-report-only-before-you-enforce]

COEP has a dry-run mode. Send `Cross-Origin-Embedder-Policy-Report-Only:
require-corp` with a `report-to` parameter pointing at a
[`Reporting-Endpoints`](/en/docs/web-security/reporting-api/headers/reporting-endpoints)
endpoint, and the browser emits a [`coep` report](/en/docs/web-security/reporting-api/reports/coep)
for every load that enforcement would have blocked, without blocking anything.
The report body tells you whether the failure was a `corp` mismatch, a
`navigation`, or a `worker initialization`, plus the blocked URL. Run it for a
while, fix the flagged resources, then flip to the enforcing header. [CentralCSP
ingests these reports](/platform/monitoring) alongside CSP violations, so
you see every would-be-blocked asset across real traffic before users do.

## CORP vs COEP side by side [#corp-vs-coep-side-by-side]

|                     | Cross-Origin-Resource-Policy                                                                                | Cross-Origin-Embedder-Policy                     |
| ------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| Who sets it         | The resource (image, script, font, media)                                                                   | The embedding page                               |
| What it declares    | Who may load me                                                                                             | I only load resources that opted in              |
| Values              | `same-origin`, `same-site`, `cross-origin`                                                                  | `require-corp`, `credentialless`                 |
| Default when absent | Treated as `cross-origin`, anyone can embed (but flips to `same-origin` under an embedder's `require-corp`) | No policy, cross-origin isolation off            |
| Failure mode        | Blocked load is a network error, body never delivered                                                       | Non-opted-in subresources and frames are blocked |

## Recommendation [#recommendation]

Treat the two together. Set CORP on your own resources at the level they need:
`same-origin` for private assets, `same-site` where subdomains consume them, and
`cross-origin` only on assets that are genuinely public. The OWASP HTTP headers
cheat sheet recommends `same-site` as the general baseline, with `require-corp`
on pages. When you turn on `require-corp`, make sure every asset that page loads,
your own files and your CDN's, sends a CORP header the embedder will accept, or
is fetched with `crossorigin`. Reach for `credentialless` when changing every
third party is not practical and you do not need Safari, and use the Report-Only
header to find breakage before enforcing.

A scanner is the fast way to see which of your responses actually carry CORP and
which pages assert COEP. The [security headers scanner](/tools/security-headers)
checks both across your site so you can spot the gaps before an isolated page
starts failing to load assets.

## FAQ [#faq]

### Do I need CORP if I already have COEP? [#do-i-need-corp-if-i-already-have-coep]

Yes, they cover opposite directions. COEP on your page does nothing to protect
your own resources from being loaded by other sites; only CORP on those responses
does that. And once your page sends `COEP: require-corp`, every cross-origin
asset it loads, including your own CDN subdomain, must send CORP or be fetched
with CORS, because a missing CORP header is treated as `same-origin` and blocked.

### Does COEP require-corp block images? [#does-coep-require-corp-block-images]

Yes. A plain `<img>` pointing at a cross-origin host is a no-cors load, and under
`require-corp` it is blocked (Chromium shows `net::ERR_BLOCKED_BY_RESPONSE`)
unless the image server sends `Cross-Origin-Resource-Policy: cross-origin` (or a
matching `same-site`), or the tag uses `crossorigin` and the server answers with
valid CORS headers. If you control neither, proxy the image through your own
origin or use `COEP: credentialless`.

### What is the difference between CORP and CORS? [#what-is-the-difference-between-corp-and-cors]

They move in opposite directions. CORS is a server opt-in that grants cross-origin
read access to responses for requests made in CORS mode. CORP is a server
restriction that removes the default embeddability of no-cors loads (images,
scripts, media that any page could historically include). CORP only affects
no-cors requests, and CORS headers on a no-cors response are ignored, which is
why `Access-Control-Allow-Origin` alone never satisfies `require-corp`.

### Should I use credentialless instead of require-corp? [#should-i-use-credentialless-instead-of-require-corp]

A COEP value that reaches cross-origin isolation without requiring CORP from
every third party. Cross-origin no-cors subresource requests are sent without
credentials (no cookies, no client certificates), so the responses cannot carry
personalized data. CORS-mode requests still follow `require-corp` rules, and
cross-origin iframes still need their own COEP header. It works in Chromium
browsers and Firefox but not Safari.

## Related reading [#related-reading]

* [COOP and COEP, cross-origin isolation explained](/en/blog/coop-coep-cross-origin-isolation)
* [Cross-Origin-Opener-Policy reference](/en/docs/web-security/policies/cross-origin-opener-policy)
* [Cross-Origin-Resource-Policy reference](/en/docs/web-security/security-headers/cross-origin-resource-policy)
* [Cross-Origin-Embedder-Policy reference](/en/docs/web-security/policies/cross-origin-embedder-policy)
* [The coep report type](/en/docs/web-security/reporting-api/reports/coep)

## Sources [#sources]

* [WHATWG Fetch, Cross-Origin-Resource-Policy header](https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header)
* [WICG, COEP credentialless specification](https://wicg.github.io/credentiallessness/)
* [Chromium, CORP enforcement source](https://github.com/chromium/chromium/blob/main/services/network/public/cpp/cross_origin_resource_policy.cc)
* [Chromium, blocked-by-response reasons](https://github.com/chromium/chromium/blob/main/services/network/public/mojom/blocked_by_response_reason.mojom)
* [MDN, Cross-Origin-Resource-Policy header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Resource-Policy)
* [MDN, Cross-Origin Resource Policy guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cross-Origin_Resource_Policy)
* [MDN, Cross-Origin-Embedder-Policy header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy)
* [MDN, SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)
* [web.dev, Making your website cross-origin isolated](https://web.dev/articles/coop-coep)
* [Chrome developers, SharedArrayBuffer updates](https://developer.chrome.com/blog/enabling-shared-array-buffer)
* [OWASP, HTTP headers cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html)
