fix(api): gate service server secrets by sensitive scope

Only eager load nested server settings for service responses when the API token has read:sensitive, preventing read-only tokens from exposing sentinel fields while preserving sensitive access.
This commit is contained in:
Andras Bacsai
2026-05-11 11:53:22 +02:00
parent c97f916052
commit 12aba41d9a
2 changed files with 89 additions and 4 deletions
@@ -14,6 +14,7 @@ use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use App\Support\ValidationPatterns;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
@@ -50,9 +51,9 @@ class ServicesController extends Controller
* Handles both single models and Eloquent Collections (the listing endpoint
* passes a Collection of Services per project to removeSensitiveData()).
*/
private function exposeNestedServerSecrets($model): void
private function exposeNestedServerSecrets(Model|Collection $model): void
{
if ($model instanceof Collection || $model instanceof \Illuminate\Database\Eloquent\Collection) {
if ($model instanceof Collection) {
foreach ($model as $item) {
$this->exposeNestedServerSecrets($item);
}
@@ -215,8 +216,12 @@ class ServicesController extends Controller
}
$projects = Project::where('team_id', $teamId)->get();
$services = collect();
$serviceRelations = $request->attributes->get('can_read_sensitive', false) === true
? ['destination.server.settings']
: [];
foreach ($projects as $project) {
$services->push($project->services()->get());
$services->push($project->services()->with($serviceRelations)->get());
}
foreach ($services as $service) {
$service = $this->removeSensitiveData($service);
@@ -771,7 +776,12 @@ class ServicesController extends Controller
$this->authorize('view', $service);
$service = $service->load(['applications', 'databases']);
$serviceRelations = ['applications', 'databases'];
if ($request->attributes->get('can_read_sensitive', false) === true) {
$serviceRelations[] = 'destination.server.settings';
}
$service = $service->load($serviceRelations);
return response()->json($this->removeSensitiveData($service));
}