Skip to content

Media: Broaden the Document-Isolation-Policy header to all admin pages (backport GB #76662)#11298

Draft
adamsilverstein wants to merge 1 commit into
WordPress:trunkfrom
adamsilverstein:fix/broaden-dip-header-scope
Draft

Media: Broaden the Document-Isolation-Policy header to all admin pages (backport GB #76662)#11298
adamsilverstein wants to merge 1 commit into
WordPress:trunkfrom
adamsilverstein:fix/broaden-dip-header-scope

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Mar 19, 2026

Copy link
Copy Markdown
Member

Summary

Send the Document-Isolation-Policy (DIP) header on all admin pages (via admin_init) instead of only on the four block-editor screens (load-post.php, load-post-new.php, load-site-editor.php, load-widgets.php).

  • Replace the block-editor screen gate in wp_set_up_cross_origin_isolation() with an upload_files capability check.
  • Retain the existing escape hatch for third-party page builders that take over an admin editor screen via a custom action query parameter (see "Page-builder compatibility" below).

Why

When the editor sends the DIP header but sibling admin navigations (site editor sub-routes, template and pattern operations) load without it, Chromium 137+ places them in different agent clusters. The mismatch breaks cross-window communication (window.opener, popup references) and SharedArrayBuffer access required for WebAssembly-based client-side media processing. Broadening the header to every admin page keeps all admin navigations in the same agent cluster.

The block-editor screen gate is no longer needed: cross-origin isolation is only relevant for users who can upload media, so an upload_files capability check is sufficient and cheaper than resolving the current screen on every admin request.

Scope

Page-builder compatibility

The action !== 'edit' escape hatch is retained. It skips DIP when a third-party builder takes over an admin editor screen via a custom action value, because those builders embed a same-origin front-end preview iframe and access it synchronously — which DIP breaks when only the admin frame is isolated.

At risk — admin chrome + synchronous same-origin preview iframe:

  • Elementor (action=elementor) no longer strictly needs the hatch: as of v4.x it sends the DIP header on both its editor frame and its preview iframe itself (elementor/elementor#35976), so it self-isolates. The hatch is harmless to it.
  • Brizy (action=in-front-editor) still relies on the hatch and would regress without it — no equivalent fix shipped.
  • WPBakery front-end editor (vc_action=vc_inline) and Visual Composer standalone (vcv-action=frontend) match the same pattern but trigger on a different query parameter, so the action-based hatch never covered them.

Unaffected — editor chrome served on the front end (admin header never reaches it): Beaver Builder (?fl_builder), Divi Visual Builder (?et_fb=1), Oxygen (?ct_builder=true), Bricks (?bricks=run), Cornerstone (?cornerstone=1), Avada Live (?fb-edit=1), Thrive Architect (?tve=true), Breakdance (?breakdance=builder), Zion Builder.

Unaffected — backend metabox on the standard action=edit screen (already coexists with DIP): WPBakery backend editor, Divi Classic Builder, SiteOrigin Page Builder, Avada backend builder.

Alternate approaches considered

  1. Send DIP on front-end preview pages too (via template_redirect). Earlier iterations of this backport added a wp_set_up_cross_origin_isolation_for_preview() helper so editor → preview popups stayed in the same agent cluster. Rejected: DIP is kept admin-only; front-end isolation is out of scope and the upstream Gutenberg change dropped this path, leaving no core counterpart. (Removed from this PR.)
  2. Replace the action !== 'edit' heuristic with editor-replacement detection (e.g. use_block_editor_for_post() === false / the replace_editor filter). This would protect all editor-replacing builders regardless of query parameter — including WPBakery and Visual Composer, which the action-based check misses. Deferred: larger change that needs the screen/post context restored, and the current heuristic plus builders self-isolating (as Elementor now does) covers the practical cases. Worth a follow-up.
  3. Remove the escape hatch entirely (platform-forward) — send DIP on all admin editors and require builders to self-isolate as Elementor did. Rejected: would regress Brizy and any other builder embedding a same-origin preview iframe without a shipped fix.
  4. Keep hooking only the specific editor screens (load-post.php etc., the status quo). Rejected: this is the root cause — sibling admin navigations load without DIP and fall into a different agent cluster, which is the bug this PR fixes.

Changes

src/wp-includes/default-filters.php

  • Hook wp_set_up_cross_origin_isolation on admin_init instead of the four load-* screen actions.

src/wp-includes/media.php

  • wp_set_up_cross_origin_isolation() — drop the get_current_screen() block-editor gate; gate on current_user_can( 'upload_files' ) instead. The third-party page-builder escape hatch is unchanged.

tests/phpunit/tests/media/wpCrossOriginIsolation.php

  • Replace test_returns_early_when_no_screen() with test_returns_early_when_user_cannot_upload().

Test plan

  • vendor/bin/phpunit tests/phpunit/tests/media/wpCrossOriginIsolation.php — all tests pass.
  • In Chromium 137+ as a user with upload_files, confirm the Document-Isolation-Policy: isolate-and-credentialless response header is present on /wp-admin/index.php, /wp-admin/site-editor.php, /wp-admin/edit.php, and /wp-admin/post.php?post=…&action=edit.
  • Sign in as a Subscriber (no upload_files) and confirm the header is not sent on any admin page.
  • Visit a third-party editor URL with ?action=elementor (or similar custom action) and confirm the header is not sent.

Related

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

Send the Document-Isolation-Policy (DIP) header on every admin page via
`admin_init` rather than only on the four block-editor screens
(`load-post.php`, `load-post-new.php`, `load-site-editor.php`, and
`load-widgets.php`).

Hooking the narrow set of screens placed the editor in its own browser
agent cluster while sibling admin navigations (site editor sub-routes,
template and pattern operations) loaded without the header. The agent
cluster mismatch broke cross-window communication and SharedArrayBuffer
access required for WebAssembly-based client-side media processing in
Chromium 137+.

The block-editor screen gate is replaced by an `upload_files` capability
check, since cross-origin isolation is only needed for users who can
upload media. The existing escape hatch for third-party page builders
that override the block editor via a custom `action` query parameter is
retained.

The front-end preview is intentionally out of scope; DIP remains an
admin-only concern.

See related Gutenberg pull request: WordPress/gutenberg#76662.
@adamsilverstein adamsilverstein force-pushed the fix/broaden-dip-header-scope branch from 543c846 to 7962e83 Compare June 19, 2026 10:40
@adamsilverstein adamsilverstein changed the title Media: Broaden DIP header to all admin and preview pages Media: Broaden the Document-Isolation-Policy header to all admin pages (backport GB #76662) Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant