From 3a863378b1f71bf62ef548cf96a6ce8f91023923 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 21 Jul 2026 22:11:25 +0200 Subject: [PATCH] fix(gitlab): hide source secrets from unauthorized users Only persist the system-wide setting during instant saves, preventing unvalidated source details from being stored. --- app/Livewire/Source/Gitlab/Change.php | 14 ++--- tests/Feature/GitlabAppAuthorizationTest.php | 55 ++++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/app/Livewire/Source/Gitlab/Change.php b/app/Livewire/Source/Gitlab/Change.php index 723b4eeae..f22f1845d 100644 --- a/app/Livewire/Source/Gitlab/Change.php +++ b/app/Livewire/Source/Gitlab/Change.php @@ -7,6 +7,7 @@ use App\Models\PrivateKey; use App\Rules\SafeExternalUrl; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Http; use Illuminate\Support\Str; use Livewire\Component; @@ -252,10 +253,10 @@ class Change extends Component $this->customUser = $this->gitlab_app->custom_user; $this->customPort = $this->gitlab_app->custom_port; $this->clientId = $this->gitlab_app->client_id; - // Decrypt and surface for authorized editors (same pattern as GitHub App client_secret). - $this->gitlab_app->makeVisible(['client_secret', 'webhook_token', 'access_token', 'refresh_token']); - $this->clientSecretInput = $this->gitlab_app->client_secret; - $this->webhookToken = $this->gitlab_app->webhook_token; + if (Gate::allows('update', $this->gitlab_app)) { + $this->clientSecretInput = $this->gitlab_app->client_secret; + $this->webhookToken = $this->gitlab_app->webhook_token; + } $this->groupName = $this->gitlab_app->group_name; $this->isSystemWide = $this->gitlab_app->is_system_wide; $this->privateKeyId = $this->gitlab_app->private_key_id; @@ -282,8 +283,9 @@ class Change extends Component try { $this->authorize('update', $this->gitlab_app); - $this->gitlab_app->makeVisible(['client_secret', 'webhook_token', 'access_token', 'refresh_token']); - $this->syncData(true); + $this->validateOnly('isSystemWide'); + + $this->gitlab_app->is_system_wide = $this->isSystemWide; $this->gitlab_app->save(); $this->dispatch('success', 'GitLab App updated.'); } catch (\Throwable $e) { diff --git a/tests/Feature/GitlabAppAuthorizationTest.php b/tests/Feature/GitlabAppAuthorizationTest.php index 7fc20d07f..4ff591120 100644 --- a/tests/Feature/GitlabAppAuthorizationTest.php +++ b/tests/Feature/GitlabAppAuthorizationTest.php @@ -46,6 +46,38 @@ beforeEach(function () { }); describe('GitLab App authorization', function () { + test('unrelated users cannot inspect system-wide source secrets in the component payload', function () { + $otherTeam = Team::factory()->create(); + $systemWideSource = GitlabApp::create([ + 'name' => 'Shared GitLab', + 'api_url' => 'https://gitlab.example.com/api/v4', + 'html_url' => 'https://gitlab.example.com', + 'custom_user' => 'git', + 'custom_port' => 22, + 'client_id' => 'shared-client-id', + 'client_secret' => 'shared-client-secret', + 'webhook_token' => 'shared-webhook-token', + 'access_token' => 'shared-access-token', + 'refresh_token' => 'shared-refresh-token', + 'expires_at' => time() + 3600, + 'team_id' => $otherTeam->id, + 'is_system_wide' => true, + 'is_public' => false, + ]); + + $this->actingAs($this->owner); + session(['currentTeam' => $this->team]); + + $component = Livewire::withQueryParams(['gitlab_app_uuid' => $systemWideSource->uuid]) + ->test(Change::class) + ->assertSet('clientSecretInput', null) + ->assertSet('webhookToken', null); + + expect($component->html()) + ->not->toContain('shared-client-secret') + ->not->toContain('shared-webhook-token'); + }); + test('team member cannot update a gitlab app via instantSave', function () { $this->actingAs($this->member); session(['currentTeam' => $this->team]); @@ -72,6 +104,29 @@ describe('GitLab App authorization', function () { expect($this->gitlabApp->refresh()->is_system_wide)->toBeTrue(); }); + test('instantSave rejects unsafe GitLab URLs', function (string $url) { + $this->actingAs($this->owner); + session(['currentTeam' => $this->team]); + + Livewire::withQueryParams(['gitlab_app_uuid' => $this->gitlabApp->uuid]) + ->test(Change::class) + ->set('htmlUrl', $url) + ->set('apiUrl', $url.'/api/v4') + ->set('isSystemWide', true) + ->call('instantSave') + ->assertDispatched('success'); + + $this->gitlabApp->refresh(); + + expect($this->gitlabApp->html_url)->toBe('https://gitlab.example.com') + ->and($this->gitlabApp->api_url)->toBe('https://gitlab.example.com/api/v4') + ->and($this->gitlabApp->is_system_wide)->toBeTrue(); + })->with([ + 'private address' => 'http://10.0.0.1', + 'loopback address' => 'http://127.0.0.1', + 'metadata service address' => 'http://169.254.169.254', + ]); + test('team member cannot create an application from a private gitlab repository', function () { $this->actingAs($this->member); session(['currentTeam' => $this->team]);