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