diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
index f721819976..04b574abe9 100644
--- a/app/Http/Controllers/Api/ApplicationsController.php
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -2438,7 +2438,7 @@ class ApplicationsController extends Controller
#[OA\Get(
summary: 'Get application logs.',
- description: 'Get application logs by UUID.',
+ description: 'Get application logs by UUID. Requires the `read:sensitive` or `root` token ability.',
path: '/applications/{uuid}/logs',
operationId: 'get-application-logs-by-uuid',
security: [
@@ -2505,7 +2505,7 @@ class ApplicationsController extends Controller
)]
#[OA\Get(
summary: 'Get preview application logs.',
- description: 'Get runtime container logs for a preview deployment by application UUID and pull request ID.',
+ description: 'Get runtime container logs for a preview deployment by application UUID and pull request ID. Requires the `read:sensitive` or `root` token ability.',
path: '/applications/{uuid}/previews/{pull_request_id}/logs',
operationId: 'get-preview-application-logs-by-pull-request-id',
security: [
diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php
index 57af00469c..b4b9bfbb5f 100644
--- a/app/Http/Controllers/Api/DatabasesController.php
+++ b/app/Http/Controllers/Api/DatabasesController.php
@@ -2612,7 +2612,7 @@ class DatabasesController extends Controller
#[OA\Get(
summary: 'Get database logs.',
- description: 'Get database logs by UUID.',
+ description: 'Get database logs by UUID. Requires the `read:sensitive` or `root` token ability.',
path: '/databases/{uuid}/logs',
operationId: 'get-database-logs-by-uuid',
security: [
diff --git a/app/Http/Controllers/Api/ScheduledTasksController.php b/app/Http/Controllers/Api/ScheduledTasksController.php
index c0eff20521..910be871ad 100644
--- a/app/Http/Controllers/Api/ScheduledTasksController.php
+++ b/app/Http/Controllers/Api/ScheduledTasksController.php
@@ -21,10 +21,18 @@ class ScheduledTasksController extends Controller
'application_id',
'service_id',
]);
+ if (! $this->canReadSensitive()) {
+ $task->makeHidden(['command']);
+ }
return serializeApiResponse($task);
}
+ private function canReadSensitive(): bool
+ {
+ return request()->attributes->get('can_read_sensitive', false) === true;
+ }
+
private function resolveApplication(Request $request, int $teamId): ?Application
{
return Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
@@ -218,6 +226,9 @@ class ScheduledTasksController extends Controller
$executions = $task->executions()->get()->map(function ($execution) {
$execution->makeHidden(['id', 'scheduled_task_id']);
+ if (! $this->canReadSensitive()) {
+ $execution->makeHidden(['message']);
+ }
return serializeApiResponse($execution);
});
diff --git a/app/Http/Controllers/Api/ServiceApplicationsController.php b/app/Http/Controllers/Api/ServiceApplicationsController.php
index adb0cb678e..49380f2a42 100644
--- a/app/Http/Controllers/Api/ServiceApplicationsController.php
+++ b/app/Http/Controllers/Api/ServiceApplicationsController.php
@@ -378,7 +378,7 @@ class ServiceApplicationsController extends Controller
#[OA\Get(
summary: 'Get service application logs',
- description: 'Get Docker logs for a single compose service container.',
+ description: 'Get Docker logs for a single compose service container. Requires the `read:sensitive` or `root` token ability.',
path: '/services/{uuid}/applications/{app_uuid}/logs',
operationId: 'get-service-application-logs-by-service-and-app-uuid',
security: [
@@ -447,7 +447,7 @@ class ServiceApplicationsController extends Controller
)]
#[OA\Post(
summary: 'Get service application logs',
- description: 'Get Docker logs for a single compose service container.',
+ description: 'Get Docker logs for a single compose service container. Requires the `read:sensitive` or `root` token ability.',
path: '/services/{uuid}/applications/{app_uuid}/logs',
operationId: 'post-service-application-logs-by-service-and-app-uuid',
security: [['bearerAuth' => []]],
diff --git a/app/Http/Controllers/Api/ServiceDatabasesController.php b/app/Http/Controllers/Api/ServiceDatabasesController.php
index 62d1292192..795778ffb0 100644
--- a/app/Http/Controllers/Api/ServiceDatabasesController.php
+++ b/app/Http/Controllers/Api/ServiceDatabasesController.php
@@ -358,7 +358,7 @@ class ServiceDatabasesController extends Controller
#[OA\Get(
summary: 'Get service database logs',
- description: 'Get Docker logs for a compose database container.',
+ description: 'Get Docker logs for a compose database container. Requires the `read:sensitive` or `root` token ability.',
path: '/services/{uuid}/databases/{database_uuid}/logs',
operationId: 'get-service-database-logs-by-service-and-database-uuid',
security: [['bearerAuth' => []]],
diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php
index f78f2a0f3d..fdcaf57f5b 100644
--- a/app/Http/Controllers/Api/ServicesController.php
+++ b/app/Http/Controllers/Api/ServicesController.php
@@ -873,7 +873,7 @@ class ServicesController extends Controller
#[OA\Get(
summary: 'Get service logs.',
- description: 'Get logs for a specific service sub-resource by service UUID. The `sub_service_name` query parameter must match the `name` field of one of the service applications or databases returned by `GET /services/{uuid}`.',
+ description: 'Get logs for a specific service sub-resource by service UUID. The `sub_service_name` query parameter must match the `name` field of one of the service applications or databases returned by `GET /services/{uuid}`. Requires the `read:sensitive` or `root` token ability.',
path: '/services/{uuid}/logs',
operationId: 'get-service-logs-by-uuid',
security: [
diff --git a/app/Livewire/Project/Shared/GetLogs.php b/app/Livewire/Project/Shared/GetLogs.php
index 71e8ad8560..e809c1aae5 100644
--- a/app/Livewire/Project/Shared/GetLogs.php
+++ b/app/Livewire/Project/Shared/GetLogs.php
@@ -150,6 +150,9 @@ class GetLogs extends Component
#[Renderless]
public function getLogs(?string $since = null): string
{
+ if (! $this->canReadLogs()) {
+ return 'Unauthorized.';
+ }
if (! Server::ownedByCurrentTeam()->where('id', $this->server->id)->exists()) {
return 'Unauthorized.';
}
@@ -174,6 +177,9 @@ class GetLogs extends Component
public function copyLogs(): string
{
+ if (! $this->canReadLogs()) {
+ return '';
+ }
if (! Server::ownedByCurrentTeam()->where('id', $this->server->id)->exists()) {
return '';
}
@@ -191,6 +197,9 @@ class GetLogs extends Component
public function downloadAllLogs(): string
{
+ if (! $this->canReadLogs()) {
+ return '';
+ }
if (! Server::ownedByCurrentTeam()->where('id', $this->server->id)->exists()) {
return '';
}
@@ -263,8 +272,19 @@ class GetLogs extends Component
return "({$command}) 2>&1 | head -c ".($maxBytes + 1);
}
+ /**
+ * Container output can contain secrets, so only users who can edit the resource (or the server,
+ * for server-level containers) may read it.
+ */
+ private function canReadLogs(): bool
+ {
+ return auth()->user()?->can('update', $this->resource ?? $this->server) ?? false;
+ }
+
public function render()
{
- return view('livewire.project.shared.get-logs');
+ return view('livewire.project.shared.get-logs', [
+ 'canReadLogs' => $this->canReadLogs(),
+ ]);
}
}
diff --git a/app/Livewire/Project/Shared/ScheduledTask/Executions.php b/app/Livewire/Project/Shared/ScheduledTask/Executions.php
index e95fd2f5a1..cf426e0ca8 100644
--- a/app/Livewire/Project/Shared/ScheduledTask/Executions.php
+++ b/app/Livewire/Project/Shared/ScheduledTask/Executions.php
@@ -126,6 +126,10 @@ class Executions extends Component
return collect();
}
+ if (! $this->canReadOutput()) {
+ return collect(['Hidden (only admins can view)']);
+ }
+
if (! $this->selectedExecution->message) {
return collect(['Waiting for task output...']);
}
@@ -138,7 +142,7 @@ class Executions extends Component
public function downloadLogs(int $executionId)
{
$execution = $this->executions->firstWhere('id', $executionId);
- if (! $execution) {
+ if (! $execution || ! $this->canReadOutput()) {
return;
}
@@ -147,9 +151,17 @@ class Executions extends Component
}, 'task-execution-'.$execution->id.'.log');
}
+ /**
+ * Task output can contain secrets, so only users who can edit the task's resource may read it.
+ */
+ public function canReadOutput(): bool
+ {
+ return auth()->user()?->can('update', $this->task->application ?? $this->task->service) ?? false;
+ }
+
public function hasMoreLogs()
{
- if (! $this->selectedExecution || ! $this->selectedExecution->message) {
+ if (! $this->selectedExecution || ! $this->selectedExecution->message || ! $this->canReadOutput()) {
return false;
}
$lines = collect(explode("\n", $this->selectedExecution->message));
diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php
index c121f1b93b..3bfe0700d9 100644
--- a/app/Livewire/Project/Shared/ScheduledTask/Show.php
+++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php
@@ -106,7 +106,7 @@ class Show extends Component
} else {
$this->isEnabled = $this->task->enabled;
$this->name = $this->task->name;
- $this->command = $this->task->command;
+ $this->command = auth()->user()?->can('update', $this->resource) ? $this->task->command : 'Hidden (only admins can view)';
$this->frequency = $this->task->frequency;
$this->container = $this->task->container;
$this->timeout = $this->task->timeout ?? 300;
diff --git a/resources/views/livewire/project/shared/get-logs.blade.php b/resources/views/livewire/project/shared/get-logs.blade.php
index 2fad7cfa7a..e05ef34eaa 100644
--- a/resources/views/livewire/project/shared/get-logs.blade.php
+++ b/resources/views/livewire/project/shared/get-logs.blade.php
@@ -1,3 +1,8 @@
+@if (! $canReadLogs)
+