fix(gitlab): hide source secrets from unauthorized users

Only persist the system-wide setting during instant saves, preventing
unvalidated source details from being stored.
This commit is contained in:
Andras Bacsai
2026-07-21 22:11:25 +02:00
parent aeeb2665cd
commit 3a863378b1
2 changed files with 63 additions and 6 deletions
+8 -6
View File
@@ -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) {
@@ -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]);