Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3f563a1
Media: Skip cross-origin isolation for third-party page builders.
adamsilverstein Mar 5, 2026
055c638
Merge branch 'trunk' into skip-page-builder-dip
adamsilverstein Mar 9, 2026
d8f3209
Merge branch 'trunk' into skip-page-builder-dip
adamsilverstein Mar 10, 2026
12e10a9
Merge branch 'trunk' into skip-page-builder-dip
adamsilverstein Mar 10, 2026
a65f438
Media: Disable client-side processing on non-secure origins
adamsilverstein Mar 10, 2026
1c74821
Media: Use multiline comment style per WP standards
adamsilverstein Mar 10, 2026
2f0421f
Media: Document page builder skip in docblock
adamsilverstein Mar 10, 2026
fb7caae
Merge branch 'trunk' into skip-page-builder-dip
adamsilverstein Mar 10, 2026
e7298c1
Media: Add tests for secure origin check
adamsilverstein Mar 10, 2026
85d6e62
Media: Remove phpcs nonce verification ignore
adamsilverstein Mar 10, 2026
a1e630e
Update src/wp-includes/media.php
adamsilverstein Mar 10, 2026
9ee5ac1
Use class property type hints and null coalescing operator
westonruter Mar 10, 2026
7af02dc
Update media.php
adamsilverstein Mar 10, 2026
ec1490c
Update media.php
adamsilverstein Mar 10, 2026
e26a9c3
Update media.php
adamsilverstein Mar 10, 2026
afacb9e
Update media.php
adamsilverstein Mar 10, 2026
77a504f
Merge branch 'trunk' into skip-page-builder-dip
adamsilverstein Mar 10, 2026
b122f7f
Fix sideload tests for non-secure contexts
adamsilverstein Mar 11, 2026
119efb6
Reinitialize REST server in sideload tests
adamsilverstein Mar 11, 2026
97d4a76
Merge branch 'trunk' into skip-page-builder-dip
adamsilverstein Mar 11, 2026
1ed4cdf
Merge branch 'trunk' into skip-page-builder-dip
adamsilverstein Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions src/wp-includes/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -6411,14 +6411,18 @@ function wp_get_image_editor_output_format( $filename, $mime_type ) {
* @return bool Whether client-side media processing is enabled.
*/
function wp_is_client_side_media_processing_enabled(): bool {
// This is due to SharedArrayBuffer requiring a secure context.
$host = strtolower( (string) strtok( $_SERVER['HTTP_HOST'] ?? '', ':' ) );
$enabled = ( is_ssl() || 'localhost' === $host || str_ends_with( $host, '.localhost' ) );

/**
* Filters whether client-side media processing is enabled.
*
* @since 7.0.0
*
* @param bool $enabled Whether client-side media processing is enabled. Default true.
* @param bool $enabled Whether client-side media processing is enabled. Default true if the page is served in a secure context.
*/
return (bool) apply_filters( 'wp_client_side_media_processing_enabled', true );
return (bool) apply_filters( 'wp_client_side_media_processing_enabled', $enabled );
}

/**
Expand All @@ -6431,7 +6435,7 @@ function wp_set_client_side_media_processing_flag(): void {
return;
}

wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true', 'before' );
wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true;', 'before' );

$chromium_version = wp_get_chromium_major_version();

Expand Down Expand Up @@ -6477,6 +6481,10 @@ function wp_get_chromium_major_version(): ?int {
* media processing in the editor. Uses Document-Isolation-Policy
* on supported browsers (Chromium 137+).
*
* Skips setup when a third-party page builder overrides the block
* editor via a custom `action` query parameter, as DIP would block
* same-origin iframe access that these editors rely on.
*
* @since 7.0.0
*/
function wp_set_up_cross_origin_isolation(): void {
Expand All @@ -6494,6 +6502,15 @@ function wp_set_up_cross_origin_isolation(): void {
return;
}

/*
* Skip when a third-party page builder overrides the block editor.
* DIP isolates the document into its own agent cluster,
* which blocks same-origin iframe access that these editors rely on.
*/
if ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While implementing wp_set_up_cross_origin_isolation, was this issue discussed anywhere? If so, could you please share the reference links?

Discuss edge cases where action=edit is used but the block editor is replaced (e.g., Web Stories).

Also, do we have any research or data on how many plugins might be impacted by this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not an issue for Web Stories because it short-circuits here:

if ( ! $screen->is_block_editor() && 'site-editor' !== $screen->id && ! ( 'widgets' === $screen->id && wp_use_widgets_block_editor() ) ) {
return;
}

This is because it is filtering replace_editor in the same way that the latest Elementor is doing in elementor/elementor#34900.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, it also short-circuits here with the Classic Editor plugin.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While implementing wp_set_up_cross_origin_isolation, was this issue discussed anywhere? If so, could you please share the reference links?

@mukeshpanchal27 -

the original pr included this fix, but it was removed because it was deemed unrelated (#11098 (comment)).

Both changes came out of bugs reports after the release of beta 1 - https://core.trac.wordpress.org/ticket/64740 there is more discussion there about why the fix for Elementor was added

return;
}

// Cross-origin isolation is not needed if users can't upload files anyway.
if ( ! current_user_can( 'upload_files' ) ) {
return;
Expand Down
69 changes: 65 additions & 4 deletions tests/phpunit/tests/media/wpCrossOriginIsolation.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,36 @@
* @group media
* @covers ::wp_set_up_cross_origin_isolation
* @covers ::wp_start_cross_origin_isolation_output_buffer
* @covers ::wp_is_client_side_media_processing_enabled
*/
class Tests_Media_wpCrossOriginIsolation extends WP_UnitTestCase {

/**
* Original HTTP_USER_AGENT value.
*
* @var string|null
*/
private $original_user_agent;
private ?string $original_user_agent;

/**
* Original HTTP_HOST value.
*/
private ?string $original_http_host;

/**
* Original HTTPS value.
*/
private ?string $original_https;

/**
* Original $_GET['action'] value.
*/
private ?string $original_get_action;

public function set_up() {
parent::set_up();
$this->original_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null;
$this->original_user_agent = $_SERVER['HTTP_USER_AGENT'] ?? null;
$this->original_http_host = $_SERVER['HTTP_HOST'] ?? null;
$this->original_https = $_SERVER['HTTPS'] ?? null;
$this->original_get_action = $_GET['action'] ?? null;
}

public function tear_down() {
Expand All @@ -28,6 +45,24 @@ public function tear_down() {
$_SERVER['HTTP_USER_AGENT'] = $this->original_user_agent;
}

if ( null === $this->original_http_host ) {
unset( $_SERVER['HTTP_HOST'] );
} else {
$_SERVER['HTTP_HOST'] = $this->original_http_host;
}

if ( null === $this->original_https ) {
unset( $_SERVER['HTTPS'] );
} else {
$_SERVER['HTTPS'] = $this->original_https;
}

if ( null === $this->original_get_action ) {
unset( $_GET['action'] );
} else {
$_GET['action'] = $this->original_get_action;
}

// Clean up any output buffers started during tests.
while ( ob_get_level() > 1 ) {
ob_end_clean();
Expand Down Expand Up @@ -124,6 +159,32 @@ public function test_does_not_start_output_buffer_for_safari() {
$this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Safari.' );
}

/**
* @ticket 64803
*/
public function test_client_side_processing_disabled_on_non_secure_origin() {
$_SERVER['HTTP_HOST'] = 'example.com';
$_SERVER['HTTPS'] = '';

$this->assertFalse(
wp_is_client_side_media_processing_enabled(),
'Client-side media processing should be disabled on non-secure, non-localhost origins.'
);
}

/**
* @ticket 64803
*/
public function test_client_side_processing_enabled_on_localhost() {
$_SERVER['HTTP_HOST'] = 'localhost';
$_SERVER['HTTPS'] = '';

$this->assertTrue(
wp_is_client_side_media_processing_enabled(),
'Client-side media processing should be enabled on localhost.'
);
}

/**
* This test must run in a separate process because the output buffer
* callback sends HTTP headers via header(), which would fail in the
Expand Down
30 changes: 30 additions & 0 deletions tests/phpunit/tests/rest-api/rest-attachments-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -3162,6 +3162,12 @@ static function ( $data ) use ( &$captured_data ) {
* @requires function imagejpeg
*/
public function test_sideload_scaled_image() {
add_filter( 'wp_client_side_media_processing_enabled', '__return_true' );
// Reinitialize REST server so the sideload route is registered.
global $wp_rest_server;
$wp_rest_server = new Spy_REST_Server();
do_action( 'rest_api_init', $wp_rest_server );

wp_set_current_user( self::$author_id );

// First, create an attachment.
Expand Down Expand Up @@ -3215,6 +3221,12 @@ public function test_sideload_scaled_image() {
* @requires function imagejpeg
*/
public function test_sideload_scaled_image_requires_auth() {
add_filter( 'wp_client_side_media_processing_enabled', '__return_true' );
// Reinitialize REST server so the sideload route is registered.
global $wp_rest_server;
$wp_rest_server = new Spy_REST_Server();
do_action( 'rest_api_init', $wp_rest_server );

wp_set_current_user( self::$author_id );

// Create an attachment.
Expand Down Expand Up @@ -3244,6 +3256,12 @@ public function test_sideload_scaled_image_requires_auth() {
* @ticket 64737
*/
public function test_sideload_route_includes_scaled_enum() {
add_filter( 'wp_client_side_media_processing_enabled', '__return_true' );
// Reinitialize REST server so the sideload route is registered.
global $wp_rest_server;
$wp_rest_server = new Spy_REST_Server();
do_action( 'rest_api_init', $wp_rest_server );

$server = rest_get_server();
$routes = $server->get_routes();

Expand All @@ -3266,6 +3284,12 @@ public function test_sideload_route_includes_scaled_enum() {
* @requires function imagejpeg
*/
public function test_sideload_scaled_unique_filename() {
add_filter( 'wp_client_side_media_processing_enabled', '__return_true' );
// Reinitialize REST server so the sideload route is registered.
global $wp_rest_server;
$wp_rest_server = new Spy_REST_Server();
do_action( 'rest_api_init', $wp_rest_server );

wp_set_current_user( self::$author_id );

// Create an attachment.
Expand Down Expand Up @@ -3300,6 +3324,12 @@ public function test_sideload_scaled_unique_filename() {
* @requires function imagejpeg
*/
public function test_sideload_scaled_unique_filename_conflict() {
add_filter( 'wp_client_side_media_processing_enabled', '__return_true' );
// Reinitialize REST server so the sideload route is registered.
global $wp_rest_server;
$wp_rest_server = new Spy_REST_Server();
do_action( 'rest_api_init', $wp_rest_server );

wp_set_current_user( self::$author_id );

// Create the first attachment.
Expand Down
3 changes: 3 additions & 0 deletions tests/phpunit/tests/rest-api/rest-schema-setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class WP_Test_REST_Schema_Initialization extends WP_Test_REST_TestCase {
public function set_up() {
parent::set_up();

// Ensure client-side media processing is enabled so the sideload route is registered.
add_filter( 'wp_client_side_media_processing_enabled', '__return_true' );

/** @var WP_REST_Server $wp_rest_server */
global $wp_rest_server;
$wp_rest_server = new Spy_REST_Server();
Expand Down
Loading