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.
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.
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
- CDN images and fonts. Public CDNs increasingly send
Cross-Origin-Resource-Policy: cross-originby default, but many older or self-hosted asset hosts do not. If you control the asset host, add CORP there. If you do not, switch your document tocredentialless(these assets need no cookies) or self-host the asset. - Third-party scripts and widgets. Analytics, ad, and chat scripts loaded cross-origin without CORP are blocked. They rarely accept CORP requests on your behalf, so the practical options are
credentialless(if the script needs no first-party cookies on its own origin) or removing the script from the isolated context. Cookie-dependent widgets break undercredentialless. - Authenticated cross-origin resources. A personalized image or API response gated by a cross-origin cookie cannot use
credentialless— the request goes out without the cookie and returns the wrong (or a 401) response. Proxy it through your own origin so the fetch becomes same-origin, or move it off the isolated route. - same-site is not cross-origin. Setting CORP
same-siteon an asset embedded by a different registrable domain still blocks it. Usecross-originfor genuinely cross-site embedding, and remembersame-siteis scheme-sensitive. - Nested iframes are not covered by CORP. A cross-origin
<iframe>in an isolated document is not rescued by adding CORP to the frame — the embedded document must itself sendCross-Origin-Embedder-Policy: require-corp(orcredentialless) to be allowed inside arequire-corpparent.credentiallesson the parent relaxes subresources but still refuses a cross-origin child document that carries no COEP of its own. If you do not control the framed origin, the frame cannot join the isolated context; move it out of the isolated route or drop isolation for that page.
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:
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.