From 99f60228ad11840cfab9b5dda4cdba4ef8ff200b Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:47:22 +0200 Subject: [PATCH] fix(api): avoid lazy loading nested server secrets --- .../Api/ApplicationsController.php | 30 +++---------------- .../Controllers/Api/DatabasesController.php | 26 ---------------- templates/service-templates-latest.json | 6 ++-- templates/service-templates.json | 6 ++-- .../Security/ApiSensitiveFieldsTest.php | 18 +++++++++++ 5 files changed, 28 insertions(+), 58 deletions(-) diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index afef39464..2a2c0c3ac 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -69,7 +69,6 @@ private function removeSensitiveData($application) $application->makeHidden([ 'private_key_id', ]); - $this->hideNestedServerSecrets($application); } if ($application->is_shown_once ?? false) { @@ -79,31 +78,6 @@ private function removeSensitiveData($application) return serializeApiResponse($application); } - private function hideNestedServerSecrets($model): void - { - $server = $model->destination?->server ?? null; - if (! $server) { - return; - } - - $server->makeHidden([ - 'logdrain_axiom_api_key', - 'logdrain_newrelic_license_key', - ]); - - $settings = $server->settings ?? null; - if ($settings) { - $settings->makeHidden([ - 'sentinel_token', - 'sentinel_custom_url', - 'logdrain_newrelic_license_key', - 'logdrain_axiom_api_key', - 'logdrain_custom_config', - 'logdrain_custom_config_parser', - ]); - } - } - /** * Expose sensitive fields on eager-loaded nested Server + ServerSetting * relations for callers with the `read:sensitive` or `root` token ability. @@ -184,8 +158,12 @@ public function applications(Request $request) } $tagName = $request->query('tag'); + $applicationRelations = $request->attributes->get('can_read_sensitive', false) === true + ? ['destination.server.settings'] + : []; $applications = Application::ownedByCurrentTeamAPI($teamId) + ->with($applicationRelations) ->when($tagName, function ($query, $tagName) { $query->whereHas('tags', function ($query) use ($tagName) { $query->where('name', $tagName); diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 49f5fa214..d3991b14b 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -60,37 +60,11 @@ private function removeSensitiveData($database) 'mariadb_root_password', ]); $this->exposeNestedServerSecrets($database); - } else { - $this->hideNestedServerSecrets($database); } return serializeApiResponse($database); } - private function hideNestedServerSecrets(Model $model): void - { - $server = $model->destination?->server; - if ($server === null) { - return; - } - - $server->makeHidden([ - 'logdrain_axiom_api_key', - 'logdrain_newrelic_license_key', - ]); - - if ($server->settings !== null) { - $server->settings->makeHidden([ - 'sentinel_token', - 'sentinel_custom_url', - 'logdrain_newrelic_license_key', - 'logdrain_axiom_api_key', - 'logdrain_custom_config', - 'logdrain_custom_config_parser', - ]); - } - } - /** * Expose sensitive fields on eager-loaded nested Server + ServerSetting * relations for callers with the `read:sensitive` or `root` token ability. diff --git a/templates/service-templates-latest.json b/templates/service-templates-latest.json index 8d1a3369b..0a2dfda9d 100644 --- a/templates/service-templates-latest.json +++ b/templates/service-templates-latest.json @@ -803,7 +803,7 @@ "category": "backend", "logo": "svgs/convex.svg", "minversion": "0.0.0", - "template_last_updated_at": "2026-05-09T19:26:30+05:30", + "template_last_updated_at": "2026-06-12T10:45:52+02:00", "port": "6791" }, "cryptgeon": { @@ -1779,7 +1779,7 @@ "category": "devtools", "logo": "svgs/gitea.svg", "minversion": "0.0.0", - "template_last_updated_at": "2026-06-01T07:54:27-05:00" + "template_last_updated_at": "2026-06-06T00:11:24+02:00" }, "gitea-with-mariadb": { "documentation": "https://docs.gitea.com?utm_source=coolify.io", @@ -2361,7 +2361,7 @@ "category": "automation", "logo": "svgs/inngest.png", "minversion": "0.0.0", - "template_last_updated_at": null, + "template_last_updated_at": "2026-07-02T13:25:47+02:00", "port": "8288" }, "invoice-ninja": { diff --git a/templates/service-templates.json b/templates/service-templates.json index 2ad3008f5..4d97d8d5e 100644 --- a/templates/service-templates.json +++ b/templates/service-templates.json @@ -803,7 +803,7 @@ "category": "backend", "logo": "svgs/convex.svg", "minversion": "0.0.0", - "template_last_updated_at": "2026-05-09T19:26:30+05:30", + "template_last_updated_at": "2026-06-12T10:45:52+02:00", "port": "6791" }, "cryptgeon": { @@ -1779,7 +1779,7 @@ "category": "devtools", "logo": "svgs/gitea.svg", "minversion": "0.0.0", - "template_last_updated_at": "2026-06-01T07:54:27-05:00" + "template_last_updated_at": "2026-06-06T00:11:24+02:00" }, "gitea-with-mariadb": { "documentation": "https://docs.gitea.com?utm_source=coolify.io", @@ -2361,7 +2361,7 @@ "category": "automation", "logo": "svgs/inngest.png", "minversion": "0.0.0", - "template_last_updated_at": null, + "template_last_updated_at": "2026-07-02T13:25:47+02:00", "port": "8288" }, "invoice-ninja": { diff --git a/tests/Feature/Security/ApiSensitiveFieldsTest.php b/tests/Feature/Security/ApiSensitiveFieldsTest.php index 2d2d7a3ef..4401a839b 100644 --- a/tests/Feature/Security/ApiSensitiveFieldsTest.php +++ b/tests/Feature/Security/ApiSensitiveFieldsTest.php @@ -11,6 +11,7 @@ use App\Models\StandalonePostgresql; use App\Models\Team; use App\Models\User; +use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\DB; @@ -550,6 +551,23 @@ function makeTeamUser(): array ]); }); + test('read token database list does not lazy load nested server relations', function () { + $token = makeApiToken($this->user, $this->team, ['read']); + + $preventedLazyLoading = Model::preventsLazyLoading(); + Model::preventLazyLoading(); + + try { + $response = $this->withoutExceptionHandling()->withHeaders([ + 'Authorization' => 'Bearer '.$token, + ])->getJson('/api/v1/databases'); + } finally { + Model::preventLazyLoading($preventedLazyLoading); + } + + $response->assertStatus(200); + }); + test('read token does not leak postgres_password or db urls', function () { $token = makeApiToken($this->user, $this->team, ['read']);