Opened 2 months ago
Last modified 3 days ago
#65493 assigned defect (bug)
Editor: Broaden the Document-Isolation-Policy header to all admin pages
| Reported by: | adamsilverstein | Owned by: | adamsilverstein |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | Media | Version: | |
| Severity: | normal | Keywords: | has-patch has-test-info has-unit-tests 2nd-opinion dev-feedback |
| Cc: | Focuses: |
Description (last modified by )
The block editor sends the Document-Isolation-Policy (DIP) header to enable
SharedArrayBuffer for WebAssembly-based client-side media processing in
Chromium 137+. Today the header is hooked onto only four screens:
load-post.php, load-post-new.php, load-site-editor.php, and
load-widgets.php.
Because the header is scoped that narrowly, navigations from an editor screen to a sibling admin screen (site editor sub-routes, template operations, pattern editing) load without the header. Chromium 137+ then places those documents in a different browser agent cluster from the editor. The agent-cluster mismatch breaks cross-window communication (window.opener, popup references) and SharedArrayBuffer access that client-side media processing relies on.
This is the same problem that currently forces several editor E2E suites to disable client-side media processing wholesale.
Proposed change
Send the DIP header on all admin pages by hooking wp_set_up_cross_origin_isolation() onto admin_init instead of the four load-* screen actions. Keeping every admin navigation in the same agent cluster preserves cross-window communication.
The block-editor screen gate is no longer needed. Cross-origin isolation is only relevant for users who can upload media, so the screen check is replaced by a single capability check:
<?php // Cross-origin isolation is not needed if users can't upload files anyway. if ( ! current_user_can( 'upload_files' ) ) { return; }
This is also cheaper than resolving the current screen on every admin request.
The existing escape hatch for third-party page builders that override the block editor via a custom action query parameter is retained:
<?php if ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) { return; }
Scope
- Admin-only. The front-end preview is intentionally out of scope; DIP remains an admin-only concern.
- The classic-theme site-preview skip is a separate change (see #65399).
Affected functions
wp_set_up_cross_origin_isolation()insrc/wp-includes/media.php- Hook registration in
src/wp-includes/default-filters.php
How to test
- Run Chromium 137+ against a build with the patch applied.
- As a user with the
upload_filescapability, confirm theDocument-Isolation-Policy: isolate-and-credentiallessresponse 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) and confirm the header is not sent. - Run
vendor/bin/phpunit tests/phpunit/tests/media/wpCrossOriginIsolation.php.
Patch
Pull request: https://github.com/WordPress/wordpress-develop/pull/11298
Upstream Gutenberg pull request:
https://github.com/WordPress/gutenberg/pull/76662
Change History (14)
#3
in reply to: ↑ 2
@
2 months ago
Replying to adamsilverstein:
The existing escape hatch for third-party page builders that override the block editor via a custom action query parameter is retained:
I don't think this is required any longer. We fixed the compatibility issue with Elementor in https://github.com/elementor/elementor/issues/35975, so i believe we can remove that, although its worth checking compatibility with other popular page builders like beaver builder, wp bakery and divi.
Page-builder compatibility Research
The escape hatch is kept because some builders take over an admin editor screen
via a custom action value and embed a same-origin front-end preview iframe
that they access synchronously — which Document-Isolation-Policy 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 (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 theaction-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
- Send DIP on front-end preview pages too (via
template_redirect). Earlier iterations added awp_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. No core counterpart remains. - Replace the
action !== 'edit'heuristic with editor-replacement detection (e.g.use_block_editor_for_post() === false/ thereplace_editorfilter). This would protect all editor-replacing builders regardless of query parameter — including WPBakery and Visual Composer, which theaction-based check misses. Deferred: it is a 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 revisiting as a follow-up. - Remove the escape hatch entirely (platform-forward) — send DIP on all admin editors and require builders to self-isolate as Elementor did. Rejected: it would regress Brizy and any other builder that embeds a same-origin preview iframe without having shipped a fix.
- Keep hooking only the specific editor screens (the status quo of
load-post.php/load-post-new.php/load-site-editor.php/load-widgets.php). Rejected: this is the root cause — sibling admin navigations load without DIP and fall into a different agent cluster, which is the bug this ticket fixes.
#5
@
8 weeks ago
Patch testing report
Patch / PR tested
- https://github.com/WordPress/wordpress-develop/pull/11298
- trunk @ fb76cccf79 with PR diff applied (PR head 7962e831b2)
Environment
WordPress: 7.1-alpha-62161-src
PHP: 8.2.18 (Docker)
MySQL: 8.0.36
OS: macOS 26.5.1
Browser: Chromium 149 (Chrome/149.0.0.0), desktop viewport
Local wordpress-develop @ http://localhost:8889
Steps
- Checked out PR #11298 and verified it is a PHP-only change (no build needed for -src).
- Ran the unit test file: vendor/bin/phpunit tests/phpunit/tests/media/wpCrossOriginIsolation.php.
- As an Administrator (has upload_files), fetched several admin screens and inspected the Document-Isolation-Policy (DIP) response header: index.php, edit.php, site-editor.php, upload.php, options-general.php, and post.php?post=…&action=edit.
- Tested the third-party page-builder escape hatch by requesting admin pages with custom action values: ?action=elementor and ?action=in-front-editor (Brizy), comparing against ?action=edit and no action.
- Created a Subscriber (no upload_files), signed in via an isolated browser context, and checked the DIP header on admin pages.
Results
- Unit tests: pass — OK (20 tests, 22 assertions), including the renamed test_returns_early_when_user_cannot_upload().
- Header on non-editor admin page (index.php / Dashboard): pass —
Document-Isolation-Policy: isolate-and-credentiallessnow present (on trunk it is absent here, since the header was previously hooked only to load-post/load-post-new/load-site-editor/load-widgets). This is the core fix. - Header on editor + sibling admin screens: pass — present on edit.php, site-editor.php, upload.php, options-general.php, and post.php?post=…&action=edit.
- Escape hatch (custom action): pass — header NOT sent on index.php?action=elementor or ?action=in-front-editor; correctly still sent for ?action=edit and requests with no action param.
- Subscriber (no upload_files): pass — header NOT sent on any admin page (index.php, profile.php).
Conclusion
PR #11298 broadens the Document-Isolation-Policy header from the four block-editor load-* screens to all admin pages by hooking wp_set_up_cross_origin_isolation() on admin_init, replacing the get_current_screen()/is_block_editor() gate with a current_user_can( 'upload_files' ) capability check, while retaining the existing action !== 'edit' escape hatch for page builders. Observed behaviour matches the ticket exactly: every admin navigation for an upload-capable user now stays in the same agent cluster, non-capable users and custom-action page builders are correctly excluded. The change is minimal and scoped to the ticket — 3 files (+14/−20), no signature or return-shape changes, internal hook registration only, so no backward-compatibility concern for the public API. The per-request cost is lower than before (capability check vs. screen resolution). Recommend commit.
This ticket was mentioned in PR #11298 on WordPress/wordpress-develop by @adamsilverstein.
8 weeks ago
#7
- Keywords has-unit-tests added
## 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 anupload_filescapability check. - Retain the existing escape hatch for third-party page builders that take over an admin editor screen via a custom
actionquery 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
- Admin-only. The front-end preview is intentionally out of scope; DIP remains an admin-only concern. This mirrors the upstream Gutenberg PR.
- The classic-theme site-preview skip is handled separately in #12004 (backport of GB #78404).
## 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 theaction-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
- Send DIP on front-end preview pages too (via
template_redirect). Earlier iterations of this backport added awp_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.) - Replace the
action !== 'edit'heuristic with editor-replacement detection (e.g.use_block_editor_for_post() === false/ thereplace_editorfilter). This would protect all editor-replacing builders regardless of query parameter — including WPBakery and Visual Composer, which theaction-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. - 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.
- Keep hooking only the specific editor screens (
load-post.phpetc., 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_isolationonadmin_initinstead of the fourload-*screen actions.
### src/wp-includes/media.php
wp_set_up_cross_origin_isolation()— drop theget_current_screen()block-editor gate; gate oncurrent_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()withtest_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 theDocument-Isolation-Policy: isolate-and-credentiallessresponse 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 customaction) and confirm the header is not sent.
## Related
- Upstream PR: WordPress/gutenberg#76662.
- Part of the 7.1 client-side media work.
@adamsilverstein commented on PR #11298:
8 weeks ago
#10
Code change looks reasonable. Probably should land early to get feedback in case it breaks plugins.
plugin breakage would be my main concern.
I guess it would be ok to commit before beta with a clear understanding that a revert might be needed after collecting feedback.
@adamsilverstein commented on PR #11298:
5 weeks ago
#11
I wonder if we should make this plugin only for a release cycle before backporting to core?
@apeatling commented on PR #11298:
10 days ago
#13
Coming here to add Miles to the list of things that will break with this change. I iframe the standard block editor from a Miles created admin page, and DIP splits the framed editor into a different agent cluster than its same-origin parent, the action and replace_editor skips don't help because we're rendering the real editor at action=edit.
@adamsilverstein commented on PR #11298:
3 days ago
#14
Coming here to add [bymiles.ai Miles] to the list of things that will break with this change. I iframe the standard block editor from a Miles created admin page, and DIP splits the framed editor into a different agent cluster than its same-origin parent, the action and replace_editor skips don't help because I'm rendering the real editor at action=edit.
Hi @apeatling - thanks for the feedback and example.
Rgarding your current compatibility issues - have you considered adopting DIP for your Miles Parent frame? If you emit Document-Isolation-Policy: isolate-and-credentialless while mirroring core's guards you should get full compatibility (Elementor did something similar).
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
I don't think this is required any longer. We fixed the compatibility issue with Elementor in https://github.com/elementor/elementor/issues/35975, so i believe we can remove that, although its worth checking compatibility with other popular page builders like beaver builder, wp bakery and divi.