diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index dcf0e5fbe..728ddfff5 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -15,6 +15,8 @@ use App\Models\PrivateKey; use App\Models\Project; use App\Models\Server; use App\Models\Service; +use App\Rules\ValidGitBranch; +use App\Rules\ValidGitRepositoryUrl; use Illuminate\Http\Request; use Illuminate\Validation\Rule; use OpenApi\Attributes as OA; @@ -831,8 +833,8 @@ class ApplicationsController extends Controller $destination = $destinations->first(); if ($type === 'public') { $validationRules = [ - 'git_repository' => 'string|required', - 'git_branch' => 'string|required', + 'git_repository' => ['string', 'required', new ValidGitRepositoryUrl], + 'git_branch' => ['string', 'required', new ValidGitBranch], 'build_pack' => ['required', Rule::enum(BuildPackTypes::class)], 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required', 'docker_compose_location' => 'string', @@ -883,7 +885,7 @@ class ApplicationsController extends Controller $application->source_type = GithubApp::class; $application->source_id = GithubApp::find(0)->id; } - $application->git_repository = $repository_url_parsed->getSegment(1).'/'.$repository_url_parsed->getSegment(2); + $application->git_repository = str($repository_url_parsed->getSegment(1).'/'.$repository_url_parsed->getSegment(2))->trim()->toString(); $application->fqdn = $fqdn; $application->destination_id = $destination->id; $application->destination_type = $destination->getMorphClass(); @@ -935,7 +937,7 @@ class ApplicationsController extends Controller } elseif ($type === 'private-gh-app') { $validationRules = [ 'git_repository' => 'string|required', - 'git_branch' => 'string|required', + 'git_branch' => ['string', 'required', new ValidGitBranch], 'build_pack' => ['required', Rule::enum(BuildPackTypes::class)], 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required', 'github_app_uuid' => 'string|required', @@ -1043,7 +1045,7 @@ class ApplicationsController extends Controller $application->docker_compose_domains = $dockerComposeDomainsJson; } $application->fqdn = $fqdn; - $application->git_repository = $gitRepository; + $application->git_repository = str($gitRepository)->trim()->toString(); $application->destination_id = $destination->id; $application->destination_type = $destination->getMorphClass(); $application->environment_id = $environment->id; @@ -1090,8 +1092,8 @@ class ApplicationsController extends Controller } elseif ($type === 'private-deploy-key') { $validationRules = [ - 'git_repository' => 'string|required', - 'git_branch' => 'string|required', + 'git_repository' => ['string', 'required', new ValidGitRepositoryUrl], + 'git_branch' => ['string', 'required', new ValidGitBranch], 'build_pack' => ['required', Rule::enum(BuildPackTypes::class)], 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required', 'private_key_uuid' => 'string|required', diff --git a/app/Livewire/Project/New/GithubPrivateRepository.php b/app/Livewire/Project/New/GithubPrivateRepository.php index b1b0aef15..a7aaa94a4 100644 --- a/app/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Livewire/Project/New/GithubPrivateRepository.php @@ -7,6 +7,7 @@ use App\Models\GithubApp; use App\Models\Project; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; +use App\Rules\ValidGitBranch; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Route; use Livewire\Component; @@ -155,6 +156,21 @@ class GithubPrivateRepository extends Component public function submit() { try { + // Validate git repository parts and branch + $validator = validator([ + 'selected_repository_owner' => $this->selected_repository_owner, + 'selected_repository_repo' => $this->selected_repository_repo, + 'selected_branch_name' => $this->selected_branch_name, + ], [ + 'selected_repository_owner' => 'required|string|regex:/^[a-zA-Z0-9\-_]+$/', + 'selected_repository_repo' => 'required|string|regex:/^[a-zA-Z0-9\-_\.]+$/', + 'selected_branch_name' => ['required', 'string', new ValidGitBranch], + ]); + + if ($validator->fails()) { + throw new \RuntimeException('Invalid repository data: '.$validator->errors()->first()); + } + $destination_uuid = $this->query['destination']; $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); if (! $destination) { @@ -171,8 +187,8 @@ class GithubPrivateRepository extends Component $application = Application::create([ 'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name), 'repository_project_id' => $this->selected_repository_id, - 'git_repository' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}", - 'git_branch' => $this->selected_branch_name, + 'git_repository' => str($this->selected_repository_owner)->trim()->toString().'/'.str($this->selected_repository_repo)->trim()->toString(), + 'git_branch' => str($this->selected_branch_name)->trim()->toString(), 'build_pack' => $this->build_pack, 'ports_exposes' => $this->port, 'publish_directory' => $this->publish_directory, diff --git a/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php b/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php index 01b0c9ae8..d76f7baaa 100644 --- a/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php +++ b/app/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php @@ -9,6 +9,8 @@ use App\Models\PrivateKey; use App\Models\Project; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; +use App\Rules\ValidGitBranch; +use App\Rules\ValidGitRepositoryUrl; use Illuminate\Support\Str; use Livewire\Component; use Spatie\Url\Url; @@ -53,17 +55,29 @@ class GithubPrivateRepositoryDeployKey extends Component private ?string $git_host = null; - private string $git_repository; + private ?string $git_repository = null; protected $rules = [ - 'repository_url' => 'required', - 'branch' => 'required|string', + 'repository_url' => ['required', 'string'], + 'branch' => ['required', 'string'], 'port' => 'required|numeric', 'is_static' => 'required|boolean', 'publish_directory' => 'nullable|string', 'build_pack' => 'required|string', ]; + protected function rules() + { + return [ + 'repository_url' => ['required', 'string', new ValidGitRepositoryUrl], + 'branch' => ['required', 'string', new ValidGitBranch], + 'port' => 'required|numeric', + 'is_static' => 'required|boolean', + 'publish_directory' => 'nullable|string', + 'build_pack' => 'required|string', + ]; + } + protected $validationAttributes = [ 'repository_url' => 'Repository', 'branch' => 'Branch', @@ -135,6 +149,9 @@ class GithubPrivateRepositoryDeployKey extends Component $this->get_git_source(); + // Note: git_repository has already been validated and transformed in get_git_source() + // It may now be in SSH format (git@host:repo.git) which is valid for deploy keys + $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); if ($this->git_source === 'other') { @@ -194,6 +211,15 @@ class GithubPrivateRepositoryDeployKey extends Component private function get_git_source() { + // Validate repository URL before parsing + $validator = validator(['repository_url' => $this->repository_url], [ + 'repository_url' => ['required', 'string', new ValidGitRepositoryUrl], + ]); + + if ($validator->fails()) { + throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('repository_url')); + } + $this->repository_url_parsed = Url::fromString($this->repository_url); $this->git_host = $this->repository_url_parsed->getHost(); $this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2); @@ -206,8 +232,10 @@ class GithubPrivateRepositoryDeployKey extends Component if (str($this->repository_url)->startsWith('http')) { $this->git_host = $this->repository_url_parsed->getHost(); $this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2); + // Convert to SSH format for deploy key usage $this->git_repository = Str::finish("git@$this->git_host:$this->git_repository", '.git'); } else { + // If it's already in SSH format, just use it as-is $this->git_repository = $this->repository_url; } $this->git_source = 'other'; diff --git a/app/Livewire/Project/New/PublicGitRepository.php b/app/Livewire/Project/New/PublicGitRepository.php index 45b3b5726..8de998a96 100644 --- a/app/Livewire/Project/New/PublicGitRepository.php +++ b/app/Livewire/Project/New/PublicGitRepository.php @@ -9,6 +9,8 @@ use App\Models\Project; use App\Models\Service; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; +use App\Rules\ValidGitBranch; +use App\Rules\ValidGitRepositoryUrl; use Carbon\Carbon; use Livewire\Component; use Spatie\Url\Url; @@ -62,7 +64,7 @@ class PublicGitRepository extends Component public bool $new_compose_services = false; protected $rules = [ - 'repository_url' => 'required|url', + 'repository_url' => ['required', 'string'], 'port' => 'required|numeric', 'isStatic' => 'required|boolean', 'publish_directory' => 'nullable|string', @@ -71,6 +73,20 @@ class PublicGitRepository extends Component 'docker_compose_location' => 'nullable|string', ]; + protected function rules() + { + return [ + 'repository_url' => ['required', 'string', new ValidGitRepositoryUrl], + 'port' => 'required|numeric', + 'isStatic' => 'required|boolean', + 'publish_directory' => 'nullable|string', + 'build_pack' => 'required|string', + 'base_directory' => 'nullable|string', + 'docker_compose_location' => 'nullable|string', + 'git_branch' => ['required', 'string', new ValidGitBranch], + ]; + } + protected $validationAttributes = [ 'repository_url' => 'repository', 'port' => 'port', @@ -141,6 +157,15 @@ class PublicGitRepository extends Component public function loadBranch() { try { + // Validate repository URL + $validator = validator(['repository_url' => $this->repository_url], [ + 'repository_url' => ['required', 'string', new ValidGitRepositoryUrl], + ]); + + if ($validator->fails()) { + throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('repository_url')); + } + if (str($this->repository_url)->startsWith('git@')) { $github_instance = str($this->repository_url)->after('git@')->before(':'); $repository = str($this->repository_url)->after(':')->before('.git'); @@ -191,6 +216,15 @@ class PublicGitRepository extends Component $this->git_branch = 'main'; $this->base_directory = '/'; + // Validate repository URL before parsing + $validator = validator(['repository_url' => $this->repository_url], [ + 'repository_url' => ['required', 'string', new ValidGitRepositoryUrl], + ]); + + if ($validator->fails()) { + throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('repository_url')); + } + $this->repository_url_parsed = Url::fromString($this->repository_url); $this->git_host = $this->repository_url_parsed->getHost(); $this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2); @@ -234,6 +268,27 @@ class PublicGitRepository extends Component { try { $this->validate(); + + // Additional validation for git repository and branch + if ($this->git_source === 'other') { + // For 'other' sources, git_repository contains the full URL + $validator = validator(['git_repository' => $this->git_repository], [ + 'git_repository' => ['required', 'string', new ValidGitRepositoryUrl], + ]); + + if ($validator->fails()) { + throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('git_repository')); + } + } + + $branchValidator = validator(['git_branch' => $this->git_branch], [ + 'git_branch' => ['required', 'string', new ValidGitBranch], + ]); + + if ($branchValidator->fails()) { + throw new \RuntimeException('Invalid branch: '.$branchValidator->errors()->first('git_branch')); + } + $destination_uuid = $this->query['destination']; $project_uuid = $this->parameters['project_uuid']; $environment_uuid = $this->parameters['environment_uuid']; diff --git a/app/Models/Application.php b/app/Models/Application.php index c2fec427e..f8f86d1f9 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1128,15 +1128,20 @@ class Application extends BaseModel $branch = $this->git_branch; ['repository' => $customRepository, 'port' => $customPort] = $this->customRepository(); $baseDir = $custom_base_dir ?? $this->generateBaseDir($deployment_uuid); + + // Escape shell arguments for safety to prevent command injection + $escapedBranch = escapeshellarg($branch); + $escapedBaseDir = escapeshellarg($baseDir); + $commands = collect([]); // Check if shallow clone is enabled $isShallowCloneEnabled = $this->settings?->is_git_shallow_clone_enabled ?? false; $depthFlag = $isShallowCloneEnabled ? ' --depth=1' : ''; - $git_clone_command = "git clone{$depthFlag} -b \"{$this->git_branch}\""; + $git_clone_command = "git clone{$depthFlag} -b {$escapedBranch}"; if ($only_checkout) { - $git_clone_command = "git clone{$depthFlag} --no-checkout -b \"{$this->git_branch}\""; + $git_clone_command = "git clone{$depthFlag} --no-checkout -b {$escapedBranch}"; } if ($pull_request_id !== 0) { $pr_branch_name = "pr-{$pull_request_id}-coolify"; @@ -1150,7 +1155,8 @@ class Application extends BaseModel if ($this->source->getMorphClass() === \App\Models\GithubApp::class) { if ($this->source->is_public) { $fullRepoUrl = "{$this->source->html_url}/{$customRepository}"; - $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}"; + $escapedRepoUrl = escapeshellarg("{$this->source->html_url}/{$customRepository}"); + $git_clone_command = "{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}"; if (! $only_checkout) { $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true); } @@ -1162,11 +1168,15 @@ class Application extends BaseModel } else { $github_access_token = generateGithubInstallationToken($this->source); if ($exec_in_docker) { - $git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git {$baseDir}"; - $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; + $repoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; + $escapedRepoUrl = escapeshellarg($repoUrl); + $git_clone_command = "{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}"; + $fullRepoUrl = $repoUrl; } else { - $git_clone_command = "{$git_clone_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository} {$baseDir}"; - $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}"; + $repoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}"; + $escapedRepoUrl = escapeshellarg($repoUrl); + $git_clone_command = "{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}"; + $fullRepoUrl = $repoUrl; } if (! $only_checkout) { $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false); @@ -1181,10 +1191,11 @@ class Application extends BaseModel $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; $git_checkout_command = $this->buildGitCheckoutCommand($pr_branch_name); + $escapedPrBranch = escapeshellarg($branch); if ($exec_in_docker) { - $commands->push(executeInDocker($deployment_uuid, "cd {$baseDir} && git fetch origin {$branch} && $git_checkout_command")); + $commands->push(executeInDocker($deployment_uuid, "cd {$escapedBaseDir} && git fetch origin {$escapedPrBranch} && $git_checkout_command")); } else { - $commands->push("cd {$baseDir} && git fetch origin {$branch} && $git_checkout_command"); + $commands->push("cd {$escapedBaseDir} && git fetch origin {$escapedPrBranch} && $git_checkout_command"); } } @@ -1202,7 +1213,8 @@ class Application extends BaseModel throw new RuntimeException('Private key not found. Please add a private key to the application and try again.'); } $private_key = base64_encode($private_key); - $git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$customRepository} {$baseDir}"; + $escapedCustomRepository = escapeshellarg($customRepository); + $git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}"; if ($only_checkout) { $git_clone_command = $git_clone_command_base; } else { diff --git a/app/Rules/ValidGitBranch.php b/app/Rules/ValidGitBranch.php new file mode 100644 index 000000000..a3069f08e --- /dev/null +++ b/app/Rules/ValidGitBranch.php @@ -0,0 +1,96 @@ +', '\n', '\r', '\0', '"', "'", '\\', + '!', '*', '?', '[', ']', '~', '^', ':', ' ', + '#', + ]; + + foreach ($dangerousChars as $char) { + if (str_contains($branch, $char)) { + Log::warning('Git branch validation failed - dangerous character', [ + 'branch' => $branch, + 'character' => $char, + 'ip' => request()->ip(), + 'user_id' => auth()->id(), + ]); + $fail('The :attribute contains invalid characters.'); + + return; + } + } + + // Git branch name rules: + // - Cannot contain: .., //, @{ + // - Cannot start or end with: / or . + // - Cannot be empty after trimming + + if (str_contains($branch, '..') || + str_contains($branch, '//') || + str_contains($branch, '@{')) { + $fail('The :attribute contains invalid patterns.'); + + return; + } + + if (str_starts_with($branch, '/') || + str_ends_with($branch, '/') || + str_starts_with($branch, '.') || + str_ends_with($branch, '.')) { + $fail('The :attribute cannot start or end with / or .'); + + return; + } + + // Allow only safe characters for branch names + // Letters, numbers, hyphens, underscores, forward slashes, and dots + if (! preg_match('/^[a-zA-Z0-9\-_\/\.]+$/', $branch)) { + $fail('The :attribute contains invalid characters. Only letters, numbers, hyphens, underscores, forward slashes, and dots are allowed.'); + + return; + } + + // Additional Git-specific validations + // Branch name cannot be 'HEAD' + if ($branch === 'HEAD') { + $fail('The :attribute cannot be HEAD.'); + + return; + } + + // Check for consecutive dots (not allowed in Git) + if (str_contains($branch, '..')) { + $fail('The :attribute cannot contain consecutive dots.'); + + return; + } + + // Check for .lock suffix (reserved by Git) + if (str_ends_with($branch, '.lock')) { + $fail('The :attribute cannot end with .lock.'); + + return; + } + } +} diff --git a/app/Rules/ValidGitRepositoryUrl.php b/app/Rules/ValidGitRepositoryUrl.php new file mode 100644 index 000000000..3cbe9246e --- /dev/null +++ b/app/Rules/ValidGitRepositoryUrl.php @@ -0,0 +1,157 @@ +allowSSH = $allowSSH; + $this->allowIP = $allowIP; + } + + /** + * Run the validation rule. + */ + public function validate(string $attribute, mixed $value, Closure $fail): void + { + if (empty($value)) { + return; + } + + // Check for dangerous shell metacharacters that could be used for command injection + $dangerousChars = [ + ';', '|', '&', '$', '`', '(', ')', '{', '}', + '[', ']', '<', '>', '\n', '\r', '\0', '"', "'", + '\\', '!', '?', '*', '~', '^', '%', '=', '+', + '#', // Comment character that could hide commands + ]; + + foreach ($dangerousChars as $char) { + if (str_contains($value, $char)) { + Log::warning('Git repository URL validation failed - dangerous character', [ + 'url' => $value, + 'character' => $char, + 'ip' => request()->ip(), + 'user_id' => auth()->id(), + ]); + $fail('The :attribute contains invalid characters.'); + + return; + } + } + + // Check for command substitution patterns + $dangerousPatterns = [ + '/\$\(.*\)/', // Command substitution $(...) + '/\${.*}/', // Variable expansion ${...} + '/;;/', // Double semicolon + '/&&/', // Command chaining + '/\|\|/', // Command chaining + '/>>/', // Redirect append + '/< $value, + 'pattern' => $pattern, + 'ip' => request()->ip(), + 'user_id' => auth()->id(), + ]); + $fail('The :attribute contains invalid patterns.'); + + return; + } + } + + // Validate based on URL type + if (str_starts_with($value, 'git@')) { + if (! $this->allowSSH) { + $fail('SSH URLs are not allowed.'); + + return; + } + + // Validate SSH URL format (git@host:user/repo.git) + if (! preg_match('/^git@[a-zA-Z0-9\.\-]+:[a-zA-Z0-9\-_\/\.]+$/', $value)) { + $fail('The :attribute is not a valid SSH repository URL.'); + + return; + } + } elseif (str_starts_with($value, 'http://') || str_starts_with($value, 'https://')) { + // Validate HTTP(S) URL + if (! filter_var($value, FILTER_VALIDATE_URL)) { + $fail('The :attribute is not a valid URL.'); + + return; + } + + $parsed = parse_url($value); + + // Check for IP addresses if not allowed + if (! $this->allowIP && filter_var($parsed['host'] ?? '', FILTER_VALIDATE_IP)) { + Log::warning('Git repository URL contains IP address', [ + 'url' => $value, + 'ip' => request()->ip(), + 'user_id' => auth()->id(), + ]); + $fail('The :attribute cannot use IP addresses.'); + + return; + } + + // Check for localhost/internal addresses + $host = strtolower($parsed['host'] ?? ''); + $internalHosts = ['localhost', '127.0.0.1', '0.0.0.0', '::1']; + if (in_array($host, $internalHosts) || str_ends_with($host, '.local')) { + Log::warning('Git repository URL points to internal host', [ + 'url' => $value, + 'host' => $host, + 'ip' => request()->ip(), + 'user_id' => auth()->id(), + ]); + $fail('The :attribute cannot point to internal hosts.'); + + return; + } + + // Ensure no query parameters or fragments + if (! empty($parsed['query']) || ! empty($parsed['fragment'])) { + $fail('The :attribute should not contain query parameters or fragments.'); + + return; + } + + // Validate path contains only safe characters + $path = $parsed['path'] ?? ''; + if (! empty($path) && ! preg_match('/^[a-zA-Z0-9\-_\/\.]+$/', $path)) { + $fail('The :attribute path contains invalid characters.'); + + return; + } + } elseif (str_starts_with($value, 'git://')) { + // Validate git:// protocol URL + if (! preg_match('/^git:\/\/[a-zA-Z0-9\.\-]+\/[a-zA-Z0-9\-_\/\.]+$/', $value)) { + $fail('The :attribute is not a valid git:// URL.'); + + return; + } + } else { + $fail('The :attribute must start with https://, http://, git://, or git@.'); + + return; + } + } +} diff --git a/resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php b/resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php index bd80c89cd..d61891869 100644 --- a/resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php +++ b/resources/views/livewire/project/new/github-private-repository-deploy-key.blade.php @@ -1,7 +1,7 @@

Create a new Application

Deploy any public or private Git repositories through a Deploy Key.
-
+
@if ($current_step === 'private_keys')

Select a private key

@@ -46,9 +46,8 @@
@endif @if ($current_step === 'repository') -

Select a repository

-
- + +
diff --git a/resources/views/livewire/project/new/public-git-repository.blade.php b/resources/views/livewire/project/new/public-git-repository.blade.php index ba9e37784..36e3350a9 100644 --- a/resources/views/livewire/project/new/public-git-repository.blade.php +++ b/resources/views/livewire/project/new/public-git-repository.blade.php @@ -1,6 +1,6 @@

Create a new Application

-
Deploy any public Git repositories.
+
Deploy any public Git repositories.