From dc9322b11f5f4ab96e56af0df7d4f877e96e5e4c Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:51:27 +0200 Subject: [PATCH] refactor(settings): validate dev_helper_version and escape build args Constrain dev_helper_version to Docker tag grammar ([A-Za-z0-9_][A-Za-z0-9_.-]{0,127}), re-validate before triggering the helper image build, and interpolate the image reference via escapeshellarg() when composing the docker build command. --- app/Livewire/Settings/Index.php | 14 ++- .../DevHelperVersionValidationTest.php | 90 +++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/Feature/DevHelperVersionValidationTest.php diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php index 9a51d107d..c2789aa91 100644 --- a/app/Livewire/Settings/Index.php +++ b/app/Livewire/Settings/Index.php @@ -35,7 +35,7 @@ class Index extends Component #[Validate('required|string|timezone')] public string $instance_timezone; - #[Validate('nullable|string|max:50')] + #[Validate(['nullable', 'string', 'max:128', 'regex:/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/'])] public ?string $dev_helper_version = null; public array $domainConflicts = []; @@ -49,6 +49,7 @@ class Index extends Component protected array $messages = [ 'fqdn.url' => 'Invalid instance URL.', 'fqdn.max' => 'URL must not exceed 255 characters.', + 'dev_helper_version.regex' => 'Dev helper version must match Docker tag format (alphanumeric, _, ., -; first char cannot be . or -).', ]; public function render() @@ -184,6 +185,8 @@ public function buildHelperImage() return; } + $this->validateOnly('dev_helper_version'); + $version = $this->dev_helper_version ?: config('constants.coolify.helper_version'); if (empty($version)) { $this->dispatch('error', 'Please specify a version to build.'); @@ -191,7 +194,14 @@ public function buildHelperImage() return; } - $buildCommand = "docker build -t ghcr.io/coollabsio/coolify-helper:{$version} -f docker/coolify-helper/Dockerfile ."; + if (! preg_match('/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/', (string) $version)) { + $this->dispatch('error', 'Invalid helper version format.'); + + return; + } + + $imageRef = escapeshellarg("ghcr.io/coollabsio/coolify-helper:{$version}"); + $buildCommand = "docker build -t {$imageRef} -f docker/coolify-helper/Dockerfile ."; $activity = remote_process( command: [$buildCommand], diff --git a/tests/Feature/DevHelperVersionValidationTest.php b/tests/Feature/DevHelperVersionValidationTest.php new file mode 100644 index 000000000..03316598c --- /dev/null +++ b/tests/Feature/DevHelperVersionValidationTest.php @@ -0,0 +1,90 @@ +rootTeam = Team::find(0) ?? Team::create(['id' => 0, 'name' => 'Root Team', 'personal_team' => false]); + if (! Server::find(0)) { + Server::factory()->create(['id' => 0, 'team_id' => $this->rootTeam->id]); + } + if (! InstanceSettings::find(0)) { + InstanceSettings::create(['id' => 0]); + } + }); + Once::flush(); + + $this->user = User::factory()->create(); + $this->rootTeam->members()->attach($this->user->id, ['role' => 'admin']); + + $this->actingAs($this->user); + session(['currentTeam' => ['id' => $this->rootTeam->id]]); +}); + +test('dev_helper_version rejects values outside Docker tag grammar on save', function () { + $invalid = [ + 'latest with spaces', + 'a$b', + 'a`b', + 'a|b', + 'a;b', + 'a&b', + 'a>b', + 'aset('dev_helper_version', $payload) + ->call('instantSave') + ->assertHasErrors(['dev_helper_version']); + } + + expect(InstanceSettings::find(0)->dev_helper_version)->toBeNull(); +}); + +test('dev_helper_version accepts valid docker tag formats', function () { + $valid = ['1.0.12', 'latest', 'dev', 'dev-branch_2', 'v1.2.3-rc1', '1_0_0']; + + foreach ($valid as $tag) { + Livewire::test(Index::class) + ->set('dev_helper_version', $tag) + ->call('instantSave') + ->assertHasNoErrors(['dev_helper_version']); + + expect(InstanceSettings::find(0)->fresh()->dev_helper_version)->toBe($tag); + } +}); + +test('buildHelperImage refuses when non-dev environment', function () { + config(['app.env' => 'production']); + + Livewire::test(Index::class) + ->set('dev_helper_version', 'latest') + ->call('buildHelperImage') + ->assertDispatched('error'); +}); + +test('buildHelperImage refuses previously stored invalid version', function () { + config(['app.env' => 'local']); + + $settings = InstanceSettings::find(0); + $settings->forceFill(['dev_helper_version' => 'bad value'])->saveQuietly(); + + Livewire::test(Index::class) + ->call('buildHelperImage') + ->assertDispatched('error'); +});