fix(api): avoid lazy loading nested server secrets

This commit is contained in:
Andras Bacsai 2026-07-02 17:47:22 +02:00
parent 13172849e1
commit 99f60228ad
5 changed files with 28 additions and 58 deletions

View file

@ -69,7 +69,6 @@ private function removeSensitiveData($application)
$application->makeHidden([ $application->makeHidden([
'private_key_id', 'private_key_id',
]); ]);
$this->hideNestedServerSecrets($application);
} }
if ($application->is_shown_once ?? false) { if ($application->is_shown_once ?? false) {
@ -79,31 +78,6 @@ private function removeSensitiveData($application)
return serializeApiResponse($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 * Expose sensitive fields on eager-loaded nested Server + ServerSetting
* relations for callers with the `read:sensitive` or `root` token ability. * relations for callers with the `read:sensitive` or `root` token ability.
@ -184,8 +158,12 @@ public function applications(Request $request)
} }
$tagName = $request->query('tag'); $tagName = $request->query('tag');
$applicationRelations = $request->attributes->get('can_read_sensitive', false) === true
? ['destination.server.settings']
: [];
$applications = Application::ownedByCurrentTeamAPI($teamId) $applications = Application::ownedByCurrentTeamAPI($teamId)
->with($applicationRelations)
->when($tagName, function ($query, $tagName) { ->when($tagName, function ($query, $tagName) {
$query->whereHas('tags', function ($query) use ($tagName) { $query->whereHas('tags', function ($query) use ($tagName) {
$query->where('name', $tagName); $query->where('name', $tagName);

View file

@ -60,37 +60,11 @@ private function removeSensitiveData($database)
'mariadb_root_password', 'mariadb_root_password',
]); ]);
$this->exposeNestedServerSecrets($database); $this->exposeNestedServerSecrets($database);
} else {
$this->hideNestedServerSecrets($database);
} }
return serializeApiResponse($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 * Expose sensitive fields on eager-loaded nested Server + ServerSetting
* relations for callers with the `read:sensitive` or `root` token ability. * relations for callers with the `read:sensitive` or `root` token ability.

View file

@ -803,7 +803,7 @@
"category": "backend", "category": "backend",
"logo": "svgs/convex.svg", "logo": "svgs/convex.svg",
"minversion": "0.0.0", "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" "port": "6791"
}, },
"cryptgeon": { "cryptgeon": {
@ -1779,7 +1779,7 @@
"category": "devtools", "category": "devtools",
"logo": "svgs/gitea.svg", "logo": "svgs/gitea.svg",
"minversion": "0.0.0", "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": { "gitea-with-mariadb": {
"documentation": "https://docs.gitea.com?utm_source=coolify.io", "documentation": "https://docs.gitea.com?utm_source=coolify.io",
@ -2361,7 +2361,7 @@
"category": "automation", "category": "automation",
"logo": "svgs/inngest.png", "logo": "svgs/inngest.png",
"minversion": "0.0.0", "minversion": "0.0.0",
"template_last_updated_at": null, "template_last_updated_at": "2026-07-02T13:25:47+02:00",
"port": "8288" "port": "8288"
}, },
"invoice-ninja": { "invoice-ninja": {

View file

@ -803,7 +803,7 @@
"category": "backend", "category": "backend",
"logo": "svgs/convex.svg", "logo": "svgs/convex.svg",
"minversion": "0.0.0", "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" "port": "6791"
}, },
"cryptgeon": { "cryptgeon": {
@ -1779,7 +1779,7 @@
"category": "devtools", "category": "devtools",
"logo": "svgs/gitea.svg", "logo": "svgs/gitea.svg",
"minversion": "0.0.0", "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": { "gitea-with-mariadb": {
"documentation": "https://docs.gitea.com?utm_source=coolify.io", "documentation": "https://docs.gitea.com?utm_source=coolify.io",
@ -2361,7 +2361,7 @@
"category": "automation", "category": "automation",
"logo": "svgs/inngest.png", "logo": "svgs/inngest.png",
"minversion": "0.0.0", "minversion": "0.0.0",
"template_last_updated_at": null, "template_last_updated_at": "2026-07-02T13:25:47+02:00",
"port": "8288" "port": "8288"
}, },
"invoice-ninja": { "invoice-ninja": {

View file

@ -11,6 +11,7 @@
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use App\Models\Team; use App\Models\Team;
use App\Models\User; use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB; 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 () { test('read token does not leak postgres_password or db urls', function () {
$token = makeApiToken($this->user, $this->team, ['read']); $token = makeApiToken($this->user, $this->team, ['read']);