Lập trình · 03/10/2026

Practical Cross-Origin Isolation: COOP, COEP, and SharedArrayBuffer

A web application needs SharedArrayBuffer, multi-threaded WebAssembly, or high-resolution timers. You add COOP and COEP, verify that crossOriginIsolated === true—and suddenly CDN images, fonts, analytics, or the sign-in popup stop working. Cross-origin isolation is not difficult because of two configuration lines; it is difficult because every resource and window relationship becomes part of the trust contract.

Cross-origin isolation thực chiến: COOP, COEP và SharedArrayBuffer

A web application needs SharedArrayBuffer, multi-threaded WebAssembly, or high-resolution timers. You add COOP and COEP, verify that crossOriginIsolated === true—and suddenly CDN images, fonts, analytics, or the sign-in popup stop working. Cross-origin isolation is not difficult because of two configuration lines; it is difficult because every resource and window relationship becomes part of the trust contract.

This guide deploys Cross-Origin-Opener-Policy (COOP), Cross-Origin-Embedder-Policy (COEP), and Cross-Origin-Resource-Policy (CORP) incrementally: inventory dependencies, choose require-corp or credentialless, repair CORS/CORP, test popups, workers, iframes, and service workers, then roll out with observability. Behavior is cross-checked against the HTML Standard, Fetch Standard, and MDN COOP documentation.

1. What problem does cross-origin isolation solve?

Browsers must assume that documents from other origins may be untrusted. Powerful APIs such as shared memory increase timing precision and can amplify side-channel attacks. A page therefore receives full access to these capabilities only inside a cross-origin isolated environment.

In practice, that state commonly requires:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Or the COEP value may be:

Cross-Origin-Embedder-Policy: credentialless

COOP separates the top-level document from cross-origin windows at the browsing-context-group level. COEP requires embedded cross-origin resources to grant explicit permission through CORS or CORP; with credentialless, cross-origin no-cors requests omit credentials and do not require CORP in the same way.

CORS, CORP, COEP, and COOP are not synonyms. CORS controls whether script can read a cross-origin response; CORP lets a resource server restrict where its response is used; COEP sets an embedding policy for a document; COOP governs relationships between top-level windows.

2. Verify success at runtime

Do not infer success solely because headers appear in DevTools. Test the capability in the actual document and worker:

console.log({
  secureContext: window.isSecureContext,
  isolated: window.crossOriginIsolated,
  sharedArrayBuffer: typeof SharedArrayBuffer,
});

Inside a worker:

// worker.js
self.postMessage({
  isolated: self.crossOriginIsolated,
  sharedArrayBuffer: typeof SharedArrayBuffer,
});

The page must run in a secure context. A Permissions-Policy: cross-origin-isolated directive can also deny the capability even when COOP and COEP appear correct. Inspect the final response after redirects, not only the first response.

3. Inventory dependencies before enforcement

COEP exposes implicit dependencies: images without CORS, marketing scripts loaded as no-cors, CDN fonts without permission headers, payment iframes, and OAuth popups that depend on window.opener. Build an inventory by request type and owner:

TypeQuestionCommon response
Script/moduleSame origin or CDN? Valid CORS?Self-host, enable CORS, or use a resource that grants CORP.
Image/video/audioLoaded as no-cors? Cookies or hotlink protection?Add suitable CORP or request through CORS.
FontDoes the response include Access-Control-Allow-Origin?Configure CORS at the CDN or origin.
Worker/WASMAre URL, MIME, redirects, and policy correct?Prefer same-origin and pin the exact artifact.
iframeDoes the provider support embedding under COEP?Verify the contract; a separate route may be required.
OAuth/payment popupDoes it read window.opener or invoke a callback?Test the flow; use a safe redirect/message protocol.

Add domain, initiator, mode, and credentials columns in the Network panel and filter blocked requests. Run a crawler or integration suite across important routes because dependencies activated only after user interaction may not appear on the landing page.

