From fc849c2aa785dab4503985348e85a2f160d11e32 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Wed, 29 Jul 2026 10:33:45 +0200 Subject: [PATCH 1/4] Validate site slug and site URL in site create and generate commands --- features/site-create.feature | 21 +++++++++++++++++++++ src/Site_Command.php | 18 ++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/features/site-create.feature b/features/site-create.feature index 11bfc6741..388e7915b 100644 --- a/features/site-create.feature +++ b/features/site-create.feature @@ -250,3 +250,24 @@ Feature: Create a new site on a WP multisite | blog_id | url | | 1 | https://example.com/ | | 2 | http://testsite.example.com/ | + + Scenario: Error when invalid slug containing special characters is provided + Given a WP multisite install + + When I try `wp site create --slug='x$(touch /tmp/wpcli_poc)'` + Then STDERR should be: + """ + Error: Slug may only contain letters, numbers, and dashes. + """ + And the return code should be 1 + + Scenario: Error when invalid domain format is provided in site-url + Given a WP multisite install + + When I try `wp site create --site-url='http://invalid$domain.com/site'` + Then STDERR should be: + """ + Error: Invalid domain format in --site-url. + """ + And the return code should be 1 + diff --git a/src/Site_Command.php b/src/Site_Command.php index 472000c9e..bd0189696 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -592,6 +592,14 @@ public function create( $args, $assoc_args ) { $custom_domain = sanitize_text_field( $parsed_url['host'] ); $custom_path = isset( $parsed_url['path'] ) ? sanitize_text_field( '/' . ltrim( $parsed_url['path'], '/' ) ) : '/'; + if ( ! preg_match( '|^[a-zA-Z0-9.-]+$|', $custom_domain ) ) { + WP_CLI::error( 'Invalid domain format in --site-url.' ); + } + + if ( ! preg_match( '|^[a-zA-Z0-9/_.-]+$|', $custom_path ) ) { + WP_CLI::error( 'Invalid path format in --site-url.' ); + } + // Ensure path ends with / if ( '/' !== substr( $custom_path, -1 ) ) { $custom_path .= '/'; @@ -651,9 +659,10 @@ public function create( $args, $assoc_args ) { $public = ! Utils\get_flag_value( $assoc_args, 'private' ); // Sanitize - if ( preg_match( '|^([a-zA-Z0-9-])+$|', $base ) ) { - $base = strtolower( $base ); + if ( ! preg_match( '|^([a-zA-Z0-9-])+$|', $base ) ) { + WP_CLI::error( 'Slug may only contain letters, numbers, and dashes.' ); } + $base = strtolower( $base ); // If not a subdomain install, make sure the domain isn't a reserved word if ( ! is_subdomain_install() ) { @@ -792,9 +801,10 @@ public function generate( $args, $assoc_args ) { // Base. $base = $assoc_args['slug']; - if ( preg_match( '|^([a-zA-Z0-9-])+$|', $base ) ) { - $base = strtolower( $base ); + if ( ! preg_match( '|^([a-zA-Z0-9-])+$|', $base ) ) { + WP_CLI::error( 'Slug may only contain letters, numbers, and dashes.' ); } + $base = strtolower( $base ); $is_subdomain_install = is_subdomain_install(); // If not a subdomain install, make sure the domain isn't a reserved word From ab707ca05a58985b311b939953655c5f626fa21f Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 3 Aug 2026 12:31:40 +0200 Subject: [PATCH 2/4] Address code review feedback --- features/site-create.feature | 21 +++++++++++++++++++++ features/site-generate.feature | 11 +++++++++++ src/Site_Command.php | 15 ++++++++++++--- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/features/site-create.feature b/features/site-create.feature index 388e7915b..4335615c1 100644 --- a/features/site-create.feature +++ b/features/site-create.feature @@ -271,3 +271,24 @@ Feature: Create a new site on a WP multisite """ And the return code should be 1 + Scenario: Error when malformed domain with double dots is provided in site-url + Given a WP multisite install + + When I try `wp site create --site-url='http://example..com/site'` + Then STDERR should be: + """ + Error: Invalid domain format in --site-url. + """ + And the return code should be 1 + + Scenario: Error when invalid path format is provided in site-url + Given a WP multisite install + + When I try `wp site create --site-url='http://example.com/invalid_path'` + Then STDERR should be: + """ + Error: Invalid path format in --site-url. + """ + And the return code should be 1 + + diff --git a/features/site-generate.feature b/features/site-generate.feature index 4f053420f..a58999008 100644 --- a/features/site-generate.feature +++ b/features/site-generate.feature @@ -105,3 +105,14 @@ Feature: Generate new WordPress sites """ And STDOUT should be empty And the return code should be 1 + + Scenario: Error when invalid slug containing special characters is provided + Given a WP multisite install + + When I try `wp site generate --slug='x$(touch /tmp/wpcli_poc)'` + Then STDERR should be: + """ + Error: Slug may only contain letters, numbers, and dashes. + """ + And the return code should be 1 + diff --git a/src/Site_Command.php b/src/Site_Command.php index bd0189696..5877b8d7a 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -579,7 +579,7 @@ public function create( $args, $assoc_args ) { if ( $has_site_url ) { $parsed_url = wp_parse_url( $assoc_args['site-url'] ); - if ( ! isset( $parsed_url['host'] ) ) { + if ( false === $parsed_url || ! isset( $parsed_url['host'] ) ) { WP_CLI::error( 'Invalid URL format. Please provide a valid URL (e.g., http://site.example.com).' ); } @@ -592,11 +592,20 @@ public function create( $args, $assoc_args ) { $custom_domain = sanitize_text_field( $parsed_url['host'] ); $custom_path = isset( $parsed_url['path'] ) ? sanitize_text_field( '/' . ltrim( $parsed_url['path'], '/' ) ) : '/'; - if ( ! preg_match( '|^[a-zA-Z0-9.-]+$|', $custom_domain ) ) { + $domain_parts = explode( '.', $custom_domain ); + $valid_domain = true; + foreach ( $domain_parts as $part ) { + if ( '' === $part || ! preg_match( '/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$/', $part ) ) { + $valid_domain = false; + break; + } + } + + if ( ! $valid_domain ) { WP_CLI::error( 'Invalid domain format in --site-url.' ); } - if ( ! preg_match( '|^[a-zA-Z0-9/_.-]+$|', $custom_path ) ) { + if ( ! preg_match( '|^[a-zA-Z0-9/-]+$|', $custom_path ) || false !== strpos( $custom_path, '//' ) ) { WP_CLI::error( 'Invalid path format in --site-url.' ); } From 22e146bfb8007b1cc992cde713f174b52f76ccf4 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 3 Aug 2026 14:37:03 +0200 Subject: [PATCH 3/4] Address additional code review feedback --- features/site-create.feature | 11 +++++++++++ src/Site_Command.php | 14 ++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/features/site-create.feature b/features/site-create.feature index 4335615c1..7a6921177 100644 --- a/features/site-create.feature +++ b/features/site-create.feature @@ -291,4 +291,15 @@ Feature: Create a new site on a WP multisite """ And the return code should be 1 + Scenario: Error when duplicate slashes are provided in site-url path + Given a WP multisite install + + When I try `wp site create --site-url='http://example.com//foo'` + Then STDERR should be: + """ + Error: Invalid path format in --site-url. + """ + And the return code should be 1 + + diff --git a/src/Site_Command.php b/src/Site_Command.php index 5877b8d7a..5e969ee37 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -579,7 +579,7 @@ public function create( $args, $assoc_args ) { if ( $has_site_url ) { $parsed_url = wp_parse_url( $assoc_args['site-url'] ); - if ( false === $parsed_url || ! isset( $parsed_url['host'] ) ) { + if ( ! is_array( $parsed_url ) || ! isset( $parsed_url['host'] ) ) { WP_CLI::error( 'Invalid URL format. Please provide a valid URL (e.g., http://site.example.com).' ); } @@ -589,8 +589,9 @@ public function create( $args, $assoc_args ) { } // Sanitize domain and path + $raw_path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '/'; $custom_domain = sanitize_text_field( $parsed_url['host'] ); - $custom_path = isset( $parsed_url['path'] ) ? sanitize_text_field( '/' . ltrim( $parsed_url['path'], '/' ) ) : '/'; + $custom_path = sanitize_text_field( '/' . ltrim( $raw_path, '/' ) ); $domain_parts = explode( '.', $custom_domain ); $valid_domain = true; @@ -605,7 +606,7 @@ public function create( $args, $assoc_args ) { WP_CLI::error( 'Invalid domain format in --site-url.' ); } - if ( ! preg_match( '|^[a-zA-Z0-9/-]+$|', $custom_path ) || false !== strpos( $custom_path, '//' ) ) { + if ( ! preg_match( '|^[a-zA-Z0-9/-]+$|', $custom_path ) || false !== strpos( $raw_path, '//' ) ) { WP_CLI::error( 'Invalid path format in --site-url.' ); } @@ -630,7 +631,12 @@ public function create( $args, $assoc_args ) { $base = strtolower( $base ); } else { // For subdirectory installs, derive slug from the last part of the path. - $path_parts = array_filter( explode( '/', trim( $custom_path, '/' ) ) ); + $path_parts = array_filter( + explode( '/', trim( $custom_path, '/' ) ), + function ( $part ) { + return '' !== $part; + } + ); $base = (string) array_pop( $path_parts ); // If base is empty (root path), require explicit slug. From 9c0ad0fcbabf8b84b7e0a5fa64fe8224ef1f8f60 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Mon, 3 Aug 2026 15:20:39 +0200 Subject: [PATCH 4/4] Strict URL parsing and slug anchor validation --- features/site-create.feature | 11 +++++++++++ src/Site_Command.php | 14 ++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/features/site-create.feature b/features/site-create.feature index 7a6921177..6c6c11ccd 100644 --- a/features/site-create.feature +++ b/features/site-create.feature @@ -301,5 +301,16 @@ Feature: Create a new site on a WP multisite """ And the return code should be 1 + Scenario: Error when unsupported components are provided in site-url + Given a WP multisite install + + When I try `wp site create --site-url='http://example.com:8080/site'` + Then STDERR should be: + """ + Error: Invalid URL format. User credentials, ports, query parameters, and fragments are not supported in --site-url. + """ + And the return code should be 1 + + diff --git a/src/Site_Command.php b/src/Site_Command.php index 5e969ee37..f7735435a 100644 --- a/src/Site_Command.php +++ b/src/Site_Command.php @@ -579,7 +579,7 @@ public function create( $args, $assoc_args ) { if ( $has_site_url ) { $parsed_url = wp_parse_url( $assoc_args['site-url'] ); - if ( ! is_array( $parsed_url ) || ! isset( $parsed_url['host'] ) ) { + if ( ! is_array( $parsed_url ) || ! isset( $parsed_url['host'] ) || ! is_string( $parsed_url['host'] ) ) { WP_CLI::error( 'Invalid URL format. Please provide a valid URL (e.g., http://site.example.com).' ); } @@ -588,8 +588,14 @@ public function create( $args, $assoc_args ) { WP_CLI::error( 'Invalid URL scheme. Only http and https schemes are supported.' ); } + // Reject unsupported URL components (user, pass, port, query, fragment) + $unsupported = array_intersect_key( $parsed_url, array_flip( [ 'user', 'pass', 'port', 'query', 'fragment' ] ) ); + if ( ! empty( $unsupported ) ) { + WP_CLI::error( 'Invalid URL format. User credentials, ports, query parameters, and fragments are not supported in --site-url.' ); + } + // Sanitize domain and path - $raw_path = isset( $parsed_url['path'] ) ? $parsed_url['path'] : '/'; + $raw_path = isset( $parsed_url['path'] ) && is_string( $parsed_url['path'] ) ? $parsed_url['path'] : '/'; $custom_domain = sanitize_text_field( $parsed_url['host'] ); $custom_path = sanitize_text_field( '/' . ltrim( $raw_path, '/' ) ); @@ -674,7 +680,7 @@ function ( $part ) { $public = ! Utils\get_flag_value( $assoc_args, 'private' ); // Sanitize - if ( ! preg_match( '|^([a-zA-Z0-9-])+$|', $base ) ) { + if ( ! preg_match( '|^([a-zA-Z0-9-])+$|D', $base ) ) { WP_CLI::error( 'Slug may only contain letters, numbers, and dashes.' ); } $base = strtolower( $base ); @@ -816,7 +822,7 @@ public function generate( $args, $assoc_args ) { // Base. $base = $assoc_args['slug']; - if ( ! preg_match( '|^([a-zA-Z0-9-])+$|', $base ) ) { + if ( ! preg_match( '|^([a-zA-Z0-9-])+$|D', $base ) ) { WP_CLI::error( 'Slug may only contain letters, numbers, and dashes.' ); } $base = strtolower( $base );