fix(security): enforce team access on mutable actions

Authorize cloud provider token access, audit sensitive operations, and
standardize public IDs across deployment and resource flows.
This commit is contained in:
Andras Bacsai committed 2026-06-04 11:03:06 +02:00
1 parent 9dca7ca351
commit 062ad57740
83 files changed
+824 -278

No files matched your search

+1 -2
View File
@@ -6,7 +6,6 @@ use App\Models\Project;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class AddEmpty extends Component
{
@@ -38,7 +37,7 @@ class AddEmpty extends Component
'name' => $this->name,
'description' => $this->description,
'team_id' => currentTeam()->id,
'uuid' => (string) new Cuid2,
'uuid' => new_public_id(),
]);
$productionEnvironment = $project->environments()->where('name', 'production')->first();
+1 -2
View File
@@ -13,7 +13,6 @@ use Illuminate\Support\Collection;
use Livewire\Component;
use Livewire\Features\SupportEvents\Event;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
class General extends Component
{
@@ -549,7 +548,7 @@ class General extends Component
try {
$this->authorize('update', $this->application);
$uuid = new Cuid2;
$uuid = new_public_id();
$domain = generateUrl(server: $this->application->destination->server, random: $uuid);
$sanitizedKey = str($serviceName)->replace('-', '_')->replace('.', '_')->toString();
$this->parsedServiceDomains[$sanitizedKey]['domain'] = $domain;
+1 -2
View File
@@ -7,7 +7,6 @@ use App\Actions\Docker\GetContainersStatus;
use App\Models\Application;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Heading extends Component
{
@@ -129,7 +128,7 @@ class Heading extends Component
protected function setDeploymentUuid()
{
$this->deploymentUuid = new Cuid2;
$this->deploymentUuid = new_public_id();
$this->parameters['deployment_uuid'] = $this->deploymentUuid;
}
@@ -9,7 +9,6 @@ use App\Models\ApplicationPreview;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Previews extends Component
{
@@ -312,7 +311,7 @@ class Previews extends Component
protected function setDeploymentUuid()
{
$this->deployment_uuid = new Cuid2;
$this->deployment_uuid = new_public_id();
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
}
@@ -6,7 +6,6 @@ use App\Models\ApplicationPreview;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
class PreviewsCompose extends Component
{
@@ -64,7 +63,7 @@ class PreviewsCompose extends Component
if (empty($domain_string)) {
$server = $this->preview->application->destination->server;
$template = $this->preview->application->preview_url_template;
$random = new Cuid2;
$random = new_public_id();
// Generate a unique domain like main app services do
$generated_fqdn = generateUrl(server: $server, random: $random);
@@ -79,7 +78,7 @@ class PreviewsCompose extends Component
$domain_list = explode(',', $domain_string);
$preview_fqdns = [];
$template = $this->preview->application->preview_url_template;
$random = new Cuid2;
$random = new_public_id();
foreach ($domain_list as $single_domain) {
$single_domain = trim($single_domain);
@@ -6,7 +6,6 @@ use App\Models\Application;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Rollback extends Component
{
@@ -52,7 +51,7 @@ class Rollback extends Component
$commit = validateGitRef($commit, 'rollback commit');
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $this->application,
+8 -9
View File
@@ -13,7 +13,6 @@ use App\Models\Server;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class CloneMe extends Component
{
@@ -64,7 +63,7 @@ class CloneMe extends Component
->servers()
->get()
->reject(fn ($server) => $server->isBuildServer());
$this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug();
$this->newName = str($this->project->name.'-clone-'.new_public_id())->slug();
}
public function toggleVolumeCloning(bool $value)
@@ -112,7 +111,7 @@ class CloneMe extends Component
if ($this->environment->name !== 'production') {
$project->environments()->create([
'name' => $this->environment->name,
'uuid' => (string) new Cuid2,
'uuid' => new_public_id(),
]);
}
$environment = $project->environments->where('name', $this->environment->name)->first();
@@ -124,7 +123,7 @@ class CloneMe extends Component
$project = $this->project;
$environment = $this->project->environments()->create([
'name' => $this->newName,
'uuid' => (string) new Cuid2,
'uuid' => new_public_id(),
]);
}
$applications = $this->environment->applications;
@@ -138,7 +137,7 @@ class CloneMe extends Component
}
foreach ($databases as $database) {
$uuid = (string) new Cuid2;
$uuid = new_public_id();
$newDatabase = $database->replicate([
'id',
'created_at',
@@ -229,7 +228,7 @@ class CloneMe extends Component
$scheduledBackups = $database->scheduledBackups()->get();
foreach ($scheduledBackups as $backup) {
$uuid = (string) new Cuid2;
$uuid = new_public_id();
$newBackup = $backup->replicate([
'id',
'created_at',
@@ -258,7 +257,7 @@ class CloneMe extends Component
}
foreach ($services as $service) {
$uuid = (string) new Cuid2;
$uuid = new_public_id();
$newService = $service->replicate([
'id',
'created_at',
@@ -282,7 +281,7 @@ class CloneMe extends Component
'created_at',
'updated_at',
])->fill([
'uuid' => (string) new Cuid2,
'uuid' => new_public_id(),
'service_id' => $newService->id,
'team_id' => currentTeam()->id,
]);
@@ -413,7 +412,7 @@ class CloneMe extends Component
$scheduledBackups = $database->scheduledBackups()->get();
foreach ($scheduledBackups as $backup) {
$uuid = (string) new Cuid2;
$uuid = new_public_id();
$newBackup = $backup->replicate([
'id',
'created_at',
@@ -2,13 +2,20 @@
namespace App\Livewire\Project\Database;
use App\Models\StandalonePostgresql;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Validate;
use Livewire\Component;
class InitScript extends Component
{
use AuthorizesRequests;
#[Locked]
public StandalonePostgresql $database;
#[Locked]
public array $script;
@@ -35,6 +42,7 @@ class InitScript extends Component
public function submit()
{
try {
$this->authorize('update', $this->database);
$this->validate();
$this->script['index'] = $this->index;
$this->script['content'] = $this->content;
@@ -48,6 +56,7 @@ class InitScript extends Component
public function delete()
{
try {
$this->authorize('update', $this->database);
$this->dispatch('delete_init_script', $this->script);
} catch (Exception $e) {
return handleError($e, $this);
+1 -2
View File
@@ -7,7 +7,6 @@ use App\Models\Project;
use App\Services\DockerImageParser;
use App\Support\ValidationPatterns;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class DockerImage extends Component
{
@@ -130,7 +129,7 @@ class DockerImage extends Component
$imageTag = $parser->isImageHash() ? 'sha256-'.$parser->getTag() : $parser->getTag();
$application = Application::create([
'name' => 'docker-image-'.new Cuid2,
'name' => 'docker-image-'.new_public_id(),
'repository_project_id' => 0,
'git_repository' => 'coollabsio/coolify',
'git_branch' => 'main',
+1 -2
View File
@@ -4,7 +4,6 @@ namespace App\Livewire\Project\New;
use App\Models\Project;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class EmptyProject extends Component
{
@@ -13,7 +12,7 @@ class EmptyProject extends Component
$project = Project::create([
'name' => generate_random_name(),
'team_id' => currentTeam()->id,
'uuid' => (string) new Cuid2,
'uuid' => new_public_id(),
]);
return redirectRoute($this, 'project.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $project->environments->first()->uuid]);
@@ -6,7 +6,6 @@ use App\Models\Application;
use App\Models\GithubApp;
use App\Models\Project;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class SimpleDockerfile extends Component
{
@@ -48,7 +47,7 @@ CMD ["nginx", "-g", "daemon off;"]
$port = 80;
}
$application = Application::create([
'name' => 'dockerfile-'.new Cuid2,
'name' => 'dockerfile-'.new_public_id(),
'repository_project_id' => 0,
'git_repository' => 'coollabsio/coolify',
'git_branch' => 'main',
+1 -2
View File
@@ -8,7 +8,6 @@ use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Danger extends Component
{
@@ -39,7 +38,7 @@ class Danger extends Component
public function mount()
{
$parameters = get_route_parameters();
$this->modalId = new Cuid2;
$this->modalId = new_public_id();
$this->projectUuid = data_get($parameters, 'project_uuid');
$this->environmentUuid = data_get($parameters, 'environment_uuid');
+1 -2
View File
@@ -10,7 +10,6 @@ use App\Models\StandaloneDocker;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Destination extends Component
{
@@ -80,7 +79,7 @@ class Destination extends Component
return;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$server = Server::ownedByCurrentTeam()->findOrFail($server_id);
$destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail();
$result = queue_application_deployment(
@@ -6,12 +6,15 @@ use App\Models\Application;
use App\Models\Server;
use App\Models\Service;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Attributes\On;
use Livewire\Component;
class ExecuteContainerCommand extends Component
{
use AuthorizesRequests;
public $selected_container = 'default';
public Collection $containers;
@@ -40,6 +43,7 @@ class ExecuteContainerCommand extends Component
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::ownedByCurrentTeam()->where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->authorize('view', $this->resource);
if ($this->resource->destination->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->destination->server);
}
@@ -56,6 +60,7 @@ class ExecuteContainerCommand extends Component
abort(404);
}
$this->resource = $resource;
$this->authorize('view', $this->resource);
if ($this->resource->destination->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->destination->server);
}
@@ -63,6 +68,7 @@ class ExecuteContainerCommand extends Component
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::ownedByCurrentTeam()->where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->authorize('view', $this->resource);
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}
@@ -70,6 +76,7 @@ class ExecuteContainerCommand extends Component
} elseif (data_get($this->parameters, 'server_uuid')) {
$this->type = 'server';
$this->resource = Server::ownedByCurrentTeam()->where('uuid', $this->parameters['server_uuid'])->firstOrFail();
$this->authorize('view', $this->resource);
$this->servers = $this->servers->push($this->resource);
}
$this->servers = $this->servers->sortByDesc(fn ($server) => $server->isTerminalEnabled());
@@ -152,7 +159,9 @@ class ExecuteContainerCommand extends Component
public function connectToServer()
{
try {
$this->authorize('canAccessTerminal');
$server = $this->servers->first();
$this->authorize('view', $server);
if ($server->isForceDisabled()) {
throw new \RuntimeException('Server is disabled.');
}
@@ -181,6 +190,7 @@ class ExecuteContainerCommand extends Component
return;
}
try {
$this->authorize('canAccessTerminal');
// Validate container name format
if (! ValidationPatterns::isValidContainerName($this->selected_container)) {
throw new \InvalidArgumentException('Invalid container name format');
@@ -198,6 +208,8 @@ class ExecuteContainerCommand extends Component
throw new \RuntimeException('Invalid server configuration.');
}
$this->authorize('view', $server);
if ($server->isForceDisabled()) {
throw new \RuntimeException('Server is disabled.');
}
@@ -22,7 +22,6 @@ use App\Models\StandaloneRedis;
use App\Models\SwarmDocker;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class ResourceOperations extends Component
{
@@ -66,7 +65,7 @@ class ResourceOperations extends Component
if (! $new_destination) {
return $this->addError('destination_id', 'Destination not found.');
}
$uuid = (string) new Cuid2;
$uuid = new_public_id();
$server = $new_destination->server;
if ($this->resource->getMorphClass() === Application::class) {
@@ -89,7 +88,7 @@ class ResourceOperations extends Component
$this->resource->getMorphClass() === StandaloneDragonfly::class ||
$this->resource->getMorphClass() === StandaloneClickhouse::class
) {
$uuid = (string) new Cuid2;
$uuid = new_public_id();
$new_resource = $this->resource->replicate([
'id',
'created_at',
@@ -180,7 +179,7 @@ class ResourceOperations extends Component
$scheduledBackups = $this->resource->scheduledBackups()->get();
foreach ($scheduledBackups as $backup) {
$uuid = (string) new Cuid2;
$uuid = new_public_id();
$newBackup = $backup->replicate([
'id',
'created_at',
@@ -216,7 +215,7 @@ class ResourceOperations extends Component
return redirect()->to($route);
} elseif ($this->resource->type() === 'service') {
$uuid = (string) new Cuid2;
$uuid = new_public_id();
$new_resource = $this->resource->replicate([
'id',
'created_at',
@@ -243,7 +242,7 @@ class ResourceOperations extends Component
'created_at',
'updated_at',
])->fill([
'uuid' => (string) new Cuid2,
'uuid' => new_public_id(),
'service_id' => $new_resource->id,
'team_id' => currentTeam()->id,
]);
+7
View File
@@ -5,11 +5,14 @@ namespace App\Livewire\Project\Shared;
use App\Helpers\SshMultiplexingHelper;
use App\Models\Server;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\On;
use Livewire\Component;
class Terminal extends Component
{
use AuthorizesRequests;
public bool $hasShell = true;
public bool $isTerminalConnected = false;
@@ -32,7 +35,11 @@ class Terminal extends Component
#[On('send-terminal-command')]
public function sendTerminalCommand($isContainer, $identifier, $serverUuid)
{
$this->authorize('canAccessTerminal');
$server = Server::ownedByCurrentTeam()->whereUuid($serverUuid)->firstOrFail();
$this->authorize('view', $server);
if (! $server->isTerminalEnabled() || $server->isForceDisabled()) {
abort(403, 'Terminal access is disabled on this server.');
}
+1 -2
View File
@@ -7,7 +7,6 @@ use App\Models\Project;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Show extends Component
{
@@ -49,7 +48,7 @@ class Show extends Component
$environment = Environment::create([
'name' => $this->name,
'project_id' => $this->project->id,
'uuid' => (string) new Cuid2,
'uuid' => new_public_id(),
]);
return redirectRoute($this, 'project.resource.index', [