diff --git a/app/Livewire/Server/New/ByHetzner.php b/app/Livewire/Server/New/ByHetzner.php index c5559576d..6f7e915c9 100644 --- a/app/Livewire/Server/New/ByHetzner.php +++ b/app/Livewire/Server/New/ByHetzner.php @@ -7,6 +7,7 @@ use App\Models\PrivateKey; use App\Models\Server; use App\Models\Team; +use App\Rules\ValidHostname; use App\Services\HetznerService; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Support\Collection; @@ -104,7 +105,7 @@ protected function rules(): array if ($this->current_step === 2) { $rules = array_merge($rules, [ - 'server_name' => 'required|string|max:255', + 'server_name' => ['required', 'string', 'max:253', new ValidHostname], 'selected_location' => 'required|string', 'selected_image' => 'required|integer', 'selected_server_type' => 'required|string', @@ -361,9 +362,12 @@ private function createHetznerServer(string $token): array ray('Uploaded new SSH key', ['ssh_key_id' => $sshKeyId, 'name' => $sshKeyName]); } + // Normalize server name to lowercase for RFC 1123 compliance + $normalizedServerName = strtolower(trim($this->server_name)); + // Prepare server creation parameters $params = [ - 'name' => $this->server_name, + 'name' => $normalizedServerName, 'server_type' => $this->selected_server_type, 'image' => $this->selected_image, 'location' => $this->selected_location, diff --git a/app/Rules/ValidHostname.php b/app/Rules/ValidHostname.php new file mode 100644 index 000000000..b6b2b8d32 --- /dev/null +++ b/app/Rules/ValidHostname.php @@ -0,0 +1,114 @@ + 253) { + $fail('The :attribute must not exceed 253 characters.'); + + return; + } + + // Check for dangerous shell metacharacters + $dangerousChars = [ + ';', '|', '&', '$', '`', '(', ')', '{', '}', + '<', '>', '\n', '\r', '\0', '"', "'", '\\', + '!', '*', '?', '[', ']', '~', '^', ':', '#', + '@', '%', '=', '+', ',', ' ', + ]; + + foreach ($dangerousChars as $char) { + if (str_contains($hostname, $char)) { + try { + $logData = [ + 'hostname' => $hostname, + 'character' => $char, + ]; + + if (function_exists('request') && app()->has('request')) { + $logData['ip'] = request()->ip(); + } + + if (function_exists('auth') && app()->has('auth')) { + $logData['user_id'] = auth()->id(); + } + + Log::warning('Hostname validation failed - dangerous character', $logData); + } catch (\Throwable $e) { + // Ignore errors when facades are not available (e.g., in unit tests) + } + + $fail('The :attribute contains invalid characters. Only lowercase letters (a-z), numbers (0-9), hyphens (-), and dots (.) are allowed.'); + + return; + } + } + + // Additional validation: hostname should not start or end with a dot + if (str_starts_with($hostname, '.') || str_ends_with($hostname, '.')) { + $fail('The :attribute cannot start or end with a dot.'); + + return; + } + + // Check for consecutive dots + if (str_contains($hostname, '..')) { + $fail('The :attribute cannot contain consecutive dots.'); + + return; + } + + // Split into labels (segments between dots) + $labels = explode('.', $hostname); + + foreach ($labels as $label) { + // Check label length (RFC 1123: max 63 characters per label) + if (strlen($label) < 1 || strlen($label) > 63) { + $fail('The :attribute contains an invalid label. Each segment must be 1-63 characters.'); + + return; + } + + // Check if label starts or ends with hyphen + if (str_starts_with($label, '-') || str_ends_with($label, '-')) { + $fail('The :attribute contains an invalid label. Labels cannot start or end with a hyphen.'); + + return; + } + + // Check if label contains only valid characters (lowercase letters, digits, hyphens) + if (! preg_match('/^[a-z0-9-]+$/', $label)) { + $fail('The :attribute contains invalid characters. Only lowercase letters (a-z), numbers (0-9), hyphens (-), and dots (.) are allowed.'); + + return; + } + + // RFC 1123 allows labels to be all numeric (unlike RFC 952) + // So we don't need to check for all-numeric labels + } + } +} diff --git a/resources/views/livewire/server/create.blade.php b/resources/views/livewire/server/create.blade.php index 394b55a4b..0f178bd34 100644 --- a/resources/views/livewire/server/create.blade.php +++ b/resources/views/livewire/server/create.blade.php @@ -2,20 +2,25 @@