4. Choose require-corp or credentialless

COEP: require-corp

This is the easiest mode to reason about: a cross-origin resource must be permitted by CORS or an appropriate CORP header. A public CDN can return:

Cross-Origin-Resource-Policy: cross-origin

Or the client can make a CORS request:

<script src="https://cdn.example.net/app.js"
        crossorigin="anonymous"></script>

with the CDN returning:

Access-Control-Allow-Origin: https://app.example.com
Vary: Origin

For a truly public resource without credentials, Access-Control-Allow-Origin: * may be appropriate. A wildcard cannot be used with credentialed CORS.

COEP: credentialless

Under credentialless, the browser strips credentials from cross-origin no-cors requests; in exchange, those resources do not need to send CORP to be embedded. This helps when you do not control the resource server, but changes semantics: cookies, client certificates, and HTTP authentication are omitted from those requests.

Do not select credentialless merely to silence errors. Confirm that a resource is public, is not personalized by cookies, does not depend on anti-hotlinking or sessions, and that target browsers support the needed behavior. CORS requests that carry credentials still require correct CORS permission.

5. Set CORP at the resource server

Cross-Origin-Resource-Policy commonly uses three values:

  • same-origin: restrict to the same scheme, host, and port; suitable for sensitive responses.
  • same-site: permit the same site, a broader boundary than origin; take care when different teams control subdomains.
  • cross-origin: allow cross-origin embedding; use only for intentionally public assets.

An Nginx rule for a public asset directory might be:

location /public-assets/ {
    add_header Cross-Origin-Resource-Policy cross-origin always;
    try_files $uri =404;
}

Do not attach cross-origin to every API or user-data response. The header must express the contract of each resource group.

6. Scope reverse-proxy configuration by route

Not every part of a website needs shared memory. Isolating a dedicated application or route reduces the blast radius for OAuth, CMS pages, chat widgets, and marketing pages.

location /compute/ {
    add_header Cross-Origin-Opener-Policy "same-origin" always;
    add_header Cross-Origin-Embedder-Policy "require-corp" always;
    add_header Permissions-Policy "cross-origin-isolated=(self)" always;

    proxy_pass http://compute_app;
}

With Caddy:

