From c9b9fd073b8443cb2ff30d9c282b8f24e0ddcdfb Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 9 Nov 2026 17:53:38 +0100 Subject: [PATCH] fix(destination): limit stop and redeploy to the app's own servers Stop now returns 404 unless the application runs on the server, either as its main server or as an additional server. Redeploy now returns 404 unless the network is the application's main destination or one of its additional networks on that server. Add a security changelog entry. --- ...estricted-the-redeploy-and-stop-actions.md | 4 +++ app/Livewire/Project/Shared/Destination.php | 29 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .changes/restricted-the-redeploy-and-stop-actions.md diff --git a/.changes/restricted-the-redeploy-and-stop-actions.md b/.changes/restricted-the-redeploy-and-stop-actions.md new file mode 100644 index 0000000000..ada690dfca --- /dev/null +++ b/.changes/restricted-the-redeploy-and-stop-actions.md @@ -0,0 +1,4 @@ +--- +type: security +--- +Restricted the `Redeploy` and `Stop` actions of an application destination to the servers that the application runs on diff --git a/app/Livewire/Project/Shared/Destination.php b/app/Livewire/Project/Shared/Destination.php index b92e49a85b..aa55564af3 100644 --- a/app/Livewire/Project/Shared/Destination.php +++ b/app/Livewire/Project/Shared/Destination.php @@ -107,11 +107,35 @@ class Destination extends Component return $server; } + /** + * Whether the application runs on the server, as its main server or as an additional server. + */ + private function runsOnServer(Server $server): bool + { + return $this->resource->destination->server_id == $server->id + || $this->resource->additional_networks()->wherePivot('server_id', $server->id)->exists(); + } + + /** + * Whether the network is the main destination or an additional network of the application on the server. + */ + private function isAttachedDestination(StandaloneDocker $destination, Server $server): bool + { + if ($this->resource->destination_type === StandaloneDocker::class && $this->resource->destination_id == $destination->id) { + return true; + } + + return $this->resource->additional_networks()->wherePivot('server_id', $server->id)->whereKey($destination->id)->exists(); + } + public function stop($serverId) { try { $this->authorize('deploy', $this->resource); $server = $this->resourceServers()->findOrFail((int) $serverId); + if (! $this->runsOnServer($server)) { + abort(404); + } StopApplicationOneServer::run($this->resource, $server); auditLog('ui.application.destination_stopped', [ 'team_id' => $this->resource->team()?->id, @@ -139,8 +163,11 @@ class Destination extends Component if (! $server) { return; } - $deployment_uuid = new_public_id(); $destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail(); + if (! $this->isAttachedDestination($destination, $server)) { + abort(404); + } + $deployment_uuid = new_public_id(); $result = queue_application_deployment( deployment_uuid: $deployment_uuid, application: $this->resource,