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'] );
+	}
 }