From 81a3bb0f0769e5a765e77649d002ea6acf9a667f Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 11 May 2026 21:18:07 +0200 Subject: [PATCH] fix(api): hide sensitive fields by default Add model-level hidden fields for secrets, tokens, keys, notification credentials, deployment logs, and environment values. Allow explicit read:sensitive API access to reveal gated private keys and deployment logs, and cover the behavior with feature and unit tests. --- app/Http/Controllers/Api/DeployController.php | 7 + .../Controllers/Api/SecurityController.php | 4 + app/Models/ApplicationDeploymentQueue.php | 4 + app/Models/CloudInitScript.php | 4 + app/Models/CloudProviderToken.php | 4 + app/Models/DiscordNotificationSettings.php | 4 + app/Models/EmailNotificationSettings.php | 10 ++ app/Models/InstanceSettings.php | 11 ++ app/Models/OauthSetting.php | 4 + app/Models/PrivateKey.php | 4 + app/Models/PushoverNotificationSettings.php | 5 + app/Models/S3Storage.php | 5 + app/Models/SharedEnvironmentVariable.php | 4 + app/Models/SlackNotificationSettings.php | 4 + app/Models/SslCertificate.php | 4 + app/Models/TelegramNotificationSettings.php | 22 ++- app/Models/WebhookNotificationSettings.php | 4 + .../components/notification/navbar.blade.php | 2 +- .../Security/ApiSensitiveFieldsTest.php | 95 ++++++++++++ .../Unit/Models/SensitiveFieldsHiddenTest.php | 136 ++++++++++++++++++ 20 files changed, 335 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index c93731d68..7a049ae2b 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -25,6 +25,10 @@ private function removeSensitiveData($deployment) $deployment->makeHidden([ 'logs', ]); + } else { + $deployment->makeVisible([ + 'logs', + ]); } return serializeApiResponse($deployment); @@ -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); } diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php index e59c40866..759af5134 100644 --- a/app/Http/Controllers/Api/SecurityController.php +++ b/app/Http/Controllers/Api/SecurityController.php @@ -16,6 +16,10 @@ private function removeSensitiveData($team) $team->makeHidden([ 'private_key', ]); + } else { + $team->makeVisible([ + 'private_key', + ]); } return serializeApiResponse($team); diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php index 67f28523c..f4c334bf3 100644 --- a/app/Models/ApplicationDeploymentQueue.php +++ b/app/Models/ApplicationDeploymentQueue.php @@ -68,6 +68,10 @@ class ApplicationDeploymentQueue extends Model 'finished_at', ]; + protected $hidden = [ + 'logs', + ]; + protected $casts = [ 'pull_request_id' => 'integer', 'finished_at' => 'datetime', diff --git a/app/Models/CloudInitScript.php b/app/Models/CloudInitScript.php index 2c78cc582..9d2676aca 100644 --- a/app/Models/CloudInitScript.php +++ b/app/Models/CloudInitScript.php @@ -12,6 +12,10 @@ class CloudInitScript extends Model 'script', ]; + protected $hidden = [ + 'script', + ]; + protected function casts(): array { return [ diff --git a/app/Models/CloudProviderToken.php b/app/Models/CloudProviderToken.php index 026d11fba..02c662d9d 100644 --- a/app/Models/CloudProviderToken.php +++ b/app/Models/CloudProviderToken.php @@ -11,6 +11,10 @@ class CloudProviderToken extends BaseModel 'name', ]; + protected $hidden = [ + 'token', + ]; + protected $casts = [ 'token' => 'encrypted', ]; diff --git a/app/Models/DiscordNotificationSettings.php b/app/Models/DiscordNotificationSettings.php index e86598126..135c921f6 100644 --- a/app/Models/DiscordNotificationSettings.php +++ b/app/Models/DiscordNotificationSettings.php @@ -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', diff --git a/app/Models/EmailNotificationSettings.php b/app/Models/EmailNotificationSettings.php index 1277e45d9..7368bafbf 100644 --- a/app/Models/EmailNotificationSettings.php +++ b/app/Models/EmailNotificationSettings.php @@ -43,6 +43,16 @@ class EmailNotificationSettings extends Model 'traefik_outdated_email_notifications', ]; + protected $hidden = [ + 'smtp_from_address', + 'smtp_from_name', + 'smtp_recipients', + 'smtp_host', + 'smtp_username', + 'smtp_password', + 'resend_api_key', + ]; + protected $casts = [ 'smtp_enabled' => 'boolean', 'smtp_from_address' => 'encrypted', diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php index d5c3bfa28..d63611fac 100644 --- a/app/Models/InstanceSettings.php +++ b/app/Models/InstanceSettings.php @@ -48,6 +48,17 @@ class InstanceSettings extends Model 'is_mcp_server_enabled', ]; + protected $hidden = [ + 'smtp_from_address', + 'smtp_from_name', + 'smtp_recipients', + 'smtp_host', + 'smtp_username', + 'smtp_password', + 'resend_api_key', + 'sentinel_token', + ]; + protected $casts = [ 'smtp_enabled' => 'boolean', 'smtp_from_address' => 'encrypted', diff --git a/app/Models/OauthSetting.php b/app/Models/OauthSetting.php index 08e08d85b..e7999134a 100644 --- a/app/Models/OauthSetting.php +++ b/app/Models/OauthSetting.php @@ -13,6 +13,10 @@ class OauthSetting extends Model protected $fillable = ['provider', 'client_id', 'client_secret', 'redirect_uri', 'tenant', 'base_url', 'enabled']; + protected $hidden = [ + 'client_secret', + ]; + protected function clientSecret(): Attribute { return Attribute::make( diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php index 1521678f3..ee330aa19 100644 --- a/app/Models/PrivateKey.php +++ b/app/Models/PrivateKey.php @@ -41,6 +41,10 @@ class PrivateKey extends BaseModel 'fingerprint', ]; + protected $hidden = [ + 'private_key', + ]; + protected $casts = [ 'private_key' => 'encrypted', ]; diff --git a/app/Models/PushoverNotificationSettings.php b/app/Models/PushoverNotificationSettings.php index 5ad617ad6..dd0d81cc0 100644 --- a/app/Models/PushoverNotificationSettings.php +++ b/app/Models/PushoverNotificationSettings.php @@ -34,6 +34,11 @@ class PushoverNotificationSettings extends Model 'traefik_outdated_pushover_notifications', ]; + protected $hidden = [ + 'pushover_user_key', + 'pushover_api_token', + ]; + protected $casts = [ 'pushover_enabled' => 'boolean', 'pushover_user_key' => 'encrypted', diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php index 3f6ee51cc..dbc895825 100644 --- a/app/Models/S3Storage.php +++ b/app/Models/S3Storage.php @@ -26,6 +26,11 @@ class S3Storage extends BaseModel 'unusable_email_sent', ]; + protected $hidden = [ + 'key', + 'secret', + ]; + protected $casts = [ 'is_usable' => 'boolean', 'key' => 'encrypted', diff --git a/app/Models/SharedEnvironmentVariable.php b/app/Models/SharedEnvironmentVariable.php index eadc33ec2..8bb241240 100644 --- a/app/Models/SharedEnvironmentVariable.php +++ b/app/Models/SharedEnvironmentVariable.php @@ -30,6 +30,10 @@ class SharedEnvironmentVariable extends Model 'version', ]; + protected $hidden = [ + 'value', + ]; + protected $casts = [ 'key' => 'string', 'value' => 'encrypted', diff --git a/app/Models/SlackNotificationSettings.php b/app/Models/SlackNotificationSettings.php index d4f125fb5..62603685e 100644 --- a/app/Models/SlackNotificationSettings.php +++ b/app/Models/SlackNotificationSettings.php @@ -33,6 +33,10 @@ class SlackNotificationSettings extends Model 'traefik_outdated_slack_notifications', ]; + protected $hidden = [ + 'slack_webhook_url', + ]; + protected $casts = [ 'slack_enabled' => 'boolean', 'slack_webhook_url' => 'encrypted', diff --git a/app/Models/SslCertificate.php b/app/Models/SslCertificate.php index eb2175d44..2311cea72 100644 --- a/app/Models/SslCertificate.php +++ b/app/Models/SslCertificate.php @@ -20,6 +20,10 @@ class SslCertificate extends Model 'is_ca_certificate', ]; + protected $hidden = [ + 'ssl_private_key', + ]; + protected $casts = [ 'ssl_certificate' => 'encrypted', 'ssl_private_key' => 'encrypted', diff --git a/app/Models/TelegramNotificationSettings.php b/app/Models/TelegramNotificationSettings.php index 4930f45d4..8c644f9bc 100644 --- a/app/Models/TelegramNotificationSettings.php +++ b/app/Models/TelegramNotificationSettings.php @@ -49,6 +49,25 @@ class TelegramNotificationSettings extends Model 'telegram_notifications_traefik_outdated_thread_id', ]; + protected $hidden = [ + 'telegram_token', + 'telegram_chat_id', + 'telegram_notifications_deployment_success_thread_id', + 'telegram_notifications_deployment_failure_thread_id', + 'telegram_notifications_status_change_thread_id', + 'telegram_notifications_backup_success_thread_id', + 'telegram_notifications_backup_failure_thread_id', + 'telegram_notifications_scheduled_task_success_thread_id', + 'telegram_notifications_scheduled_task_failure_thread_id', + 'telegram_notifications_docker_cleanup_success_thread_id', + 'telegram_notifications_docker_cleanup_failure_thread_id', + 'telegram_notifications_server_disk_usage_thread_id', + 'telegram_notifications_server_reachable_thread_id', + 'telegram_notifications_server_unreachable_thread_id', + 'telegram_notifications_server_patch_thread_id', + 'telegram_notifications_traefik_outdated_thread_id', + ]; + protected $casts = [ 'telegram_enabled' => 'boolean', 'telegram_token' => 'encrypted', @@ -75,7 +94,8 @@ class TelegramNotificationSettings extends Model 'telegram_notifications_backup_failure_thread_id' => 'encrypted', 'telegram_notifications_scheduled_task_success_thread_id' => 'encrypted', 'telegram_notifications_scheduled_task_failure_thread_id' => 'encrypted', - 'telegram_notifications_docker_cleanup_thread_id' => 'encrypted', + 'telegram_notifications_docker_cleanup_success_thread_id' => 'encrypted', + 'telegram_notifications_docker_cleanup_failure_thread_id' => 'encrypted', 'telegram_notifications_server_disk_usage_thread_id' => 'encrypted', 'telegram_notifications_server_reachable_thread_id' => 'encrypted', 'telegram_notifications_server_unreachable_thread_id' => 'encrypted', diff --git a/app/Models/WebhookNotificationSettings.php b/app/Models/WebhookNotificationSettings.php index 731006181..c6a81b50a 100644 --- a/app/Models/WebhookNotificationSettings.php +++ b/app/Models/WebhookNotificationSettings.php @@ -33,6 +33,10 @@ class WebhookNotificationSettings extends Model 'traefik_outdated_webhook_notifications', ]; + protected $hidden = [ + 'webhook_url', + ]; + protected function casts(): array { return [ diff --git a/resources/views/components/notification/navbar.blade.php b/resources/views/components/notification/navbar.blade.php index 0ee3b8ee4..256c4d528 100644 --- a/resources/views/components/notification/navbar.blade.php +++ b/resources/views/components/notification/navbar.blade.php @@ -2,7 +2,7 @@