Commit ffe25807169 for woocommerce
commit ffe25807169eba3d676b99650c432fd1c80a3d8b
Author: Taha Paksu <3295+tpaksu@users.noreply.github.com>
Date: Tue Mar 3 00:36:12 2026 +0300
Apply patch 439 to trunk (#63511)
* Apply patch 439 to trunk
* Fix lint errors
diff --git a/plugins/woocommerce/changelog/fix-sirt-store-api-batch-nonce-bypass b/plugins/woocommerce/changelog/fix-sirt-store-api-batch-nonce-bypass
new file mode 100644
index 00000000000..dfb96c46f30
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-sirt-store-api-batch-nonce-bypass
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix Store API batch endpoint path validation to prevent routing requests to non-Store-API endpoints.
diff --git a/plugins/woocommerce/src/StoreApi/Routes/V1/Batch.php b/plugins/woocommerce/src/StoreApi/Routes/V1/Batch.php
index 70587c324db..2984bbba8bd 100644
--- a/plugins/woocommerce/src/StoreApi/Routes/V1/Batch.php
+++ b/plugins/woocommerce/src/StoreApi/Routes/V1/Batch.php
@@ -117,7 +117,8 @@ class Batch extends AbstractRoute implements RouteInterface {
public function get_response( WP_REST_Request $request ) {
try {
foreach ( $request['requests'] as $args ) {
- if ( ! stristr( $args['path'], 'wc/store' ) ) {
+ $parsed_path = wp_parse_url( $args['path'], PHP_URL_PATH );
+ if ( ! $parsed_path || strpos( $parsed_path, '/wc/store' ) !== 0 ) {
throw new RouteException( 'woocommerce_rest_invalid_path', __( 'Invalid path provided.', 'woocommerce' ), 400 );
}
}
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Batch.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Batch.php
index 69a18972bff..0882cf1fdc1 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Batch.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Batch.php
@@ -157,4 +157,114 @@ class Batch extends ControllerTestCase {
$this->assertEquals( 2, count( $response_data['responses'] ) );
$this->assertEquals( 200, $response_data['responses'][0]['status'] );
}
+
+ /**
+ * @testdox Should reject batch sub-request with path outside Store API namespace.
+ * @dataProvider invalid_batch_paths_data
+ * @param string $path The path to test.
+ */
+ public function test_batch_rejects_invalid_path( string $path ): void {
+ $request = new \WP_REST_Request( 'POST', '/wc/store/v1/batch' );
+ $request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
+ $request->set_body_params(
+ array(
+ 'requests' => array(
+ array(
+ 'method' => 'POST',
+ 'path' => $path,
+ 'body' => array(),
+ ),
+ ),
+ )
+ );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertEquals( 400, $response->get_status(), "Path '$path' should be rejected" );
+ $this->assertEquals( 'woocommerce_rest_invalid_path', $response->get_data()['code'], "Path '$path' should return woocommerce_rest_invalid_path error code" );
+ }
+
+ /**
+ * Data provider for paths that should be rejected by batch path validation.
+ *
+ * @return array
+ */
+ public function invalid_batch_paths_data(): array {
+ return array(
+ 'non-store-api path' => array( '/wp/v2/users' ),
+ 'query string containing wc/store' => array( '/wp/v2/users?query=wc/store' ),
+ 'fragment containing wc/store' => array( '/wp/v2/users#wc/store' ),
+ 'wc/store appears in middle of non-api path' => array( '/other/wc/store/endpoint' ),
+ 'empty path' => array( '' ),
+ );
+ }
+
+ /**
+ * @testdox Should accept batch sub-request with valid Store API path.
+ * @dataProvider valid_batch_paths_data
+ * @param string $path The path to test.
+ */
+ public function test_batch_accepts_valid_store_api_path( string $path ): void {
+ $request = new \WP_REST_Request( 'POST', '/wc/store/v1/batch' );
+ $request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
+ $request->set_body_params(
+ array(
+ 'requests' => array(
+ array(
+ 'method' => 'GET',
+ 'path' => $path,
+ ),
+ ),
+ )
+ );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertNotEquals( 'woocommerce_rest_invalid_path', $response->get_data()['code'] ?? '', "Path '$path' should not be rejected by path validation" );
+ }
+
+ /**
+ * Data provider for paths that should pass batch path validation.
+ *
+ * @return array
+ */
+ public function valid_batch_paths_data(): array {
+ return array(
+ 'store api cart' => array( '/wc/store/v1/cart' ),
+ 'store api products' => array( '/wc/store/v1/products' ),
+ 'store api with query param' => array( '/wc/store/v1/products?per_page=5' ),
+ );
+ }
+
+ /**
+ * @testdox Should reject batch when one sub-request has a valid path and another has an invalid path.
+ */
+ public function test_batch_rejects_if_any_path_is_invalid(): void {
+ $request = new \WP_REST_Request( 'POST', '/wc/store/v1/batch' );
+ $request->set_header( 'Nonce', wp_create_nonce( 'wc_store_api' ) );
+ $request->set_body_params(
+ array(
+ 'requests' => array(
+ array(
+ 'method' => 'GET',
+ 'path' => '/wc/store/v1/cart',
+ ),
+ array(
+ 'method' => 'POST',
+ 'path' => '/wp/v2/users?query=wc/store',
+ 'body' => array(
+ 'username' => 'newuser',
+ 'email' => 'newuser@example.com',
+ 'password' => 'password123',
+ ),
+ ),
+ ),
+ )
+ );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertEquals( 400, $response->get_status(), 'Batch should be rejected when any sub-request path is invalid' );
+ $this->assertEquals( 'woocommerce_rest_invalid_path', $response->get_data()['code'] );
+ }
}