Commit 6554c9abca7 for woocommerce
commit 6554c9abca758274ce0b632b84aa63ceb26c8144
Author: Lucio Giannotta <lucio.giannotta@a8c.com>
Date: Mon Mar 30 14:01:02 2026 +0800
Fix Store API related products filter allowing transient bloat via arbitrary product IDs (#63846)
Validate that the product ID passed to the `related` query parameter
exists and is visible before calling `wc_get_related_products()`.
Without this check, unauthenticated requests with arbitrary IDs cause
unconditional transient writes, enabling database bloat on sites
without an object cache.
diff --git a/plugins/woocommerce/changelog/63846-woo6-37-codex-review-store-api-related-filter-enables-transient b/plugins/woocommerce/changelog/63846-woo6-37-codex-review-store-api-related-filter-enables-transient
new file mode 100644
index 00000000000..9b410cbd5da
--- /dev/null
+++ b/plugins/woocommerce/changelog/63846-woo6-37-codex-review-store-api-related-filter-enables-transient
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix Store API products endpoint allowing transient bloat via arbitrary product IDs in the `related` query parameter.
\ No newline at end of file
diff --git a/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php b/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
index ea67ab1b9b8..98ef5bb250b 100644
--- a/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
+++ b/plugins/woocommerce/src/StoreApi/Utilities/ProductQuery.php
@@ -7,6 +7,7 @@ use Automattic\WooCommerce\Enums\ProductStatus;
use Automattic\WooCommerce\Enums\ProductType;
use Automattic\WooCommerce\Enums\CatalogVisibility;
use Automattic\WooCommerce\Internal\ProductFilters\Interfaces\QueryClausesGenerator;
+use Automattic\WooCommerce\StoreApi\Exceptions\RouteException;
use WC_Tax;
/**
@@ -20,6 +21,7 @@ class ProductQuery implements QueryClausesGenerator {
*
* @param \WP_REST_Request $request Request data.
* @return array
+ * @throws RouteException If the related product ID is invalid or the product is not visible.
*/
public function prepare_objects_query( $request ) {
$args = array(
@@ -253,9 +255,19 @@ class ProductQuery implements QueryClausesGenerator {
// Filter by related products.
if ( ! empty( $request['related'] ) ) {
- $product_id = absint( $request['related'] );
- $limit = ! empty( $request['per_page'] ) ? (int) $request['per_page'] : 100;
- $related = wc_get_related_products( $product_id, $limit );
+ $product_id = absint( $request['related'] );
+ $related_product = wc_get_product( $product_id );
+
+ if ( ! $related_product || ! $related_product->is_visible() ) {
+ throw new RouteException(
+ 'woocommerce_rest_product_not_found',
+ __( 'The related product ID is invalid or the product is not visible.', 'woocommerce' ), // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- REST API JSON response, not HTML.
+ 404
+ );
+ }
+
+ $limit = ! empty( $request['per_page'] ) ? (int) $request['per_page'] : 100;
+ $related = wc_get_related_products( $product_id, $limit );
if ( ! empty( $related ) ) {
$args['post__in'] = ! empty( $args['post__in'] )
diff --git a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Products.php b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Products.php
index 1e110f45cac..fa952ecd8f2 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Products.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/StoreApi/Routes/Products.php
@@ -761,4 +761,19 @@ class Products extends ControllerTestCase {
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 0, $response->get_data() );
}
+
+ /**
+ * @testdox Related query parameter returns 404 for non-existent product and does not create a transient.
+ */
+ public function test_related_query_parameter_returns_404_for_nonexistent_product() {
+ $nonexistent_id = 999999999;
+
+ $request = new \WP_REST_Request( 'GET', '/wc/store/v1/products' );
+ $request->set_param( 'related', $nonexistent_id );
+
+ $response = rest_get_server()->dispatch( $request );
+
+ $this->assertEquals( 404, $response->get_status() );
+ $this->assertFalse( get_transient( 'wc_related_' . $nonexistent_id ), 'No transient should be created for a non-existent product.' );
+ }
}