Debugging Resources Blocked by COEP

This page is a focused procedure under the Cross-Origin Isolation: COOP, COEP & CORP reference. When a page sends Cross-Origin-Embedder-Policy: require-corp, every cross-origin subresource must opt in — and the ones that do not vanish from the page with a distinctive Network-panel error. This is how to identify the blocked resource, decide between adding Cross-Origin-Resource-Policy, switching to credentialless, or proxying, and roll back safely.

Configuration Syntax & Exact Values

The fix is one of two header changes. Either mark the blocked resource embeddable at its origin:

Cross-Origin-Resource-Policy: cross-origin
CORP value Effect under a COEP require-corp embedder
same-origin Resource loads only if embedder is the exact same origin; cross-origin embed is blocked.
same-site Loads if embedder shares the registrable domain (scheme-sensitive); cross-site embed blocked.
cross-origin Loads for any embedder. This is the value a public CDN asset needs.

Or relax the embedder policy on your document so third-party assets load without opting in:

Cross-Origin-Embedder-Policy: credentialless

require-corp demands CORP or CORS from every cross-origin subresource. credentialless loads cross-origin no-cors subresources without credentials (no cookies, no client certs) instead — public assets work unchanged, but any cookie-gated cross-origin resource breaks. Both values still grant crossOriginIsolated.

Decision tree for a COEP-blocked resource A decision tree: if you can add CORP to the resource, add it; otherwise switch the embedder to credentialless if the resource needs no cookies; otherwise proxy it through your own origin. Resource blocked by COEP require-corp Control the resource's origin? yes no Add CORP: cross-origin (or CORS) Needs cookies? no → credentialless Needs cookies: proxy via your origin
Prefer adding CORP at the source; if you cannot, switch to credentialless for cookie-free assets, and proxy anything that must carry credentials cross-origin.

How the Embedding Gate Decides

Under require-corp the browser runs its check on every cross-origin subresource response, not on the request. When your isolated document pulls in an image, font, script, stylesheet, worker, or <iframe> from another origin, the response is admitted only if one of two conditions holds: it carries a Cross-Origin-Resource-Policy header whose value permits this embedder, or the request was made in cors mode and the origin echoed back a matching Access-Control-Allow-Origin. A plain no-cors fetch with no CORP header produces an opaque response, and opaque responses are precisely what require-corp refuses to embed. That is the literal meaning of NotSameOriginAfterDefaultedToSameOriginByCoep: the resource was defaulted to same-origin because it never opted out of that default.

This is why adding CORP at the source and switching to credentialless are the only two genuine fixes. CORP satisfies the first condition at the resource’s origin; credentialless rewrites the rule so no-cors responses load anonymously instead of being refused. Nothing set on the document alone can attach a CORP header to a third party’s response — the header has to originate from the server that serves the asset.

How require-corp admits or blocks a cross-origin subresource An isolated document requests a cross-origin asset; the browser embedding gate paints it when CORP or CORS is present and otherwise blocks it with ERR_BLOCKED_BY_RESPONSE. Isolated document COEP: require-corp Browser embed gate CORP or CORS present? Allowed resource painted Blocked ERR_BLOCKED_BY_RESPONSE yes no requests cross-origin asset
The gate inspects the response: a permitting CORP header or a passed CORS handshake admits the asset; a bare no-cors response is refused as opaque.

Server-Side Configuration

Add Cross-Origin-Resource-Policy to the subresource responses (the assets that get blocked), not the document.

Nginx

location ~* \.(woff2|png|jpg|svg|js|css)$ {
    add_header Cross-Origin-Resource-Policy "cross-origin" always;
}

always keeps the header on error responses; without it Nginx drops add_header on non-2xx, and a 304/404 asset response would still block.

Apache (mod_headers)

<FilesMatch "\.(woff2|png|jpg|svg|js|css)$">
  Header always set Cross-Origin-Resource-Policy "cross-origin"
</FilesMatch>

always applies the header across all status codes; the default onsuccess table skips errors. Requires mod_headers.

Cloudflare (Transform Rules)

Rules → Transform Rules → Modify Response Header → Set static:
  When URI Path matches the asset paths
    Cross-Origin-Resource-Policy = cross-origin

Use Set, not Add, so the edge does not stack a second CORP header on an origin-set one. Scope to asset paths so you do not attach cross-origin to private API responses.

Node/Helmet

const helmet = require('helmet');

// On the asset-serving app/middleware:
app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' }));

// If you must relax the embedder instead, on the document app:
app.use(helmet.crossOriginEmbedderPolicy({ policy: 'credentialless' }));

Diagnostic & Verification Steps

The signature of a COEP block is a specific Network-panel reason, not a generic CORS error.

