Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8b7dbba
fix(api): hide sensitive fields by default, expose via makeVisible fo…
andrasbacsai Apr 30, 2026
e828058
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai Apr 30, 2026
8dc79f4
fix(api): expose nested server secrets for privileged tokens
andrasbacsai Apr 30, 2026
ea0ade4
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai Apr 30, 2026
5f124ae
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai May 5, 2026
0888198
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai May 5, 2026
c553b6a
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai May 6, 2026
224b9e4
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai May 7, 2026
ffa494d
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai May 11, 2026
c97f916
fix(api): hide application compose PR fields
andrasbacsai May 11, 2026
12aba41
fix(api): gate service server secrets by sensitive scope
andrasbacsai May 11, 2026
b29bdce
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai May 11, 2026
f40bb80
style(api): tighten nested server secret visibility checks
andrasbacsai May 11, 2026
81a3bb0
fix(api): hide sensitive fields by default
andrasbacsai May 11, 2026
db23ee7
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai May 11, 2026
fb13a6c
fix(api): expose cloud tokens with sensitive read access
andrasbacsai May 13, 2026
0038a8f
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai Jun 2, 2026
b01c2dd
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai Jun 4, 2026
2fcc42b
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai Jun 4, 2026
70eda65
fix(api): hide nested server secrets from read tokens
andrasbacsai Jun 4, 2026
2ebe2e8
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai Jun 15, 2026
f5ecdfa
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai Jun 15, 2026
6871160
fix(api): gate sensitive storage and GitHub fields
andrasbacsai Jul 2, 2026
1317284
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai Jul 2, 2026
99f6022
fix(api): avoid lazy loading nested server secrets
andrasbacsai Jul 2, 2026
e9bef84
fix(api): hide nested server secrets in database responses
andrasbacsai Jul 7, 2026
9a2c432
fix(api): expose sensitive fields for privileged tokens
andrasbacsai Jul 7, 2026
ff976a1
Merge remote-tracking branch 'origin/next' into api-sensitive-data-sc…
andrasbacsai Jul 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions app/Http/Controllers/Api/ApplicationsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ private function removeSensitiveData($application)
'resourceable_id',
'resourceable_type',
]);
if (request()->attributes->get('can_read_sensitive', false) === false) {
$application->makeHidden([
if (request()->attributes->get('can_read_sensitive', false) === true) {
$application->makeVisible([
'custom_labels',
'dockerfile',
'docker_compose',
Expand All @@ -51,16 +51,48 @@ private function removeSensitiveData($application)
'manual_webhook_secret_gitea',
'manual_webhook_secret_github',
'manual_webhook_secret_gitlab',
'private_key_id',
'http_basic_auth_password',
'value',
'real_value',
'http_basic_auth_password',
]);
$this->exposeNestedServerSecrets($application);
} else {
Comment thread
andrasbacsai marked this conversation as resolved.
$application->makeHidden([
'private_key_id',
]);
}

return serializeApiResponse($application);
}

/**
* Expose sensitive fields on eager-loaded nested Server + ServerSetting
* relations for callers with the `read:sensitive` or `root` token ability.
* Models hide these by default via $hidden; this re-exposes them per-request.
*/
private function exposeNestedServerSecrets($model): void
{
$server = $model->destination?->server ?? null;
if (! $server) {
return;
}
$server->makeVisible([
'logdrain_axiom_api_key',
'logdrain_newrelic_license_key',
]);
$settings = $server->settings ?? null;
if ($settings) {
$settings->makeVisible([
'sentinel_token',
'sentinel_custom_url',
'logdrain_newrelic_license_key',
'logdrain_axiom_api_key',
'logdrain_custom_config',
'logdrain_custom_config_parser',
]);
}
}

#[OA\Get(
summary: 'List',
description: 'List all applications.',
Expand Down
49 changes: 44 additions & 5 deletions app/Http/Controllers/Api/DatabasesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Support\ValidationPatterns;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
Expand All @@ -33,22 +34,56 @@ private function removeSensitiveData($database)
'id',
'laravel_through_key',
]);
if (request()->attributes->get('can_read_sensitive', false) === false) {
$database->makeHidden([
if (request()->attributes->get('can_read_sensitive', false) === true) {
$database->makeVisible([
'internal_db_url',
'external_db_url',
'init_scripts',
'postgres_password',
'dragonfly_password',
'redis_password',
'mongo_initdb_root_password',
'keydb_password',
'clickhouse_admin_password',
'mysql_password',
'mysql_root_password',
'mariadb_password',
'mariadb_root_password',
]);
$this->exposeNestedServerSecrets($database);
}

return serializeApiResponse($database);
}

/**
* Expose sensitive fields on eager-loaded nested Server + ServerSetting
* relations for callers with the `read:sensitive` or `root` token ability.
*/
private function exposeNestedServerSecrets(Model $model): void
{
$server = $model->destination?->server;
if ($server === null) {
return;
}

$server->makeVisible([
'logdrain_axiom_api_key',
'logdrain_newrelic_license_key',
]);

if ($server->settings !== null) {
$server->settings->makeVisible([
'sentinel_token',
'sentinel_custom_url',
'logdrain_newrelic_license_key',
'logdrain_axiom_api_key',
'logdrain_custom_config',
'logdrain_custom_config_parser',
]);
}
}
Comment thread
andrasbacsai marked this conversation as resolved.

#[OA\Get(
summary: 'List',
description: 'List all databases.',
Expand Down Expand Up @@ -85,8 +120,12 @@ public function databases(Request $request)
}
$projects = Project::where('team_id', $teamId)->get();
$databases = collect();
$databaseRelations = $request->attributes->get('can_read_sensitive', false) === true
? ['destination.server.settings']
: [];

foreach ($projects as $project) {
$databases = $databases->merge($project->databases());
$databases = $databases->merge($project->databases($databaseRelations));
}

$databaseIds = $databases->pluck('id')->toArray();
Expand Down Expand Up @@ -2957,8 +2996,8 @@ private function removeSensitiveEnvData($env)
'resourceable_id',
'resourceable_type',
]);
if (request()->attributes->get('can_read_sensitive', false) === false) {
$env->makeHidden([
if (request()->attributes->get('can_read_sensitive', false) === true) {
$env->makeVisible([
'value',
'real_value',
]);
Expand Down
7 changes: 7 additions & 0 deletions app/Http/Controllers/Api/DeployController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ private function removeSensitiveData($deployment)
$deployment->makeHidden([
'logs',
]);
} else {
$deployment->makeVisible([
'logs',
]);
}

return serializeApiResponse($deployment);
Expand Down Expand Up @@ -699,6 +703,9 @@ public function get_application_deployments(Request $request)
$this->authorize('view', $application);

$deployments = $application->deployments($skip, $take);
if ($request->attributes->get('can_read_sensitive', false) === true) {
$deployments['deployments']->each->makeVisible(['logs']);
}

return response()->json($deployments);
}
Expand Down
4 changes: 4 additions & 0 deletions app/Http/Controllers/Api/SecurityController.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ private function removeSensitiveData($team)
$team->makeHidden([
'private_key',
]);
} else {
$team->makeVisible([
'private_key',
]);
}

return serializeApiResponse($team);
Expand Down
16 changes: 12 additions & 4 deletions app/Http/Controllers/Api/ServersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ class ServersController extends Controller
{
private function removeSensitiveDataFromSettings($settings)
{
if (request()->attributes->get('can_read_sensitive', false) === false) {
$settings = $settings->makeHidden([
if (request()->attributes->get('can_read_sensitive', false) === true) {
$settings = $settings->makeVisible([
'sentinel_token',
'sentinel_custom_url',
'logdrain_newrelic_license_key',
'logdrain_axiom_api_key',
'logdrain_custom_config',
'logdrain_custom_config_parser',
]);
}

Expand All @@ -36,8 +41,11 @@ private function removeSensitiveData($server)
$server->makeHidden([
'id',
]);
if (request()->attributes->get('can_read_sensitive', false) === false) {
// Do nothing
if (request()->attributes->get('can_read_sensitive', false) === true) {
$server->makeVisible([
'logdrain_axiom_api_key',
'logdrain_newrelic_license_key',
]);
}

return serializeApiResponse($server);
Expand Down
56 changes: 52 additions & 4 deletions app/Http/Controllers/Api/ServicesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
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;
use Illuminate\Support\Facades\Validator;
use OpenApi\Attributes as OA;
use Symfony\Component\Yaml\Yaml;
Expand All @@ -30,18 +32,55 @@ private function removeSensitiveData($service)
'resourceable_id',
'resourceable_type',
]);
if (request()->attributes->get('can_read_sensitive', false) === false) {
$service->makeHidden([
if (request()->attributes->get('can_read_sensitive', false) === true) {
$service->makeVisible([
'docker_compose_raw',
'docker_compose',
'value',
'real_value',
]);
$this->exposeNestedServerSecrets($service);
}

return serializeApiResponse($service);
}

/**
* Expose sensitive fields on eager-loaded nested Server + ServerSetting
* relations for callers with the `read:sensitive` or `root` token ability.
* Handles both single models and Eloquent Collections (the listing endpoint
* passes a Collection of Services per project to removeSensitiveData()).
*/
private function exposeNestedServerSecrets(Model|Collection $model): void
{
if ($model instanceof Collection) {
foreach ($model as $item) {
$this->exposeNestedServerSecrets($item);
}

return;
}
$server = $model->destination?->server ?? $model->server ?? null;
if (! $server) {
return;
}
$server->makeVisible([
'logdrain_axiom_api_key',
'logdrain_newrelic_license_key',
]);
$settings = $server->settings ?? null;
if ($settings) {
$settings->makeVisible([
'sentinel_token',
'sentinel_custom_url',
'logdrain_newrelic_license_key',
'logdrain_axiom_api_key',
'logdrain_custom_config',
'logdrain_custom_config_parser',
]);
}
}

Comment thread
andrasbacsai marked this conversation as resolved.
private function applyServiceUrls(Service $service, array $urlsArray, string $teamId, bool $forceDomainOverride = false): ?array
{
$errors = [];
Expand Down Expand Up @@ -177,8 +216,12 @@ public function services(Request $request)
}
$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);
Expand Down Expand Up @@ -733,7 +776,12 @@ public function service_by_uuid(Request $request)

$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));
}
Expand Down
20 changes: 20 additions & 0 deletions app/Models/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,26 @@ class Application extends BaseModel

protected $appends = ['server_status'];

/**
* Sensitive fields hidden by default in serialized output (toArray/toJson).
* API controllers should call makeVisible([...]) for callers with the
* `read:sensitive` or `root` token ability. Internal serializers (deployment
* job, compose generation) must makeVisible explicitly before toArray().
*/
protected $hidden = [
'http_basic_auth_password',
'manual_webhook_secret_github',
'manual_webhook_secret_gitlab',
'manual_webhook_secret_bitbucket',
'manual_webhook_secret_gitea',
'dockerfile',
'docker_compose',
'docker_compose_pr',
'docker_compose_raw',
'docker_compose_pr_raw',
'custom_labels',
];
Comment thread
andrasbacsai marked this conversation as resolved.

protected function casts(): array
{
return [
Expand Down
4 changes: 4 additions & 0 deletions app/Models/ApplicationDeploymentQueue.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ class ApplicationDeploymentQueue extends Model
'finished_at',
];

protected $hidden = [
'logs',
];

protected $casts = [
'pull_request_id' => 'integer',
'finished_at' => 'datetime',
Expand Down
4 changes: 4 additions & 0 deletions app/Models/CloudInitScript.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ class CloudInitScript extends Model
'script',
];

protected $hidden = [
'script',
];

protected function casts(): array
{
return [
Expand Down
4 changes: 4 additions & 0 deletions app/Models/CloudProviderToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ class CloudProviderToken extends BaseModel
'name',
];

protected $hidden = [
'token',
];
Comment thread
coderabbitai[bot] marked this conversation as resolved.

protected $casts = [
'token' => 'encrypted',
];
Expand Down
4 changes: 4 additions & 0 deletions app/Models/DiscordNotificationSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class DiscordNotificationSettings extends Model
'discord_ping_enabled',
];

protected $hidden = [
'discord_webhook_url',
];

protected $casts = [
'discord_enabled' => 'boolean',
'discord_webhook_url' => 'encrypted',
Expand Down
Loading
Loading