fix: add mass assignment protection to models

Replace $guarded = [] with explicit $fillable whitelists across all
models. Update controllers to use request->only($allowedFields) when
assigning request data. Switch Livewire components to forceFill() for
explicit mass assignment. Add integration tests for mass assignment
protection.
This commit is contained in:
Andras Bacsai
2026-03-28 12:32:57 +01:00
parent 48ba4ece3c
commit 67a4fcc2ab
20 changed files with 593 additions and 95 deletions
@@ -7,9 +7,18 @@ use App\Actions\Database\StopDatabase;
use App\Actions\Service\StartService;
use App\Actions\Service\StopService;
use App\Jobs\VolumeCloneJob;
use App\Models\Application;
use App\Models\Environment;
use App\Models\Project;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDocker;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Models\SwarmDocker;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
@@ -60,7 +69,7 @@ class ResourceOperations extends Component
$uuid = (string) new Cuid2;
$server = $new_destination->server;
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
if ($this->resource->getMorphClass() === Application::class) {
$new_resource = clone_application($this->resource, $new_destination, ['uuid' => $uuid], $this->cloneVolumeData);
$route = route('project.application.configuration', [
@@ -71,21 +80,21 @@ class ResourceOperations extends Component
return redirect()->to($route);
} elseif (
$this->resource->getMorphClass() === \App\Models\StandalonePostgresql::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneMongodb::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneMysql::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneMariadb::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneRedis::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneKeydb::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneDragonfly::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class
$this->resource->getMorphClass() === StandalonePostgresql::class ||
$this->resource->getMorphClass() === StandaloneMongodb::class ||
$this->resource->getMorphClass() === StandaloneMysql::class ||
$this->resource->getMorphClass() === StandaloneMariadb::class ||
$this->resource->getMorphClass() === StandaloneRedis::class ||
$this->resource->getMorphClass() === StandaloneKeydb::class ||
$this->resource->getMorphClass() === StandaloneDragonfly::class ||
$this->resource->getMorphClass() === StandaloneClickhouse::class
) {
$uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate([
'id',
'created_at',
'updated_at',
])->fill([
])->forceFill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
'status' => 'exited',
@@ -133,7 +142,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
])->fill([
])->forceFill([
'name' => $newName,
'resource_id' => $new_resource->id,
]);
@@ -162,7 +171,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
])->fill([
])->forceFill([
'resource_id' => $new_resource->id,
]);
$newStorage->save();
@@ -175,7 +184,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
])->fill([
])->forceFill([
'uuid' => $uuid,
'database_id' => $new_resource->id,
'database_type' => $new_resource->getMorphClass(),
@@ -194,7 +203,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
])->fill($payload);
])->forceFill($payload);
$newEnvironmentVariable->save();
}
@@ -211,7 +220,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
])->fill([
])->forceFill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
'destination_id' => $new_destination->id,
@@ -232,7 +241,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
])->fill([
])->forceFill([
'uuid' => (string) new Cuid2,
'service_id' => $new_resource->id,
'team_id' => currentTeam()->id,
@@ -246,7 +255,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
])->fill([
])->forceFill([
'resourceable_id' => $new_resource->id,
'resourceable_type' => $new_resource->getMorphClass(),
]);
@@ -254,9 +263,9 @@ class ResourceOperations extends Component
}
foreach ($new_resource->applications() as $application) {
$application->update([
$application->forceFill([
'status' => 'exited',
]);
])->save();
$persistentVolumes = $application->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
@@ -271,7 +280,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
])->fill([
])->forceFill([
'name' => $newName,
'resource_id' => $application->id,
]);
@@ -296,9 +305,9 @@ class ResourceOperations extends Component
}
foreach ($new_resource->databases() as $database) {
$database->update([
$database->forceFill([
'status' => 'exited',
]);
])->save();
$persistentVolumes = $database->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
@@ -313,7 +322,7 @@ class ResourceOperations extends Component
'id',
'created_at',
'updated_at',
])->fill([
])->forceFill([
'name' => $newName,
'resource_id' => $database->id,
]);
@@ -354,9 +363,9 @@ class ResourceOperations extends Component
try {
$this->authorize('update', $this->resource);
$new_environment = Environment::ownedByCurrentTeam()->findOrFail($environment_id);
$this->resource->update([
$this->resource->forceFill([
'environment_id' => $environment_id,
]);
])->save();
if ($this->resource->type() === 'application') {
$route = route('project.application.configuration', [
'project_uuid' => $new_environment->project->uuid,