Open DevTools → Network, reload, and find the failed request. Its status column shows (blocked) and hovering the request reveals:

net::ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep

The Console typically logs alongside it:

A resource is blocked by OpaqueResponseBlocking / Cross-Origin-Embedder-Policy.
The resource at  was blocked because it does not have a
Cross-Origin-Resource-Policy header that allows cross-origin embedding.

Confirm the resource is missing CORP from the command line:

curl -sI https://cdn.third-party.example/font.woff2 | grep -i cross-origin-resource-policy
# No output → the asset sends no CORP and will be blocked under require-corp.

After adding CORP, re-check and confirm isolation in the Console:

curl -sI https://cdn.third-party.example/font.woff2 | grep -i cross-origin-resource-policy
# Expected:
#   cross-origin-resource-policy: cross-origin
self.crossOriginIsolated   // Expected: true once no subresource is blocked

The Application → Frames → top → Security & isolation panel lists each blocked resource and the reason isolation was denied — the fastest way to enumerate every offender at once.

To find every offender before you enforce, deploy the report-only twin first. Cross-Origin-Embedder-Policy-Report-Only: require-corp runs the identical gate but only reports violations instead of blocking, so the page keeps working while the browser posts a report to your Reporting-Endpoints collector for each resource that would be refused:

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

Each report body carries "type": "coep" with the blocked URL and disposition "reporting". Leave it running across real traffic long enough to catch conditionally loaded assets (lazy images, on-demand widgets), fix every reported origin, and only then swap the header to the enforcing Cross-Origin-Embedder-Policy.

Browser wording differs. Chromium prints the NotSameOriginAfterDefaultedToSameOriginByCoep reason on the network entry; Firefox reports NS_ERROR_DOM_CORP_FAILED in the Console and labels the request as blocked by an opaque-response rule. Both mean the same thing — the response lacked a permitting CORP header or CORS grant — so the fix is identical regardless of engine.

Edge Cases, Security Implications & Safe Rollback

The value you pick trades reach against isolation. This matrix shows how each embedder mode treats the three cases you will actually hit — a cookie-free public asset, a cross-origin resource gated by a cookie, and the crossOriginIsolated capability itself:

How each COEP mode treats public assets, cookie-gated assets, and isolation A matrix comparing require-corp, credentialless, and no COEP across a public CDN asset, a cookie-gated cross-origin asset, and the crossOriginIsolated capability. require-corp credentialless no COEP Public CDN asset (no cookies) Blocked unless CORP present Loads anonymously Loads normally Cookie-gated cross-origin Blocked (no CORP) Wrong response (cookie stripped) Loads with cookie crossOrigin- Isolated true SharedArrayBuffer on true SharedArrayBuffer on false SharedArrayBuffer off
Both require-corp and credentialless grant isolation; they differ only in how cross-origin assets load, and credentialless is what silently strips the cookie from an authenticated request.

Rollback is non-destructive — COEP gates capabilities, it does not delete state. To restore loading immediately, remove COEP while keeping COOP:

Header unset Cross-Origin-Embedder-Policy
Header always set Cross-Origin-Opener-Policy "same-origin"

This disables SharedArrayBuffer but preserves the opener-severance protection described in the cross-origin isolation reference. Prefer switching to credentialless before a full removal, since that keeps isolation alive.

Frequently Asked Questions

What does NotSameOriginAfterDefaultedToSameOriginByCoep mean? COEP require-corp defaults every cross-origin subresource to “same-origin only” unless it explicitly opts in. The error means the resource is cross-origin, sent no permitting CORP header and passed no CORS handshake, so the browser blocked it. Add Cross-Origin-Resource-Policy: cross-origin to the resource or switch the embedder to credentialless.

Will switching to credentialless fix every block? It fixes blocks for cross-origin no-cors assets that do not depend on cookies, because they load anonymously. It will not fix resources that require a cookie cross-origin — those return the wrong response when fetched without credentials. Proxy those through your origin instead.

Can I add CORP to a third-party CDN’s response? Only if the CDN exposes a setting for it or you proxy the asset through infrastructure you control. You cannot add a header to a response served from someone else’s origin. For uncontrolled assets, use credentialless or self-host.

Does removing COEP also disable my COOP protection? No. COOP and COEP are independent headers. Removing Cross-Origin-Embedder-Policy only disables crossOriginIsolated (and thus SharedArrayBuffer); keep Cross-Origin-Opener-Policy: same-origin to retain opener-based cross-site-leak defense.

Conclusion

Work incrementally: reproduce the block on staging, enumerate every offending resource in the Network and Security & isolation panels, then resolve each by adding CORP at the source where you can. For assets you do not control, switch the document to credentialless and re-verify self.crossOriginIsolated === true. Only if neither works should you remove COEP — and even then keep COOP same-origin in production.