handle_path /compute/* {
    header {
        Cross-Origin-Opener-Policy "same-origin"
        Cross-Origin-Embedder-Policy "require-corp"
        Permissions-Policy "cross-origin-isolated=(self)"
    }
    reverse_proxy compute_app:8080
}

Inspect HTML, redirects, error pages, and CDN-cached responses. If the proxy adds headers only to status 200, an error document may behave differently. Ensure cache keys and invalidation cannot mix policy-enabled and non-policy responses.

7. Authentication and payment popups are fragile

COOP: same-origin can move a cross-origin popup into a different browsing context group, severing its window.opener relationship. Legacy flows that call a function on the opener, poll popup.closed, or access popup properties may stop working.

Do not solve this by removing COOP globally. Design options include:

  • Use full-page redirect with state and PKCE for OAuth, then return to a same-origin callback.
  • Use the provider's documented protocol instead of custom cross-window DOM access.
  • Place the isolated workload on a separate origin while authentication and payment retain suitable policies.
  • If considering same-origin-allow-popups, explicitly verify whether the page still becomes cross-origin isolated; do not infer capability from the directive's name.

Run end-to-end tests across target browsers: blocked popup, user cancellation, timeout, multi-step redirects, SameSite cookies, and an already-open tab.

8. Test service workers, iframes, and caches separately

A service worker can serve a response cached before policy was enabled. Clear caches in tests, bump cache versions, and inspect the actual navigation response. Client-side JavaScript cannot add these policy headers in time; they must be on the HTTP response.

For iframes, the document chain and subresources must be compatible. A third-party iframe that does not support COEP may fail inside an isolated document. When that iframe is business-critical and the vendor cannot support the policy, a separate route or origin is clearer than fragile exceptions.

Serve worker scripts from the same origin where possible. Validate MIME type, redirects, CORS, and the final response policy. For multi-threaded WebAssembly, test a non-shared-memory fallback so the application can present a reduced mode instead of a blank-screen failure.

9. Roll out with report-only and active observation

Browsers provide report-only variants of COOP and COEP, but reporting support and schemas can vary by engine. Treat reports as an additional signal, not as a replacement for integration tests and Network-panel inspection.

Cross-Origin-Opener-Policy-Report-Only: same-origin
Cross-Origin-Embedder-Policy-Report-Only: require-corp
Reporting-Endpoints: isolation="https://reports.example.com/browser"

The report collector must resist abuse: limit body size, rate-limit, distrust client fields, remove sensitive data, and set retention. A practical rollout is:

  1. Enable report-only in staging and a production canary.
  2. Exercise navigation, sign-in, payment, upload, media, workers, and offline flows.
  3. Repair dependencies through CORS/CORP or self-hosting; do not whitelist blindly.
  4. Enforce for a small traffic cohort or narrow route.
  5. Monitor JavaScript errors, resource failures, authentication conversion, and crossOriginIsolated.
  6. Expand after sufficient observation with a tested rollback.

10. Minimum production test suite

test('compute page is cross-origin isolated', async ({ page }) => {
  await page.goto('https://app.example.com/compute/');

  const state = await page.evaluate(() => ({
    isolated: crossOriginIsolated,
    sab: typeof SharedArrayBuffer,
  }));

  expect(state.isolated).toBe(true);
  expect(state.sab).toBe('function');
});

Beyond capability assertions, verify that:

  • No critical request is blocked.
  • Fonts, images, video, required analytics, and error reporting still operate.
  • The worker starts and completes a shared-memory task.
  • Login/logout, OAuth popup or redirect, payment callback, and deep links work.
  • Error and maintenance pages carry the intended policy.
  • Unsupported browser versions receive a clear fallback.

11. Diagnose by symptom

SymptomCommon causeNext check
crossOriginIsolated is falseMissing COOP/COEP, insecure context, Permissions Policy, or ancestorInspect the final top-level response and iframe chain.
Image/script has ERR_BLOCKED_BY_RESPONSEA no-cors resource lacks CORP under require-corpFix the asset server, use CORS, or self-host.
Font failsCDN lacks CORS or caches the wrong Origin responseInspect ACAO, Vary: Origin, and cache keys.
Login popup completes but app receives nothingThe opener relationship was severedMove to a redirect/callback protocol or separate origin.
Staging passes, production failsCDN, proxy, or service worker changes or caches headersCompare actual edge responses and cache versions.
One route works, another is falseHeaders apply only to certain locations or statusesInspect redirects, error pages, and always behavior.

12. Deployment checklist

  • Identify which API truly needs cross-origin isolation and design a fallback.
  • Inventory every subresource, iframe, worker, popup, and redirect.
  • Choose require-corp or credentialless from credential requirements.
  • Fix CORS/CORP at the resource-owning server; do not relax the entire site.
  • Apply COOP/COEP to the smallest practical route or origin.
  • Test crossOriginIsolated at runtime, not only response headers.
  • Exercise OAuth/payment, service workers, iframes, and browser fallbacks.
  • Canary, observe resource/auth failures, and keep a rollback ready.

Conclusion

COOP and COEP are not two headers to set and forget. They establish a new contract among the document, popups, workers, and every resource server. A reliable deployment starts with dependency inventory, chooses policy from credential semantics, grants permission at the correct origin, and measures capability at runtime. With narrow scope, a canary rollout, and a clear fallback, an application can use shared memory and multi-threaded WebAssembly without sacrificing authentication, payment, or third-party-resource reliability.

Discussion

Comments 0

Sign in to comment

You need an account to join the discussion and reply to other readers.

Sign inRegister

No comments yet. Be the first to share your thoughts.