Commit ffa879e6556 for woocommerce
commit ffa879e6556aeb1b95467c75c6c8bca0e2eb8c55
Author: Justin P <228780+layoutd@users.noreply.github.com>
Date: Tue Oct 20 17:28:45 2026 +0200
Verify the nonce in the product importer Tracks handlers (#69988)
* Verify the importer nonce in the product importer Tracks handlers
* Add changelog entry for importer Tracks nonce verification
* Tighten importer Tracks nonce tests and declare the helper return type
* Shorten changelog comment for importer Tracks nonce verification
* Format the wrong-action nonce test array per coding standards
diff --git a/plugins/woocommerce/changelog/fix-importer-tracks-nonce-verification b/plugins/woocommerce/changelog/fix-importer-tracks-nonce-verification
new file mode 100644
index 00000000000..7d402772835
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-importer-tracks-nonce-verification
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+Comment: Verify the nonce in the product importer Tracks handlers; telemetry only, no merchant-facing change.
+
diff --git a/plugins/woocommerce/includes/tracks/events/class-wc-importer-tracking.php b/plugins/woocommerce/includes/tracks/events/class-wc-importer-tracking.php
index 8cd852a1569..82e3009e66a 100644
--- a/plugins/woocommerce/includes/tracks/events/class-wc-importer-tracking.php
+++ b/plugins/woocommerce/includes/tracks/events/class-wc-importer-tracking.php
@@ -24,7 +24,7 @@ class WC_Importer_Tracking {
* @return void
*/
public function track_product_importer() {
- // phpcs:disable WordPress.Security.NonceVerification.Recommended
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Each step callback verifies the importer nonce.
if ( ! isset( $_REQUEST['step'] ) ) {
return;
}
@@ -45,8 +45,8 @@ class WC_Importer_Tracking {
* @return void
*/
public function track_product_importer_start() {
- // phpcs:disable WordPress.Security.NonceVerification.Recommended
- if ( ! isset( $_REQUEST['file'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified in has_valid_nonce().
+ if ( ! isset( $_REQUEST['file'] ) || ! $this->has_valid_nonce() ) {
return;
}
@@ -65,11 +65,11 @@ class WC_Importer_Tracking {
* @return void
*/
public function track_product_importer_complete() {
- // phpcs:disable WordPress.Security.NonceVerification.Recommended
- if ( ! isset( $_REQUEST['_wpnonce'] ) ) {
+ if ( ! $this->has_valid_nonce() ) {
return;
}
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Nonce verified in has_valid_nonce().
$properties = array(
'imported' => isset( $_GET['products-imported'] ) ? absint( $_GET['products-imported'] ) : 0,
'imported_variations' => isset( $_GET['products-imported-variations'] ) ? absint( $_GET['products-imported-variations'] ) : 0,
@@ -81,4 +81,15 @@ class WC_Importer_Tracking {
WC_Tracks::record_event( 'product_import_complete', $properties );
}
+
+ /**
+ * Check that the request carries a valid product importer nonce.
+ *
+ * @return bool
+ */
+ private function has_valid_nonce(): bool {
+ $nonce = isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '';
+
+ return (bool) wp_verify_nonce( $nonce, 'woocommerce-csv-importer' );
+ }
}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-importer-tracking-test.php b/plugins/woocommerce/tests/php/includes/class-wc-importer-tracking-test.php
index 1181af134b0..ae6605e41ea 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-importer-tracking-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-importer-tracking-test.php
@@ -29,7 +29,9 @@ class WC_Importer_Tracking_Test extends \WC_Unit_Test_Case {
unset(
$_REQUEST['step'],
$_REQUEST['_wpnonce'],
- $_REQUEST['nonce'],
+ $_REQUEST['file'],
+ $_REQUEST['update_existing'],
+ $_REQUEST['delimiter'],
$_GET['products-imported'],
$_GET['products-imported-variations'],
$_GET['products-updated'],
@@ -78,13 +80,78 @@ class WC_Importer_Tracking_Test extends \WC_Unit_Test_Case {
}
/**
- * Without a nonce on the request the completion event must not fire.
+ * @testdox Should not record the completion event when the nonce is missing or invalid.
+ * @testWith [null]
+ * ["not-a-valid-nonce"]
+ *
+ * @param string|null $nonce Nonce value to send, or null to send none.
*/
- public function test_import_complete_not_recorded_without_nonce() {
+ public function test_import_complete_not_recorded_without_valid_nonce( ?string $nonce ): void {
$_REQUEST['step'] = 'done';
+ if ( null !== $nonce ) {
+ $_REQUEST['_wpnonce'] = $nonce;
+ }
do_action( 'product_page_product_importer' );
$this->assertNotRecordedTracksEvent( 'wcadmin_product_import_complete' );
}
+
+ /**
+ * @testdox Should record the start event with its options when the nonce is valid.
+ */
+ public function test_import_start_is_recorded_with_valid_nonce(): void {
+ $_REQUEST['step'] = 'import';
+ $_REQUEST['file'] = '/tmp/products.csv';
+ $_REQUEST['update_existing'] = '1';
+ $_REQUEST['delimiter'] = ';';
+ $_REQUEST['_wpnonce'] = wp_create_nonce( 'woocommerce-csv-importer' );
+
+ do_action( 'product_page_product_importer' );
+
+ $events = $this->get_tracks_events( 'wcadmin_product_import_start' );
+ $this->assertNotEmpty( $events, 'The start event should be recorded for a valid nonce' );
+ $event = end( $events );
+ $this->assertTrue( $event->update_existing );
+ $this->assertEquals( ';', $event->delimiter );
+ }
+
+ /**
+ * @testdox Should not record the start event when the nonce is missing or invalid.
+ * @testWith [null]
+ * ["not-a-valid-nonce"]
+ *
+ * @param string|null $nonce Nonce value to send, or null to send none.
+ */
+ public function test_import_start_not_recorded_without_valid_nonce( ?string $nonce ): void {
+ $_REQUEST['step'] = 'import';
+ $_REQUEST['file'] = '/tmp/products.csv';
+ if ( null !== $nonce ) {
+ $_REQUEST['_wpnonce'] = $nonce;
+ }
+
+ do_action( 'product_page_product_importer' );
+
+ $this->assertNotRecordedTracksEvent( 'wcadmin_product_import_start' );
+ }
+
+ /**
+ * @testdox Should ignore a valid nonce that was created for another action.
+ */
+ public function test_events_not_recorded_with_nonce_for_another_action(): void {
+ $_REQUEST['_wpnonce'] = wp_create_nonce( 'wc-product-import' );
+ $_REQUEST['file'] = '/tmp/products.csv';
+ $events_by_step = array(
+ 'import' => 'wcadmin_product_import_start',
+ 'done' => 'wcadmin_product_import_complete',
+ );
+
+ foreach ( $events_by_step as $step => $event ) {
+ $_REQUEST['step'] = $step;
+
+ do_action( 'product_page_product_importer' );
+
+ $this->assertNotRecordedTracksEvent( $event );
+ }
+ }
}