Cross-Origin Isolation Rollback: A Header Change Checklist
Decide which URLs must stay isolated, which resource class each policy owns, and how a staged rollout returns to the previous header set when a journey breaks.
BotBrowser Team
Want the structured docs for Platform?
This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.
A cross-origin isolation change is a response-policy change to a live product, and the hard part is not enabling it. This checklist starts where the not cross-origin isolated fix ends: after the first page proves isolated, decide which URLs must stay that way, who owns each embedded policy, and what a staged rollout does when a journey breaks.
BotBrowser supports loading a declared browser build against an owned page and records whether window.crossOriginIsolated is true for that response, which teams can use to test isolation scope; it cannot send COOP or COEP for a host, cannot change a vendor's CORS or CORP answer, and does not guarantee that an application journey survives the new policy.
TL;DR
- List the URLs that need isolation, and keep the rest unisolated on purpose.
- Map every embedded resource class to a policy owner before you change a header.
- Roll out by percentage or host, not by flipping every route at once.
- Agree the rollback trigger and the previous header set before the first production request.
Contents
- Scope the URLs that need isolation
- Map resource classes to owners
- Stage the rollout
- Define the rollback
- Ship the fallback and the evidence
- Conclusion
Scope the URLs that need isolation
Isolation belongs to documents, not to a site. A product usually needs it on one surface, such as a page that starts a worker with shared memory, and does not need it on a product overview page, a login flow, or an OAuth callback. Sending Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp everywhere widens the blast radius without buying anything for the pages that never touch an isolated-only feature. Before widening the scope, reread the CORS boundary guide, which already lists the resources treated as cross-origin dependencies.
Write the scope as a list of URL patterns such as /app/editor/* and /auth/*, not as a domain. Include the document paths that must be isolated, the paths that must stay unisolated, and the callback paths whose popup behavior you cannot change. A redirect chain that ends on an isolated document is still isolated; a redirect that ends on an unisolated one is not, and that difference is easy to miss when the scope is written as "the app".
Record the scope in the same place as the rollout record (docs/cross-origin-isolation-scope.md), with the date and the owner. When a new route is added later, the scope tells the next engineer whether it inherits isolation from a parent pattern or needs its own policy. That is the difference between an intentional policy and a policy that spread by accident.
Check the scope against the sitemap and the route manifest rather than against memory, and run that comparison as a scheduled job if the route set changes often. A route generated by a template may serve a document that your header rule does not cover, especially when the template has its own layout and a different canonical host. The first place isolation surprises a team is a page nobody listed.
Keep the scope reviewable
A scope review is also the moment to confirm that the unisolated pages really do not need an isolated-only feature. Search the application for SharedArrayBuffer, crossOriginIsolated, and Atomics before declaring a page out of scope. A page that checks the boolean and falls back is safe to leave unisolated; a page that constructs a shared buffer without checking is a page that will throw the moment its feature is exercised.
When a page needs isolation only under a feature flag, record the flag with the pattern. Enabling the flag on a host that does not send the policy produces a runtime failure that looks like a code defect, and the flag record is what lets a reviewer see that the policy was never in place rather than that the feature regressed.
Write the scope as a machine-checkable list
A scope that lives in a slide deck cannot be checked, so keep the expected state as a scopes.tsv row or a JSON field the build can read. Keep it as a list of path patterns with an expected state per pattern, so a later change can be compared against it rather than argued about. A useful entry names the pattern, the expected isolation state, the reason, and the owner who asked for it.
The expected state is not always "isolated". Most products need a document to stay unisolated because an embedded chat widget, a checkout frame, or a legacy report page depends on resources that nobody can change in the same window. Recording that intent prevents a later cleanup from "fixing" a page that was deliberately excluded and breaking a journey in the process.
Group the patterns by their response path rather than by their URL prefix alone. Two paths under the same prefix can be served by different origins after a rewrite, and a CDN rule that applies to one may not apply to the other. When the list grows past a handful of entries, add a check that every versioned route is either matched by a pattern or explicitly excluded, so a new route cannot silently inherit the wrong state.
Keep the list next to the deployment configuration and update it in the same change that adds or removes a route. A scope that is updated afterwards has already been wrong in production, and the failure it causes looks like a random inconsistency rather than a missing entry.
Measure before and after, on the same build
The measurement that makes a rollout reviewable is a comparison, not a single reading; record it as scope-before.json and scope-after.json. Take a baseline of the staging host before the policy change, then repeat the same checks after it, on the same browser build and the same fixtures. A single reading after the change cannot tell you whether an unexpected value came from the policy or was already there.
Record three fields for each page: the delivered header values, the runtime isolation boolean, and the first resource that failed if any. The header values tell you what the server sent. The boolean tells you what the browser accepted. The failing resource tells you which class needs attention when the first two disagree.
Sample at least one URL per pattern rather than one URL overall. A redirect chain, a localized host, and a trailing-slash variant can each produce a different final response, and the pattern is only as strong as its least tested member.
Repeat the measurement after a CDN change, a service worker update, or a hosting migration even when the application code has not moved. Those three events change the delivered response without touching the repository, which is exactly why a policy record from before them can be misleading.
Map resource classes to owners
Isolation changes what may load, so every embedded resource class needs a decision and an owner. Group them by how the resource is served rather than by which team asked for it, because the policy answer depends on the response, not on the request.
Frames are the most sensitive class. A cross-origin iframe needs a COEP-compatible response or a CORP header, and a vendor frame that does not send one will fail once the embedding document enforces COEP. Popup-based sign-in belongs to the same class: COOP separates the opener relationship, and a provider that expects window.opener will notice. Record the provider, the frame origin, and what the provider's documentation says about isolation compatibility.
Scripts, stylesheets, fonts, and images form the next class. They are usually served by a CDN, and the answer is a CORS header or Cross-Origin-Resource-Policy: cross-origin. A CDN rule that works for a subdomain may not apply to the canonical host, so the owner to name is whoever can change the response for the exact host you call.
Media is its own class and is often the slowest to change. Video and audio endpoints may be signed, redirected, or region-specific, and each of those can change the response headers you see. Test with a signed URL that resembles a production one rather than with a static sample.
Analytics and tag managers are the class most likely to break silently. Many of them load additional scripts at runtime, so the URL you reviewed is not the URL that runs. Record that they may fail and decide whether the failure is acceptable, because an analytics beacon that stops reporting rarely blocks a user journey but does change the data you rely on.
For each class, name the policy, the owner, and the verification. The verification is one request per class, recorded with the final URL and the header value that made it compatible. A class without a verification is a class you are assuming rather than knowing, and an assumption is what turns a staged rollout into a surprise.
Stage the rollout
Stage by traffic, not by ambition. A staged rollout lets the first production evidence come from a small population while the previous policy remains one configuration change away. Choose the staging dimension from what your infrastructure can reverse fastest: a percentage at the edge, a header rule on one host, or a canary route that serves the same application code.
Keep the staging unit aligned with the user journey. A percentage of traffic can send one user to the isolated version on the first page and the unisolated version on the callback, which produces a failure that looks random and is not. Sticky percentage or host grouping keeps a single journey on one policy for its duration.
Decide the order of surfaces. Start with the surface that has the fewest embedded dependencies and the clearest fallback, and finish with the one that carries sign-in or payment. The point of the order is to learn about the resource classes in a cheap place before the expensive one.
Watch the right signals during the window. A crossOriginIsolated: false on a page that should be true is a configuration result. A blocked resource is a policy result against one class. A user who cannot complete sign-in is an application result, and it outranks both because no amount of isolated state is worth a broken journey.
Keep the window short enough to reverse and long enough to see a full journey, and write the window in the rollout record before it starts so nobody extends it by accident. A window that ends before a user can log in and return through a callback has not tested the paths most likely to break.
Define the rollback
A rollback needs a trigger, an action, and a verification, agreed before the rollout starts. The trigger is a sentence a support engineer can apply without asking the release owner: an essential journey fails for more than a stated share of staged users, or a class-owner cannot make a resource compatible during the window.
The action is the previous header set plus the previous application path. Keep both available and tested. A header rollback that restores COOP and COEP while leaving the isolated feature enabled produces a page that reports false and then fails at runtime; the application path has to change with the policy.
The verification is the same check that proved the isolated state, run in reverse: the staged host returns to the previous headers, crossOriginIsolated is false where expected, the fallback path completes the journey, and the resource classes that needed the new policy stop being required. Record that result with the date and the person who made the call.
Write down what the rollback does not cover. A cached document, a service worker, or an edge rule with a long TTL can keep serving the new policy after the origin changes. Note the purge step and the hostnames it applies to, because an incomplete rollback is a policy that looks reverted and is not.
Review the rollback record after the rollout finishes, whether or not it was used. A rollback that was never needed still tells you whether the action worked, and the next header change can reuse the procedure instead of rediscovering it.
Ship the fallback and the evidence
Choose the fallback the product can actually ship
Every isolated-only feature needs a fallback that a user can complete the task with, and the fallback path is worth a test case (fallback: passed), and the fallback should be the simpler path rather than a second system. A shared-memory worker can fall back to a main-thread loop with a progress indicator. A feature that needs isolation for performance can degrade to the slower path with a visible notice. A feature that needs isolation for correctness cannot fall back, and that is a product decision to escalate before the rollout rather than during it.
Test the fallback with the same fixtures that test the isolated path. A fallback that has never run under production-like data is an untested path, and the rollout window is the wrong time to discover that it throws on a large input.
Decide who owns the fallback. If the application team ships it and the platform team enables the header, the fallback belongs to the application team and should be part of the release checklist rather than an afterthought. The rollout record should name the fallback and where it can be verified, so a support engineer can point a user at it without waiting for a diagnosis.
Keep the evidence short enough to act on
A rollout record that only a specialist can read will not be read during an incident. Keep it to the scope list, the class owners, the staging dimension, the baseline and post-change readings, the rollback trigger, and the fallback. Each item should fit in a line and point to the longer document where needed.
Date the record and name the person who accepted the policy, together with the local time zone the window was planned in. A record without an owner becomes a record nobody defends, and the next change starts from a weaker position than the first one did.
Retire the record when the policy has been stable long enough that the previous header set no longer exists in any deployable configuration. Until then, the record is the only description of how the current state was reached.
Conclusion
Isolation is a response policy with a scope, a set of owners, and a staging plan. Decide the URLs, map the resource classes, stage by traffic, and agree the rollback trigger before the first production request. Use this checklist alongside the cross-origin isolation fix, the CORS boundary guide, and the secure-context overview.
Sources
This article is part of the guide: Web Platform Surfaces: A Consistency Review Map
Related Articles
Fix "Not Cross-Origin Isolated": COOP, COEP, and SharedArrayBuffer
PlatformPermissions Policy for Embedded Browser Features
DeploymentBrowser Deployment and Release: Operations Map
PlatformOrigin-Agent-Cluster: Predictable JavaScript Memory Isolation
PlatformCORS Browser Boundaries: Headers, Preflights, Credentials, and Cache
PlatformBrowser Release Notes for Web Platform Teams
Take BotBrowser from research to production
The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.