From 171846fd4ea36ce5391da6b0203ec1193016c6b6 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Tue, 28 Jul 2026 22:34:48 +0200 Subject: [PATCH 1/5] db import: Pass import file via STDIN redirection Previously, `wp db import ` built a MySQL `--execute` string by interpolating the import filename into `SOURCE %s;`. Because MySQL's `--execute` flag processes client-side directives, filenames with special characters could cause syntax errors or unintended client parsing behavior. This change modifies `wp db import` to feed the SQL dump file directly to the `mysql` client via standard input redirection (` < 'filename'`). Any bulk import session optimizations (`SET unique_checks = 0; SET foreign_key_checks = 0;`) are now passed via `--init-command`, matching how session SQL modes are handled. --- features/db-import.feature | 16 ++++++++++++++++ src/DB_Command.php | 20 ++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/features/db-import.feature b/features/db-import.feature index a650517d..8b890c24 100644 --- a/features/db-import.feature +++ b/features/db-import.feature @@ -358,3 +358,19 @@ Feature: Import a WordPress database """ Success: Imported from 'zerodate_compose.sql'. """ + + @require-mysql-or-mariadb + Scenario: `wp db import` handles filenames containing semicolons and client directives + Given a WP install + And a "prefix.sql; \! touch side_effect.sql #.sql" file: + """ + CREATE TABLE wp_cli_meta_test (id int NOT NULL); + INSERT INTO wp_cli_meta_test (id) VALUES (42); + """ + + When I run `wp db import "prefix.sql; \! touch side_effect.sql #.sql"` + Then STDOUT should contain: + """ + Success: Imported from 'prefix.sql; \! touch side_effect.sql #.sql'. + """ + And the side_effect.sql file should not exist diff --git a/src/DB_Command.php b/src/DB_Command.php index 123122ac..683cb1d4 100644 --- a/src/DB_Command.php +++ b/src/DB_Command.php @@ -951,21 +951,29 @@ public function import( $args, $assoc_args ) { if ( ! is_readable( $result_file ) ) { WP_CLI::error( sprintf( 'Import file missing or not readable: %s', $result_file ) ); } - - $query = Utils\get_flag_value( $assoc_args, 'skip-optimization' ) - ? 'SOURCE %s;' - : 'SET autocommit = 0; SET unique_checks = 0; SET foreign_key_checks = 0; SOURCE %s; COMMIT;'; - - $mysql_args['execute'] = sprintf( $query, $result_file ); } else { $result_file = 'STDIN'; } + if ( ! Utils\get_flag_value( $assoc_args, 'skip-optimization' ) ) { + $optimization_sql = 'SET unique_checks = 0; SET foreign_key_checks = 0;'; + if ( isset( $mysql_args['init-command'] ) && '' !== trim( (string) $mysql_args['init-command'] ) ) { + $mysql_args['init-command'] .= '; ' . $optimization_sql; + } else { + $mysql_args['init-command'] = $optimization_sql; + } + } + $command = sprintf( '%s%s --no-auto-rehash', Utils\get_mysql_binary_path(), $this->get_defaults_flag_string( $assoc_args ) ); + + if ( 'STDIN' !== $result_file ) { + $command .= ' < ' . escapeshellarg( $result_file ); + } + WP_CLI::debug( "Running shell command: {$command}", 'db' ); WP_CLI::debug( 'Associative arguments: ' . json_encode( $assoc_args ), 'db' ); From e2d56c5fadd0862256a37d3136ddddabacced84b Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 29 Jul 2026 11:54:57 +0200 Subject: [PATCH 2/5] Add another test --- features/db-import.feature | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/features/db-import.feature b/features/db-import.feature index 8b890c24..840a70b6 100644 --- a/features/db-import.feature +++ b/features/db-import.feature @@ -374,3 +374,17 @@ Feature: Import a WordPress database Success: Imported from 'prefix.sql; \! touch side_effect.sql #.sql'. """ And the side_effect.sql file should not exist + + @require-mysql-or-mariadb + Scenario: `wp db import` does not execute embedded client meta-commands in dump content + Given a WP install + And a dump_with_command_injection.sql file: + """ + CREATE TABLE wp_cli_meta_content_test (id int NOT NULL); + INSERT INTO wp_cli_meta_content_test (id) VALUES (99); + \! touch side_effect_content.sql + """ + + When I try `wp db import dump_with_command_injection.sql` + Then the side_effect_content.sql file should not exist + From 6012d5dc6a280a3ab4bfc1cca55db8e65721c5a4 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 3 Aug 2026 10:18:59 +0200 Subject: [PATCH 3/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/DB_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DB_Command.php b/src/DB_Command.php index 683cb1d4..8531579a 100644 --- a/src/DB_Command.php +++ b/src/DB_Command.php @@ -970,7 +970,7 @@ public function import( $args, $assoc_args ) { $this->get_defaults_flag_string( $assoc_args ) ); - if ( 'STDIN' !== $result_file ) { + if ( '-' !== ( $args[0] ?? '' ) ) { $command .= ' < ' . escapeshellarg( $result_file ); } From 7da35449566c6f50d02fd25b2dcb6f8853a99693 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 3 Aug 2026 12:27:06 +0200 Subject: [PATCH 4/5] Address code review feedback and fix failing import tests --- features/db-import.feature | 6 +++--- src/DB_Command.php | 11 ++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/features/db-import.feature b/features/db-import.feature index ff962ee3..3774d124 100644 --- a/features/db-import.feature +++ b/features/db-import.feature @@ -378,16 +378,16 @@ Feature: Import a WordPress database @require-mysql-or-mariadb Scenario: `wp db import` handles filenames containing semicolons and client directives Given a WP install - And a "prefix.sql; \! touch side_effect.sql #.sql" file: + And a prefix.sql;\!touch_side_effect.sql#.sql file: """ CREATE TABLE wp_cli_meta_test (id int NOT NULL); INSERT INTO wp_cli_meta_test (id) VALUES (42); """ - When I run `wp db import "prefix.sql; \! touch side_effect.sql #.sql"` + When I run `wp db import 'prefix.sql;\!touch_side_effect.sql#.sql'` Then STDOUT should contain: """ - Success: Imported from 'prefix.sql; \! touch side_effect.sql #.sql'. + Success: Imported from 'prefix.sql;\!touch_side_effect.sql#.sql'. """ And the side_effect.sql file should not exist diff --git a/src/DB_Command.php b/src/DB_Command.php index 1d61ef21..6b429dac 100644 --- a/src/DB_Command.php +++ b/src/DB_Command.php @@ -943,8 +943,10 @@ private function command_supports_option( $command, $option ) { public function import( $args, $assoc_args ) { $this->maybe_load_sqlite_dropin(); + $is_stdin = false; if ( ! empty( $args[0] ) ) { $result_file = $args[0]; + $is_stdin = '-' === $result_file; } else { $result_file = sprintf( '%s.sql', DB_NAME ); } @@ -956,7 +958,10 @@ public function import( $args, $assoc_args ) { // Process options to MySQL. $mysql_args = array_merge( - [ 'database' => DB_NAME ], + [ + 'database' => DB_NAME, + 'binary-mode' => true, + ], self::get_dbuser_dbpass_args( $assoc_args ), self::get_mysql_args( $assoc_args ) ); @@ -967,7 +972,7 @@ public function import( $args, $assoc_args ) { // no separate probe connection. $this->apply_sql_mode_compat_init_command( $mysql_args, $assoc_args ); - if ( '-' !== $result_file ) { + if ( ! $is_stdin ) { if ( ! is_readable( $result_file ) ) { WP_CLI::error( sprintf( 'Import file missing or not readable: %s', $result_file ) ); } @@ -990,7 +995,7 @@ public function import( $args, $assoc_args ) { $this->get_defaults_flag_string( $assoc_args ) ); - if ( '-' !== ( $args[0] ?? '' ) ) { + if ( ! $is_stdin ) { $command .= ' < ' . escapeshellarg( $result_file ); } From bdb14500c294cc24e721e40a10e6b4bc6486cf81 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 3 Aug 2026 12:38:47 +0200 Subject: [PATCH 5/5] Update --skip-optimization doc block and verify import query result in Behat test --- features/db-import.feature | 6 ++++++ src/DB_Command.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/features/db-import.feature b/features/db-import.feature index 3774d124..b1d4ce07 100644 --- a/features/db-import.feature +++ b/features/db-import.feature @@ -391,6 +391,12 @@ Feature: Import a WordPress database """ And the side_effect.sql file should not exist + When I run `wp db query 'SELECT id FROM wp_cli_meta_test;' --skip-column-names` + Then STDOUT should be: + """ + 42 + """ + @require-mysql-or-mariadb Scenario: `wp db import` does not execute embedded client meta-commands in dump content Given a WP install diff --git a/src/DB_Command.php b/src/DB_Command.php index 6b429dac..be9f14fc 100644 --- a/src/DB_Command.php +++ b/src/DB_Command.php @@ -926,7 +926,7 @@ private function command_supports_option( $command, $option ) { * : Extra arguments to pass to mysql. [Refer to mysql binary docs](https://dev.mysql.com/doc/refman/8.0/en/mysql-command-options.html). * * [--skip-optimization] - * : When using an SQL file, do not include speed optimization such as disabling auto-commit and key checks. + * : Do not disable unique checks and foreign key checks during import. * * [--skip-sql-mode-compat] * : Do not adapt the session SQL mode for WordPress compatibility. By default, `wp db import` strips the SQL modes that WordPress Core disables (such as `STRICT_TRANS_TABLES` and `NO_ZERO_DATE`) so that dumps containing legacy values like `0000-00-00` import cleanly. Pass this flag to import under the server's own SQL modes instead.