From e35f9baf35eea2dac0e92df821e5b6f80e56b455 Mon Sep 17 00:00:00 2001 From: vuckro Date: Wed, 10 Jun 2026 14:28:14 +0200 Subject: [PATCH] fix(security): DNS modal ownership check + Forge deploy-command domain validation - Domain_Mapping_Element::render_add_dns_record_modal() rendered the DNS record form for any domain id with no ownership check, while its siblings (render_edit_dns_record_modal, handle_add_dns_record) all gate on customer_can_manage_dns(). Add the same gate so a customer cannot view the DNS form for a domain they don't own. - Laravel_Forge_Integration::get_deploy_command() interpolated the mapped domain into the custom deploy command branch with no validation, while the symlink branch validates the domain format. Apply the same hostname-format check before building the command to prevent shell metacharacter injection. Co-Authored-By: Claude Fable 5 --- .../class-laravel-forge-integration.php | 12 ++++++++++++ inc/ui/class-domain-mapping-element.php | 9 ++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/inc/integrations/providers/laravel-forge/class-laravel-forge-integration.php b/inc/integrations/providers/laravel-forge/class-laravel-forge-integration.php index f7d2021f3..877db524a 100644 --- a/inc/integrations/providers/laravel-forge/class-laravel-forge-integration.php +++ b/inc/integrations/providers/laravel-forge/class-laravel-forge-integration.php @@ -265,6 +265,18 @@ public function get_deploy_command(string $domain): string { $symlink_target = $this->get_credential('WU_FORGE_SYMLINK_TARGET'); if ($deploy_command) { + // Validate domain to prevent shell command injection via metacharacters, + // mirroring the symlink branch below. + if (! preg_match('/^[a-z0-9][a-z0-9\-\.]*[a-z0-9]$/i', $domain)) { + wu_log_add( + 'integration-forge', + sprintf('Invalid domain format rejected for shell command: %s', $domain), + \Psr\Log\LogLevel::ERROR + ); + + return ''; + } + return str_replace('{domain}', $domain, $deploy_command); } diff --git a/inc/ui/class-domain-mapping-element.php b/inc/ui/class-domain-mapping-element.php index d4e3dfb99..84ba65e38 100644 --- a/inc/ui/class-domain-mapping-element.php +++ b/inc/ui/class-domain-mapping-element.php @@ -781,7 +781,14 @@ public function render_add_dns_record_modal(): void { } $dns_manager = \WP_Ultimo\Managers\DNS_Record_Manager::get_instance(); - $provider = $dns_manager->get_dns_provider(); + + // Same ownership gate the edit/add/delete DNS handlers enforce. + if (! $dns_manager->customer_can_manage_dns(get_current_user_id(), $domain->get_domain())) { + wp_send_json_error(new \WP_Error('permission-denied', __('You do not have permission to manage DNS for this domain.', 'ultimate-multisite'))); + return; + } + + $provider = $dns_manager->get_dns_provider(); wu_get_template( 'domain/dns-record-form',