Commit b77fc8fcf3 for woocommerce

commit b77fc8fcf3bb235af79e5fbe119accef92113578
Author: Chi-Hsuan Huang <chihsuan.tw@gmail.com>
Date:   Fri Apr 25 19:20:49 2025 +0800

    [Blueprint] Enhance SQL execution safety (#57344)

    * Enhance SQL execution safety in ImportRunSql class

    * Introduced validation checks for allowed SQL query types.
    * Added mechanisms to detect SQL injection patterns and prevent unauthorized modifications to protected user tables and capabilities.
    * Implemented SQL normalization to standardize query format.
    * Enhanced error handling during SQL execution with transaction support.

    * Add unit tests for ImportRunSql class

    * Implemented comprehensive tests for SQL query processing, including valid INSERT, UPDATE, and REPLACE queries.
    * Added tests to ensure invalid query types are correctly rejected and appropriate error messages are returned.
    * Included tests for SQL normalization, handling of SQL injection patterns, and protection against modifications to user roles and capabilities.
    * Ensured proper error handling for SQL execution failures and multiple statement queries.

    * Add changelog

    * Improve error handling in ImportRunSql class during SQL execution

    * Updated error suppression to be enabled explicitly.
    * Refactored error handling to store the last error in a variable for clarity.
    * Enhanced error messages to provide more informative feedback on SQL execution failures.

    * Refactor SQL handling in ImportRunSql class for improved security and clarity

    * Removed the normalize_sql method and replaced it with direct SQL trimming.
    * Added checks for suspicious SQL comments to enhance security against hidden malicious code.
    * Updated related tests to validate detection of suspicious comments and ensure proper error handling for SQL queries.
    * Simplified SQL validation logic by directly using trimmed SQL content in various checks.

    * Fix tests

    * Refactor SQL query preparation in ExportWCTaxRates class for improved security

    * Updated the SQL query in generateSteps method to use prepared statements, enhancing security against SQL injection.
    * Removed direct table name interpolation to ensure safer query execution.

    * Update table name handling in ExportWCTaxRates class for improved SQL query preparation

    * Modified the generateSteps method to prepend the table prefix directly to the table name variable, ensuring consistent and secure SQL query execution.
    * This change enhances clarity and maintains the integrity of the SQL preparation process.

    * Reformat

    * Add changelog

    * Remove unused imports

    * [Blueprint]  Add security restriction to only allow blueprint imports in Coming Soon mode (#57382)

    * Implement import permission check for blueprint uploads

    - Added a new API endpoint to check if blueprint imports are allowed based on site status.
    - Integrated the import permission check into the file upload state machine.
    - Updated the UI to display a notice when imports are disabled, guiding users to enable the "Coming Soon" mode or set a constant for live sites.
    - Enhanced styling for error and notice components related to blueprint uploads.

    This change improves user experience by providing clear feedback on import permissions.

    * Add changelog

    * Implement import permission check in the RestApi class

    - Added a check in the import_step method to verify if blueprint imports are allowed.
    - Returns an error message if imports are disabled, enhancing user feedback during the import process.

    * Fix lints

    * Add unit tests for blueprint import functionality

    - Introduced tests to validate the import_step endpoint, including checks for file size limits and import permissions based on site status.
    - Enhanced the setup and teardown methods for better resource management during tests.
    - Ensured that appropriate error messages are returned when imports are disabled or when file size exceeds the limit, improving feedback for users.

    * Fix lint

diff --git a/packages/php/blueprint/changelog/woo6-10-blueprints-security-audit-determine-the-scope-of-allowed-sql b/packages/php/blueprint/changelog/woo6-10-blueprints-security-audit-determine-the-scope-of-allowed-sql
new file mode 100644
index 0000000000..0e916f97af
--- /dev/null
+++ b/packages/php/blueprint/changelog/woo6-10-blueprints-security-audit-determine-the-scope-of-allowed-sql
@@ -0,0 +1,4 @@
+Significance: patch
+Type: enhancement
+
+Enhance SQL execution safety in ImportRunSql class
diff --git a/packages/php/blueprint/src/Importers/ImportRunSql.php b/packages/php/blueprint/src/Importers/ImportRunSql.php
index 3a2fb889bb..2b4bc3fbcd 100644
--- a/packages/php/blueprint/src/Importers/ImportRunSql.php
+++ b/packages/php/blueprint/src/Importers/ImportRunSql.php
@@ -9,7 +9,10 @@ use Automattic\WooCommerce\Blueprint\UsePluginHelpers;
 use Automattic\WooCommerce\Blueprint\UseWPFunctions;

 /**
- * Class ImportRunSql
+ * Processes SQL execution steps in the Blueprint.
+ *
+ * Handles the execution of SQL queries with safety checks to prevent
+ * unauthorized modifications to sensitive WordPress data.
  *
  * @package Automattic\WooCommerce\Blueprint\Importers
  */
@@ -18,22 +21,86 @@ class ImportRunSql implements StepProcessor {
 	use UseWPFunctions;

 	/**
-	 * Process the step.
+	 * List of allowed SQL query types.
 	 *
-	 * @param object $schema The schema for the step.
+	 * @var array
+	 */
+	private const ALLOWED_QUERY_TYPES = array(
+		'INSERT',
+		'UPDATE',
+		'REPLACE INTO',
+	);
+
+
+	/**
+	 * Process the SQL execution step.
 	 *
-	 * @return StepProcessorResult
+	 * Validates and executes the SQL query while ensuring:
+	 * 1. Only allowed query types are executed
+	 * 2. No modifications to admin users or roles
+	 * 3. No unauthorized changes to user capabilities
+	 *
+	 * @param object $schema The schema containing the SQL query to execute.
+	 * @return StepProcessorResult The result of the SQL execution.
 	 */
 	public function process( $schema ): StepProcessorResult {
 		global $wpdb;
 		$result = StepProcessorResult::success( RunSql::get_step_name() );

-		// Security check: Check if we can use prepared statements.
-		$wpdb->query( $schema->sql->contents ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
-		if ( $wpdb->last_error ) {
-			$result->add_error( "Error executing SQL: {$wpdb->last_error}" );
-		} else {
-			$result->add_debug( "Executed SQL ({$schema->sql->name}): {$schema->sql->contents}" );
+		$sql = trim( $schema->sql->contents );
+
+		// Check if the query type is allowed.
+		if ( ! $this->is_allowed_query_type( $sql ) ) {
+			$result->add_error(
+				sprintf(
+					'Only %s queries are allowed.',
+					implode( ', ', self::ALLOWED_QUERY_TYPES )
+				)
+			);
+			return $result;
+		}
+
+		// Check for SQL comments that might be hiding malicious code.
+		if ( $this->contains_suspicious_comments( $sql ) ) {
+			$result->add_error( 'SQL query contains suspicious comment patterns.' );
+			return $result;
+		}
+
+		// Detect SQL injection patterns.
+		if ( $this->contains_sql_injection_patterns( $sql ) ) {
+			$result->add_error( 'SQL query contains potential injection patterns.' );
+			return $result;
+		}
+
+		// Check if the query affects protected tables.
+		if ( $this->affects_protected_tables( $sql ) ) {
+			$result->add_error( 'Modifications to admin users or roles are not allowed.' );
+			return $result;
+		}
+
+		// Check if the query affects user capabilities in wp_options.
+		if ( $this->affects_user_capabilities( $sql ) ) {
+			$result->add_error( 'Modifications to user roles or capabilities are not allowed.' );
+			return $result;
+		}
+
+		$wpdb->suppress_errors( true );
+		$wpdb->query( 'START TRANSACTION' );
+
+		try {
+			$query_result = $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
+
+			$last_error = $wpdb->last_error;
+			if ( $last_error ) {
+				$wpdb->query( 'ROLLBACK' );
+				$result->add_error( 'Error executing SQL: ' . $last_error );
+			} else {
+				$wpdb->query( 'COMMIT' );
+				$result->add_debug( "Executed SQL ({$schema->sql->name}): Affected {$query_result} rows" );
+			}
+		} catch ( \Throwable $e ) {
+			$wpdb->query( 'ROLLBACK' );
+			$result->add_error( "Exception executing SQL: {$e->getMessage()}" );
 		}

 		return $result;
@@ -70,4 +137,175 @@ class ImportRunSql implements StepProcessor {

 		return true;
 	}
+	/**
+	 * Check if the SQL query type is allowed.
+	 *
+	 * @param string $sql_content The SQL query to check.
+	 * @return bool True if the query type is allowed, false otherwise.
+	 */
+	private function is_allowed_query_type( string $sql_content ): bool {
+		$uppercase_sql_content = strtoupper( trim( $sql_content ) );
+
+		foreach ( self::ALLOWED_QUERY_TYPES as $query_type ) {
+			if ( 0 === stripos( $uppercase_sql_content, $query_type ) ) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Check for suspicious comment patterns that might hide malicious code.
+	 *
+	 * This method detects various types of SQL comments that might be used
+	 * to hide malicious SQL commands or bypass security filters.
+	 *
+	 * @param string $sql_content The SQL query to check.
+	 * @return bool True if suspicious comments found, false otherwise.
+	 */
+	private function contains_suspicious_comments( string $sql_content ): bool {
+		// Quick check if there are any comments at all before running regex.
+		if (
+			strpos( $sql_content, '--' ) === false &&
+			strpos( $sql_content, '/*' ) === false &&
+			strpos( $sql_content, '#' ) === false
+		) {
+			return false;
+		}
+
+		// List of potentially dangerous SQL commands to check for in comments.
+		$dangerous_commands = array(
+			'DELETE',
+			'DROP',
+			'ALTER',
+			'CREATE',
+			'TRUNCATE',
+			'GRANT',
+			'REVOKE',
+			'EXEC',
+			'EXECUTE',
+			'CALL',
+			'INTO OUTFILE',
+			'INTO DUMPFILE',
+			'LOAD_FILE',
+			'LOAD DATA',
+			'BENCHMARK',
+			'SLEEP',
+			'INFORMATION_SCHEMA',
+			'USER\\(',
+			'DATABASE\\(',
+			'SCHEMA\\(',
+		);
+
+		$dangerous_pattern = implode( '|', $dangerous_commands );
+
+		// Check for SQL comments that might be hiding malicious code.
+		$patterns = array(
+			// Single-line comments (-- style) containing dangerous commands.
+			'/--.*?(' . $dangerous_pattern . ')/i',
+			// Single-line comments (# style) containing dangerous commands.
+			'/#.*?(' . $dangerous_pattern . ')/i',
+			// Multi-line comments hiding dangerous commands.
+			'/\/\*.*?(' . $dangerous_pattern . ').*?\*\//is',
+			// MySQL-specific execution comments (version-specific code execution).
+			'/\/\*![0-9]*.*?\*\//',
+		);
+
+		foreach ( $patterns as $pattern ) {
+			if ( preg_match( $pattern, $sql_content ) ) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+
+	/**
+	 * Check for common SQL injection patterns.
+	 *
+	 * @param string $sql_content The SQL query to check.
+	 * @return bool True if potential injection patterns found, false otherwise.
+	 */
+	private function contains_sql_injection_patterns( string $sql_content ): bool {
+		$patterns = array(
+			'/UNION\s+(?:ALL\s+)?SELECT/i',  // UNION-based injections.
+			'/OR\s+1\s*=\s*1/i',             // OR 1=1 condition.
+			'/AND\s+0\s*=\s*0/i',            // AND 0=0 condition.
+			'/;\s*--/i',                     // Inline comment terminations.
+			'/SLEEP\s*\(/i',                 // Time-based injections.
+			'/BENCHMARK\s*\(/i',             // Benchmark-based injections.
+			'/LOAD_FILE\s*\(/i',             // File access.
+			'/INTO\s+OUTFILE/i',             // File write.
+			'/INTO\s+DUMPFILE/i',            // File dump.
+			'/CREATE\s+(?:TEMPORARY\s+)?TABLE/i',  // Table creation.
+			'/DROP\s+TABLE/i',               // Table deletion.
+			'/ALTER\s+TABLE/i',              // Table alteration.
+			'/INFORMATION_SCHEMA/i',         // Database metadata access.
+			'/EXEC\s*\(/i',                  // Stored procedure execution.
+			'/SCHEMA_NAME/i',                // Schema access.
+			'/DATABASE\(\)/i',               // Current database name.
+			'/CHR\s*\(/i',                   // Character function for evasion.
+			'/CHAR\s*\(/i',                  // Character function for evasion.
+			'/FROM\s+mysql\./i',             // Direct MySQL system table access.
+			'/FROM\s+information_schema\./i', // Direct information schema access.
+		);
+		foreach ( $patterns as $pattern ) {
+			if ( preg_match( $pattern, $sql_content ) ) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+
+	/**
+	 * Check if the SQL query affects protected user tables.
+	 *
+	 * @param string $sql_content The SQL query to check.
+	 * @return bool True if the query affects protected tables, false otherwise.
+	 */
+	private function affects_protected_tables( string $sql_content ): bool {
+		global $wpdb;
+		$protected_tables = array(
+			$wpdb->users,
+			$wpdb->usermeta,
+		);
+
+		foreach ( $protected_tables as $table ) {
+			if ( preg_match( '/\b' . preg_quote( $table, '/' ) . '\b/i', $sql_content ) ) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Check if the SQL query affects user capabilities in wp_options.
+	 *
+	 * @param string $sql_content The SQL query to check.
+	 * @return bool True if the query affects user capabilities, false otherwise.
+	 */
+	private function affects_user_capabilities( string $sql_content ): bool {
+		global $wpdb;
+
+		// Check if the query affects user capabilities in wp_options.
+		if ( stripos( $sql_content, $wpdb->prefix . 'options' ) !== false ) {
+			$option_patterns = array(
+				'user_roles',
+				'capabilities',
+				'wp_user_',
+				'role_',
+				'administrator',
+			);
+
+			foreach ( $option_patterns as $pattern ) {
+				if ( stripos( $sql_content, $pattern ) !== false ) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
 }
diff --git a/packages/php/blueprint/tests/Unit/Importers/ImportRunSqlTest.php b/packages/php/blueprint/tests/Unit/Importers/ImportRunSqlTest.php
new file mode 100644
index 0000000000..ce703e900d
--- /dev/null
+++ b/packages/php/blueprint/tests/Unit/Importers/ImportRunSqlTest.php
@@ -0,0 +1,289 @@
+<?php
+
+namespace Automattic\WooCommerce\Blueprint\Tests\Unit\Importers;
+
+use Automattic\WooCommerce\Blueprint\Importers\ImportRunSql;
+use Automattic\WooCommerce\Blueprint\Steps\RunSql;
+use PHPUnit\Framework\TestCase;
+use stdClass;
+
+/**
+ * Tests for ImportRunSql.
+ */
+class ImportRunSqlTest extends TestCase {
+	/**
+	 * The importer instance being tested.
+	 *
+	 * @var ImportRunSql
+	 */
+	private $importer;
+
+	/**
+	 * Set up the test case.
+	 */
+	protected function setUp(): void {
+		parent::setUp();
+		$this->importer = new ImportRunSql();
+	}
+
+	/**
+	 * Test that the importer returns the correct step class.
+	 */
+	public function test_get_step_class(): void {
+		$this->assertEquals( RunSql::class, $this->importer->get_step_class() );
+	}
+
+	/**
+	 * Test that valid INSERT query is processed successfully.
+	 */
+	public function test_process_valid_insert_query(): void {
+		$schema = $this->create_sql_schema(
+			'INSERT INTO wp_posts (post_title) VALUES (\'Test Post\')',
+			'test_insert'
+		);
+
+		$result = $this->importer->process( $schema );
+
+		$this->assertTrue( $result->is_success() );
+	}
+
+	/**
+	 * Test that valid UPDATE query is processed successfully.
+	 */
+	public function test_process_valid_update_query(): void {
+		$schema = $this->create_sql_schema(
+			'UPDATE wp_posts SET post_title = \'Updated Title\' WHERE ID = 1',
+			'test_update'
+		);
+
+		$result = $this->importer->process( $schema );
+
+		$this->assertTrue( $result->is_success() );
+	}
+
+	/**
+	 * Test that REPLACE INTO query is processed successfully.
+	 */
+	public function test_process_valid_replace_query(): void {
+		$schema = $this->create_sql_schema(
+			'REPLACE INTO wp_options (option_name, option_value) VALUES (\'test_option\', \'test_value\')',
+			'test_replace'
+		);
+
+		$result = $this->importer->process( $schema );
+
+		$this->assertTrue( $result->is_success() );
+	}
+
+	/**
+	 * Test that invalid query types are rejected.
+	 *
+	 * @param string $query The query to test.
+	 *
+	 * @dataProvider invalid_queries_provider
+	 */
+	public function test_process_invalid_query_types( string $query ): void {
+		$schema = $this->create_sql_schema( $query, 'test_invalid_query' );
+
+		$result = $this->importer->process( $schema );
+
+		$this->assertFalse( $result->is_success() );
+		$error_messages = $result->get_messages( 'error' );
+		$this->assertNotEmpty( $error_messages );
+		$this->assertStringContainsString( 'Only INSERT, UPDATE, REPLACE INTO queries are allowed', $error_messages[0]['message'] );
+	}
+
+	/**
+	 * Data provider for invalid query types.
+	 *
+	 * @return array
+	 */
+	public function invalid_queries_provider(): array {
+		return array(
+			array( 'DELETE FROM wp_posts WHERE ID = 1' ),
+			array( 'SELECT * FROM wp_posts' ),
+			array( 'CREATE TABLE test_table (id INT)' ),
+			array( 'DROP TABLE IF EXISTS test_table' ),
+			array( 'ALTER TABLE wp_posts ADD COLUMN new_column INT' ),
+			array( 'TRUNCATE TABLE wp_posts' ),
+			array( 'GRANT ALL PRIVILEGES ON wp_posts TO \'user\'@\'localhost\'' ),
+			array( 'REVOKE ALL PRIVILEGES ON wp_posts FROM \'user\'@\'localhost\'' ),
+		);
+	}
+
+
+	/**
+	 * Test detection of suspicious SQL comments.
+	 *
+	 * @dataProvider suspicious_comments_provider
+	 *
+	 * @param string $name The name of the test case.
+	 * @param string $sql  The SQL query to test.
+	 */
+	public function test_contains_suspicious_comments( string $name, string $sql ): void {
+		$schema   = $this->create_sql_schema( $sql, $name );
+		$importer = new ImportRunSql();
+		$result   = $importer->process( $schema );
+
+		$this->assertFalse( $result->is_success() );
+		$error_messages = $result->get_messages( 'error' );
+		$this->assertNotEmpty( $error_messages );
+		$this->assertStringContainsString( 'SQL query contains suspicious comment patterns.', $error_messages[0]['message'], $name );
+	}
+
+
+	/**
+	 * Data provider for suspicious SQL comments.
+	 *
+	 * @return array[] Test cases with SQL queries containing suspicious comments.
+	 */
+	public function suspicious_comments_provider(): array {
+		return array(
+			array( 'single line comment with dangerous command', "UPDATE wp_posts SET post_status = 'draft' -- DROP TABLE wp_posts" ),
+			array( 'hash comment with dangerous command', "UPDATE wp_posts SET post_status = 'draft' # DELETE FROM wp_posts" ),
+			array( 'multi-line comment with dangerous command', "UPDATE wp_posts SET post_status = 'draft' /* ALTER TABLE wp_posts DROP COLUMN post_content */" ),
+			array( 'MySQL version specific comment', "UPDATE wp_posts SET post_status = 'draft' /*!40000 DROP TABLE wp_posts */" ),
+			array( 'comment after SQL keyword', "UPDATE/*! dangerous */wp_posts SET post_status = 'draft'" ),
+			array( 'comment with system table access', "UPDATE wp_posts SET post_status = 'draft' /* SELECT * FROM information_schema.tables */" ),
+			array( 'comment with function calls', "UPDATE wp_posts SET post_status = 'draft' /* SLEEP(10) */" ),
+		);
+	}
+
+
+	/**
+	 * Test that SQL injection patterns are detected and rejected.
+	 *
+	 * @param string $query The query to test.
+	 *
+	 * @dataProvider sql_injection_patterns_provider
+	 */
+	public function test_process_sql_injection_patterns( string $query ): void {
+		$schema = $this->create_sql_schema( $query, 'test_sql_injection' );
+
+		$result = $this->importer->process( $schema );
+
+		$this->assertFalse( $result->is_success() );
+		$error_messages = $result->get_messages( 'error' );
+		$this->assertNotEmpty( $error_messages );
+
+		$expected_message = 'SQL query contains potential injection patterns.';
+		$actual_message   = $error_messages[0]['message'];
+		$this->assertEquals( $expected_message, $actual_message );
+	}
+
+	/**
+	 * Data provider for SQL injection patterns.
+	 *
+	 * @return array
+	 */
+	public function sql_injection_patterns_provider(): array {
+		return array(
+			array( 'INSERT INTO wp_posts (post_title) VALUES (\'test\') UNION SELECT * FROM wp_users' ),
+			array( 'UPDATE wp_posts SET post_title = \'test\' WHERE 1=1 OR 1=1' ),
+			array( 'UPDATE wp_posts SET post_title = \'test\' WHERE ID = 1 AND 0=0' ),
+			array( 'INSERT INTO wp_posts (post_title) VALUES (\'test\') UNION ALL SELECT user_login FROM wp_users' ),
+			array( 'UPDATE wp_posts SET post_title = (SELECT SLEEP(5)) WHERE ID = 1' ),
+			array( 'UPDATE wp_posts SET post_title = (SELECT BENCHMARK(1000000,MD5(\'test\'))) WHERE ID = 1' ),
+			array( 'INSERT INTO wp_posts (post_title) VALUES ((SELECT LOAD_FILE(\'/etc/passwd\')))' ),
+		);
+	}
+
+
+	/**
+	 * Test that queries affecting protected tables are rejected.
+	 */
+	public function test_protected_tables_access(): void {
+		global $wpdb;
+		$protected_tables = array(
+			$wpdb->prefix . 'users',
+			$wpdb->prefix . 'usermeta',
+		);
+
+		foreach ( $protected_tables as $table ) {
+			$schema = $this->create_sql_schema(
+				"INSERT INTO $table (user_login) VALUES ('test_user')",
+				'test_protected_table'
+			);
+
+			$result = $this->importer->process( $schema );
+
+			$this->assertFalse( $result->is_success() );
+			$error_messages = $result->get_messages( 'error' );
+			$this->assertNotEmpty( $error_messages );
+			$this->assertStringContainsString( 'Modifications to admin users or roles are not allowed', $error_messages[0]['message'], $table );
+		}
+	}
+
+	/**
+	 * Test that queries affecting user capabilities are rejected.
+	 */
+	public function test_user_capabilities_protection(): void {
+		global $wpdb;
+		$queries = array(
+			"INSERT INTO {$wpdb->prefix}options (option_name, option_value) VALUES ('wp_user_roles', 'test')",
+			"UPDATE {$wpdb->prefix}options SET option_value = 'test' WHERE option_name LIKE '%capabilities%'",
+			"REPLACE INTO {$wpdb->prefix}options (option_name, option_value) VALUES ('role_administrator', 'test')",
+		);
+
+		foreach ( $queries as $query ) {
+			$schema = $this->create_sql_schema( $query, 'test_capabilities' );
+
+			$result = $this->importer->process( $schema );
+
+			$this->assertFalse( $result->is_success() );
+			$error_messages = $result->get_messages( 'error' );
+			$this->assertNotEmpty( $error_messages );
+			$this->assertStringContainsString( 'Modifications to user roles or capabilities are not allowed', $error_messages[0]['message'] );
+		}
+	}
+
+
+	/**
+	 * Test that SQL execution error is handled properly.
+	 */
+	public function test_process_sql_execution_error(): void {
+		$schema = $this->create_sql_schema(
+			'INSERT INTO wp_test_table (test_column) VALUES (\'Test Value\')',
+			'test_error'
+		);
+
+		$result = $this->importer->process( $schema );
+
+		$this->assertFalse( $result->is_success() );
+		$error_messages = $result->get_messages( 'error' );
+		$this->assertNotEmpty( $error_messages );
+		$this->assertStringContainsString( 'Error executing SQL', $error_messages[0]['message'] );
+	}
+
+	/**
+	 * Test that queries with multiple statements are rejected.
+	 */
+	public function test_process_multiple_statements_rejected(): void {
+		$schema = $this->create_sql_schema(
+			'INSERT INTO wp_posts (post_title) VALUES (\'Test Post\'); UPDATE wp_posts SET post_status = \'publish\'',
+			'test_multiple_statements'
+		);
+
+		$result = $this->importer->process( $schema );
+
+		$this->assertFalse( $result->is_success() );
+		$error_messages = $result->get_messages( 'error' );
+		$this->assertNotEmpty( $error_messages );
+		$this->assertStringContainsString( 'Error executing SQL', $error_messages[0]['message'] );
+	}
+
+	/**
+	 * Create a schema object for SQL testing.
+	 *
+	 * @param string $sql_contents The SQL query.
+	 * @param string $name        The name of the SQL step.
+	 * @return stdClass
+	 */
+	private function create_sql_schema( string $sql_contents, string $name ): stdClass {
+		$schema                = new stdClass();
+		$schema->sql           = new stdClass();
+		$schema->sql->contents = $sql_contents;
+		$schema->sql->name     = $name;
+		return $schema;
+	}
+}
diff --git a/plugins/woocommerce/changelog/woo6-10-blueprints-security-audit-determine-the-scope-of-allowed-sql b/plugins/woocommerce/changelog/woo6-10-blueprints-security-audit-determine-the-scope-of-allowed-sql
new file mode 100644
index 0000000000..073e2812a2
--- /dev/null
+++ b/plugins/woocommerce/changelog/woo6-10-blueprints-security-audit-determine-the-scope-of-allowed-sql
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix blueprint export tax sql command
diff --git a/plugins/woocommerce/changelog/wooplug-3965-blueprint-restrict-blueprint-imports-to-coming-soon-mode b/plugins/woocommerce/changelog/wooplug-3965-blueprint-restrict-blueprint-imports-to-coming-soon-mode
new file mode 100644
index 0000000000..9c4d91105a
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooplug-3965-blueprint-restrict-blueprint-imports-to-coming-soon-mode
@@ -0,0 +1,4 @@
+Significance: patch
+Type: add
+
+Restrict blueprint imports to coming soon mode
diff --git a/plugins/woocommerce/client/admin/client/blueprint/components/BlueprintUploadDropzone.tsx b/plugins/woocommerce/client/admin/client/blueprint/components/BlueprintUploadDropzone.tsx
index 9d3f8a4e90..d963a6bb0e 100644
--- a/plugins/woocommerce/client/admin/client/blueprint/components/BlueprintUploadDropzone.tsx
+++ b/plugins/woocommerce/client/admin/client/blueprint/components/BlueprintUploadDropzone.tsx
@@ -22,6 +22,8 @@ import {
 import apiFetch from '@wordpress/api-fetch';
 import { dispatch } from '@wordpress/data';
 import { recordEvent } from '@woocommerce/tracks';
+import { createInterpolateElement } from '@wordpress/element';
+import { getAdminLink } from '@woocommerce/settings';

 /**
  * Internal dependencies
@@ -147,11 +149,26 @@ const importBlueprint = async ( steps: BlueprintStep[] ) => {
 	}
 };

+const checkImportAllowed = async (): Promise< boolean > => {
+	try {
+		const response = await apiFetch< { import_allowed: boolean } >( {
+			path: 'wc-admin/blueprint/import-allowed',
+			method: 'GET',
+		} );
+		return response.import_allowed;
+	} catch ( error ) {
+		throw new Error(
+			__( 'Failed to check if imports are allowed.', 'woocommerce' )
+		);
+	}
+};
+
 interface FileUploadContext {
 	file?: File;
 	steps?: BlueprintStep[];
 	error?: Error;
 	settings_to_overwrite?: string[];
+	import_allowed?: boolean;
 }

 type FileUploadEvents =
@@ -223,6 +240,7 @@ export const fileUploadMachine = setup( {
 		stepsParser: fromPromise( ( { input }: { input: { file: File } } ) =>
 			parseBlueprintSteps( input.file )
 		),
+		importAllowedChecker: fromPromise( () => checkImportAllowed() ),
 	},
 	guards: {
 		hasSettingsToOverwrite: ( { context } ) =>
@@ -233,13 +251,32 @@ export const fileUploadMachine = setup( {
 	},
 } ).createMachine( {
 	id: 'fileUpload',
-	initial: 'idle',
+	initial: 'checkingImportAllowed',
 	context: () => ( {} ),
 	states: {
+		checkingImportAllowed: {
+			invoke: {
+				src: 'importAllowedChecker',
+				onDone: {
+					target: 'idle',
+					actions: assign( {
+						import_allowed: ( { event } ) => event.output,
+						error: () => undefined,
+					} ),
+				},
+				onError: {
+					target: 'error',
+					actions: assign( {
+						error: ( { event } ) => event.error as Error,
+					} ),
+				},
+			},
+		},
 		idle: {
 			on: {
 				UPLOAD: {
 					target: 'parsingSteps',
+					guard: ( { context } ) => context.import_allowed === true,
 					actions: assign( {
 						file: ( { event } ) => event.file,
 						error: () => undefined,
@@ -393,6 +430,39 @@ export const BlueprintUploadDropzone = () => {

 	return (
 		<>
+			{ state.matches( 'checkingImportAllowed' ) && (
+				<div className="blueprint-upload-form">
+					<div className="blueprint-upload-dropzone-uploading">
+						<Spinner />
+					</div>
+				</div>
+			) }
+			{ state.context.import_allowed === false &&
+				! state.context.error && (
+					<Notice
+						status="warning"
+						isDismissible={ false }
+						className="blueprint-upload-dropzone-notice"
+					>
+						{ createInterpolateElement(
+							__(
+								'Blueprint imports are disabled by default for live sites. <br/>Enable <link>Coming Soon mode</link> or define "ALLOW_BLUEPRINT_IMPORT_IN_LIVE_MODE" as true.',
+								'woocommerce'
+							),
+							{
+								br: <br />,
+								link: (
+									// eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
+									<a
+										href={ getAdminLink(
+											'admin.php?page=wc-settings&tab=site-visibility'
+										) }
+									/>
+								),
+							}
+						) }
+					</Notice>
+				) }
 			{ state.context.error && (
 				<div className="blueprint-upload-dropzone-error">
 					<Notice
@@ -405,49 +475,50 @@ export const BlueprintUploadDropzone = () => {
 					</Notice>
 				</div>
 			) }
-			{ ( state.matches( 'idle' ) ||
-				state.matches( 'error' ) ||
-				state.matches( 'parsingSteps' ) ) && (
-				<div className="blueprint-upload-form">
-					<FormFileUpload
-						className="blueprint-upload-field"
-						accept="application/json, application/zip"
-						multiple={ false }
-						onChange={ ( evt ) => {
-							const file = evt.target.files?.[ 0 ]; // since multiple is disabled it has to be in 0
-							if ( file ) {
-								send( { type: 'UPLOAD', file } );
-							}
-						} }
-					>
-						<div className="blueprint-upload-dropzone">
-							<Icon icon={ upload } />
-							<p className="blueprint-upload-dropzone-text">
-								{ __( 'Drag and drop or ', 'woocommerce' ) }
-								<span>
-									{ __( 'choose a file', 'woocommerce' ) }
-								</span>
-							</p>
-							<DropZone
-								onFilesDrop={ ( files ) => {
-									if ( files.length > 1 ) {
+			{ state.context.import_allowed &&
+				( state.matches( 'idle' ) ||
+					state.matches( 'error' ) ||
+					state.matches( 'parsingSteps' ) ) && (
+					<div className="blueprint-upload-form">
+						<FormFileUpload
+							className="blueprint-upload-field"
+							accept="application/json, application/zip"
+							multiple={ false }
+							onChange={ ( evt ) => {
+								const file = evt.target.files?.[ 0 ]; // since multiple is disabled it has to be in 0
+								if ( file ) {
+									send( { type: 'UPLOAD', file } );
+								}
+							} }
+						>
+							<div className="blueprint-upload-dropzone">
+								<Icon icon={ upload } />
+								<p className="blueprint-upload-dropzone-text">
+									{ __( 'Drag and drop or ', 'woocommerce' ) }
+									<span>
+										{ __( 'choose a file', 'woocommerce' ) }
+									</span>
+								</p>
+								<DropZone
+									onFilesDrop={ ( files ) => {
+										if ( files.length > 1 ) {
+											send( {
+												type: 'ERROR',
+												error: new Error(
+													'Only one file can be uploaded at a time'
+												),
+											} );
+										}
 										send( {
-											type: 'ERROR',
-											error: new Error(
-												'Only one file can be uploaded at a time'
-											),
+											type: 'UPLOAD',
+											file: files[ 0 ],
 										} );
-									}
-									send( {
-										type: 'UPLOAD',
-										file: files[ 0 ],
-									} );
-								} }
-							></DropZone>
-						</div>
-					</FormFileUpload>
-				</div>
-			) }
+									} }
+								></DropZone>
+							</div>
+						</FormFileUpload>
+					</div>
+				) }
 			{ state.matches( 'importing' ) && (
 				<div className="blueprint-upload-form">
 					<div className="blueprint-upload-dropzone-uploading">
@@ -480,6 +551,7 @@ export const BlueprintUploadDropzone = () => {
 				<Button
 					className="woocommerce-blueprint-import-button"
 					variant="primary"
+					disabled={ ! state.context.import_allowed }
 					onClick={ () => {
 						send( { type: 'IMPORT' } );
 					} }
diff --git a/plugins/woocommerce/client/admin/client/blueprint/components/style.scss b/plugins/woocommerce/client/admin/client/blueprint/components/style.scss
index 22c34a6429..5902671e0c 100644
--- a/plugins/woocommerce/client/admin/client/blueprint/components/style.scss
+++ b/plugins/woocommerce/client/admin/client/blueprint/components/style.scss
@@ -1,4 +1,4 @@
-
+.blueprint-upload-dropzone-notice,
 .blueprint-upload-dropzone-error {
 	margin-top: 16px;
 }
diff --git a/plugins/woocommerce/client/admin/client/blueprint/settings/style.scss b/plugins/woocommerce/client/admin/client/blueprint/settings/style.scss
index 1012d76164..eea1a1ee93 100644
--- a/plugins/woocommerce/client/admin/client/blueprint/settings/style.scss
+++ b/plugins/woocommerce/client/admin/client/blueprint/settings/style.scss
@@ -161,9 +161,7 @@
 		margin-top: 14px;
 	}

-	.components-notice.is-error {
-		background: #fce2e4;
-		border-left-color: #cc1818;
+	.components-notice {
 		padding: 12px;

 		button.components-notice__dismiss {
@@ -174,17 +172,19 @@
 			}
 		}

-		.components-notice__content {
+		.components-notice__content,
+		&.is-error .components-notice__content pre {
 			margin: 0;
-
-			pre {
-				color: $gray-900;
-				line-height: 24px; /* 184.615% */
-				margin: 0;
-				font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
-			}
+			color: $gray-900;
+			line-height: 24px; /* 184.615% */
+			font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
 		}
 	}
+
+	.components-notice.is-error {
+		background: #fce2e4;
+		border-left-color: #cc1818;
+	}
 }

 // Show the notice at the bottom right of the screen
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaxRates.php b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaxRates.php
index 74e6ca4d42..432d7e0a18 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaxRates.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/Exporters/ExportWCTaxRates.php
@@ -41,8 +41,7 @@ class ExportWCTaxRates implements StepExporter, HasAlias {
 		$table = $wpdb->prefix . $table;
 		return array_map(
 			fn( $record ) => new RunSql( Util::array_to_insert_sql( $record, $table, 'replace into' ) ),
-			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
-			$wpdb->get_results( "SELECT * FROM {$table}", ARRAY_A )
+			$wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i', $table ), ARRAY_A ),
 		);
 	}

diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/Init.php b/plugins/woocommerce/src/Admin/Features/Blueprint/Init.php
index 1681137e88..f212bbf040 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/Init.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/Init.php
@@ -4,7 +4,6 @@ declare( strict_types = 1 );

 namespace Automattic\WooCommerce\Admin\Features\Blueprint;

-use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCCoreProfilerOptions;
 use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCPaymentGateways;
 use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsAccount;
 use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsAdvanced;
@@ -14,15 +13,10 @@ use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsIn
 use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsProducts;
 use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCSettingsSiteVisibility;
 use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCShipping;
-use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCTaskOptions;
 use Automattic\WooCommerce\Admin\Features\Blueprint\Exporters\ExportWCTaxRates;
-use Automattic\WooCommerce\Admin\Features\Blueprint\Importers\ImportSetWCPaymentGateways;
-use Automattic\WooCommerce\Admin\Features\Blueprint\Importers\ImportSetWCShipping;
-use Automattic\WooCommerce\Admin\Features\Blueprint\Importers\ImportSetWCTaxRates;
 use Automattic\WooCommerce\Admin\PageController;
 use Automattic\WooCommerce\Blueprint\Exporters\HasAlias;
 use Automattic\WooCommerce\Blueprint\Exporters\StepExporter;
-use Automattic\WooCommerce\Blueprint\StepProcessor;
 use Automattic\WooCommerce\Blueprint\UseWPFunctions;

 /**
diff --git a/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php b/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php
index 52e27da5c2..03b309db8c 100644
--- a/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php
+++ b/plugins/woocommerce/src/Admin/Features/Blueprint/RestApi.php
@@ -8,7 +8,8 @@ use Automattic\WooCommerce\Blueprint\Exporters\ExportInstallPluginSteps;
 use Automattic\WooCommerce\Blueprint\Exporters\ExportInstallThemeSteps;
 use Automattic\WooCommerce\Blueprint\ExportSchema;
 use Automattic\WooCommerce\Blueprint\ImportStep;
-use Automattic\WooCommerce\Blueprint\ZipExportedSchema;
+use Automattic\WooCommerce\Internal\ComingSoon\ComingSoonHelper;
+use WP_Error;

 /**
  * Class RestApi
@@ -30,6 +31,20 @@ class RestApi {
 	 */
 	protected $namespace = 'wc-admin';

+	/**
+	 * ComingSoonHelper instance.
+	 *
+	 * @var ComingSoonHelper
+	 */
+	protected $coming_soon_helper;
+
+	/**
+	 * Constructor.
+	 */
+	public function __construct() {
+		$this->coming_soon_helper = new ComingSoonHelper();
+	}
+
 	/**
 	 * Get maximum allowed file size for blueprint uploads.
 	 *
@@ -110,6 +125,21 @@ class RestApi {
 				'schema' => array( $this, 'get_import_step_response_schema' ),
 			)
 		);
+
+		register_rest_route(
+			$this->namespace,
+			'/blueprint/import-allowed',
+			array(
+				array(
+					'methods'             => \WP_REST_Server::READABLE,
+					'callback'            => array( $this, 'get_import_allowed' ),
+					'permission_callback' => function () {
+						return current_user_can( 'manage_woocommerce' );
+					},
+				),
+				'schema' => array( $this, 'get_import_allowed_schema' ),
+			)
+		);
 	}

 	/**
@@ -235,6 +265,17 @@ class RestApi {
 	 * @return array
 	 */
 	public function import_step( \WP_REST_Request $request ) {
+		if ( ! $this->can_import_blueprint() ) {
+			return array(
+				'success'  => false,
+				'messages' => array(
+					array(
+						'message' => __( 'Blueprint imports are disabled', 'woocommerce' ),
+						'type'    => 'error',
+					),
+				),
+			);
+		}
 		// Get the raw body size.
 		$body_size = strlen( $request->get_body() );
 		if ( $body_size > $this->get_max_file_size() ) {
@@ -264,6 +305,63 @@ class RestApi {
 		);
 	}

+
+	/**
+	 * Check if blueprint imports are allowed based on site status and configuration.
+	 *
+	 * @return bool Returns true if imports are allowed, false otherwise.
+	 */
+	private function can_import_blueprint() {
+		// Check if override constant is defined and true.
+		if ( defined( 'ALLOW_BLUEPRINT_IMPORT_IN_LIVE_MODE' ) && ALLOW_BLUEPRINT_IMPORT_IN_LIVE_MODE ) {
+			return true;
+		}
+
+		// Only allow imports in coming soon mode.
+		if ( $this->coming_soon_helper->is_site_live() ) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * Get whether blueprint imports are allowed.
+	 *
+	 * @return \WP_REST_Response
+	 */
+	public function get_import_allowed() {
+		$can_import = $this->can_import_blueprint();
+
+		return rest_ensure_response(
+			array(
+				'import_allowed' => $can_import,
+			)
+		);
+	}
+
+	/**
+	 * Get the schema for the import-allowed endpoint.
+	 *
+	 * @return array
+	 */
+	public function get_import_allowed_schema() {
+		return array(
+			'$schema'    => 'http://json-schema.org/draft-04/schema#',
+			'title'      => 'blueprint-import-allowed',
+			'type'       => 'object',
+			'properties' => array(
+				'import_allowed' => array(
+					'description' => __( 'Whether blueprint imports are currently allowed', 'woocommerce' ),
+					'type'        => 'boolean',
+					'context'     => array( 'view' ),
+					'readonly'    => true,
+				),
+			),
+		);
+	}
+
+
 	/**
 	 * Get the schema for the import-step endpoint.
 	 *
diff --git a/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/RestApiTest.php b/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/RestApiTest.php
index e2e8e519bc..98a7687276 100644
--- a/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/RestApiTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/Features/Blueprint/RestApiTest.php
@@ -3,6 +3,8 @@
  * Unit tests for RestApi class.
  */

+declare(strict_types=1);
+
 namespace Automattic\WooCommerce\Tests\Admin\Features\Blueprint;

 use Automattic\WooCommerce\Admin\Features\Blueprint\RestApi;
@@ -23,64 +25,168 @@ class RestApiTest extends WP_Test_REST_TestCase {
 	 */
 	private $temp_file;

+	/**
+	 * @var int User ID with administrator role.
+	 */
+	private $user;
+
 	/**
 	 * Setup test case.
 	 */
 	public function setUp(): void {
 		parent::setUp();
 		$this->rest_api = new RestApi();
+		$this->useAdmin();
+
+		// Create a temporary test file with valid Blueprint schema.
+		$this->temp_file   = wp_tempnam( 'blueprint_test_' );
+		$blueprint_content = wp_json_encode(
+			array(
+				'steps' => array(
+					array(
+						'step'     => 'setWCSettings',
+						'settings' => array(
+							'woocommerce_store_address' => '123 Test St',
+							'woocommerce_store_city'    => 'Test City',
+						),
+					),
+				),
+			)
+		);
+		global $wp_filesystem;
+		WP_Filesystem();
+		$wp_filesystem->put_contents( $this->temp_file, $blueprint_content );
+	}
+
+	/**
+	 * Use a user with administrator role.
+	 *
+	 * @return void
+	 */
+	public function useAdmin() {
+		// Register an administrator user and log in.
+		$this->user = $this->factory->user->create(
+			array(
+				'role' => 'administrator',
+			)
+		);
+		wp_set_current_user( $this->user );
+	}
+
+	/**
+	 * Clean up after each test.
+	 */
+	public function tearDown(): void {
+		parent::tearDown();
+		// Clean up global state.
+		unset( $_FILES['file'] );
+
+		// Clean up temporary file.
+		if ( file_exists( $this->temp_file ) ) {
+			wp_delete_file( $this->temp_file );
+		}
+
+		remove_all_filters( 'pre_option_woocommerce_coming_soon' );
+	}
+
+	/**
+	 * Test that blueprint imports are disabled in live mode.
+	 */
+	public function test_cannot_import_blueprint_in_live_mode() {
+		add_filter(
+			'pre_option_woocommerce_coming_soon',
+			function () {
+				return 'no';
+			}
+		);
+
+		$request = new \WP_REST_Request( 'POST', '/wc-admin/blueprint/import-step' );
+		$request->set_body(
+			wp_json_encode(
+				array(
+					'step_definition' => array(
+						'step'    => 'setSiteOptions',
+						'options' => array(
+							'woocommerce_store_address' => '123 Test St',
+						),
+					),
+				)
+			)
+		);
+
+		$response = $this->rest_api->import_step( $request );

-		// Create a temporary test file with valid Blueprint schema
-		$this->temp_file = wp_tempnam('blueprint_test_');
-		$blueprint_content = json_encode([
-			'steps' => [
-				[
-					'step' => 'setWCSettings',
-					'settings' => [
-						'woocommerce_store_address' => '123 Test St',
-						'woocommerce_store_city' => 'Test City'
-					]
-				]
-			]
-		]);
-		file_put_contents($this->temp_file, $blueprint_content);
+		$this->assertFalse( $response['success'] );
+		$this->assertCount( 1, $response['messages'] );
+		$this->assertEquals( 'error', $response['messages'][0]['type'] );
+		$this->assertStringContainsString( 'Blueprint imports are disabled', $response['messages'][0]['message'] );
 	}

 	/**
 	 * Test file size validation in import_step endpoint.
 	 */
 	public function test_import_step_file_size_validation() {
-		// Create a large request body
-		$large_value = str_repeat('X', RestApi::MAX_FILE_SIZE + 1024); // Slightly over limit
-		$request = new \WP_REST_Request('POST', '/wc-admin/blueprint/import-step');
-		$request->set_body(json_encode(array(
-			'step_definition' => array(
-				'step' => 'setWCSettings',
-				'settings' => array(
-					'large_setting' => $large_value
+		add_filter(
+			'pre_option_woocommerce_coming_soon',
+			function () {
+				return 'yes';
+			}
+		);
+
+		// Create a large request body.
+		$large_value = str_repeat( 'X', RestApi::MAX_FILE_SIZE + 1024 ); // Slightly over limit.
+		$request     = new \WP_REST_Request( 'POST', '/wc-admin/blueprint/import-step' );
+		$request->set_body(
+			wp_json_encode(
+				array(
+					'step_definition' => array(
+						'step'    => 'setSiteOptions',
+						'options' => array(
+							'large_setting' => $large_value,
+						),
+					),
 				)
 			)
-		)));
+		);

-		$response = $this->rest_api->import_step($request);
+		$response = $this->rest_api->import_step( $request );

-		$this->assertFalse($response['success']);
-		$this->assertCount(1, $response['messages']);
-		$this->assertEquals('error', $response['messages'][0]['type']);
-		$this->assertStringContainsString('50 MB', $response['messages'][0]['message']);
+		$this->assertFalse( $response['success'] );
+		$this->assertCount( 1, $response['messages'] );
+		$this->assertEquals( 'error', $response['messages'][0]['type'] );
+		$this->assertStringContainsString( '50 MB', $response['messages'][0]['message'] );
 	}

 	/**
-	 * Clean up after each test.
+	 * Test that import blueprint endpoint is working.
 	 */
-	public function tearDown(): void {
-		parent::tearDown();
-		// Clean up global state
-		unset($_FILES['file']);
+	public function test_import_blueprint() {
+		add_filter(
+			'pre_option_woocommerce_coming_soon',
+			function () {
+				return 'yes';
+			}
+		);

-		// Clean up temporary file
-		if (file_exists($this->temp_file)) {
-			unlink($this->temp_file);
-		}
+		$request = new \WP_REST_Request( 'POST', '/wc-admin/blueprint/import-step' );
+		$request->set_body(
+			wp_json_encode(
+				array(
+					'step_definition' => array(
+						'step'    => 'setSiteOptions',
+						'options' => array(
+							'woocommerce_store_address' => '123 Test St',
+						),
+					),
+				)
+			)
+		);
+		$request->set_header( 'Content-Type', 'application/json' );
+
+		$response = $this->rest_api->import_step( $request );
+
+		$this->assertTrue( $response['success'], $response['messages'][0]['message'] );
+		$this->assertCount( 1, $response['messages'] );
+		$this->assertStringContainsString( 'woocommerce_store_address has been updated', $response['messages'][0]['message'] );
 	}
-}
\ No newline at end of file
+}