fix(models): replace forceFill/forceCreate with fill/create and add fillable guards

Replace all uses of `forceFill`, `forceCreate`, and `forceFill` with their
non-force equivalents across models, actions, controllers, and Livewire
components. Add explicit `$fillable` arrays to all affected Eloquent models
to enforce mass assignment protection.

Add ModelFillableCreationTest and ModelFillableRegressionTest to verify that
model creation respects fillable constraints and prevent regressions.
This commit is contained in:
Andras Bacsai committed 2026-03-31 13:45:31 +02:00
1 parent 047aff1c82
commit 1a603a10ed
79 files changed
+1411 -142

No files matched your search

+10 -2
View File
@@ -203,6 +203,14 @@ class Application extends BaseModel
'restart_count',
'last_restart_at',
'last_restart_type',
'uuid',
'environment_id',
'destination_id',
'destination_type',
'source_id',
'source_type',
'repository_project_id',
'private_key_id',
];
protected $appends = ['server_status'];
@@ -262,7 +270,7 @@ class Application extends BaseModel
}
}
if (count($payload) > 0) {
$application->forceFill($payload);
$application->fill($payload);
}
// Buildpack switching cleanup logic
@@ -299,7 +307,7 @@ class Application extends BaseModel
}
});
static::created(function ($application) {
ApplicationSetting::forceCreate([
ApplicationSetting::create([
'application_id' => $application->id,
]);
$application->compose_parsing_version = self::$parserVersion;
@@ -44,6 +44,7 @@ class ApplicationDeploymentQueue extends Model
'application_id',
'deployment_uuid',
'pull_request_id',
'docker_registry_image_tag',
'force_rebuild',
'commit',
'status',
+2 -1
View File
@@ -11,6 +11,7 @@ class ApplicationPreview extends BaseModel
use SoftDeletes;
protected $fillable = [
'uuid',
'application_id',
'pull_request_id',
'pull_request_html_url',
@@ -62,7 +63,7 @@ class ApplicationPreview extends BaseModel
});
static::saving(function ($preview) {
if ($preview->isDirty('status')) {
$preview->forceFill(['last_online_at' => now()]);
$preview->last_online_at = now();
}
});
}
+1
View File
@@ -29,6 +29,7 @@ class ApplicationSetting extends Model
];
protected $fillable = [
'application_id',
'is_static',
'is_git_submodules_enabled',
'is_git_lfs_enabled',
+1
View File
@@ -5,6 +5,7 @@ namespace App\Models;
class CloudProviderToken extends BaseModel
{
protected $fillable = [
'team_id',
'provider',
'token',
'name',
+2
View File
@@ -28,6 +28,8 @@ class Environment extends BaseModel
protected $fillable = [
'name',
'description',
'project_id',
'uuid',
];
protected static function booted()
+2
View File
@@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
class GithubApp extends BaseModel
{
protected $fillable = [
'team_id',
'private_key_id',
'name',
'organization',
'api_url',
+4 -2
View File
@@ -27,6 +27,8 @@ class Project extends BaseModel
protected $fillable = [
'name',
'description',
'team_id',
'uuid',
];
/**
@@ -51,10 +53,10 @@ class Project extends BaseModel
protected static function booted()
{
static::created(function ($project) {
ProjectSetting::forceCreate([
ProjectSetting::create([
'project_id' => $project->id,
]);
Environment::forceCreate([
Environment::create([
'name' => 'production',
'project_id' => $project->id,
'uuid' => (string) new Cuid2,
+3 -1
View File
@@ -6,7 +6,9 @@ use Illuminate\Database\Eloquent\Model;
class ProjectSetting extends Model
{
protected $fillable = [];
protected $fillable = [
'project_id',
];
public function project()
{
+2
View File
@@ -9,6 +9,8 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
class ScheduledDatabaseBackup extends BaseModel
{
protected $fillable = [
'uuid',
'team_id',
'description',
'enabled',
'save_s3',
@@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ScheduledDatabaseBackupExecution extends BaseModel
{
protected $fillable = [
'uuid',
'scheduled_database_backup_id',
'status',
'message',
'size',
+4
View File
@@ -30,12 +30,16 @@ class ScheduledTask extends BaseModel
use HasSafeStringAttribute;
protected $fillable = [
'uuid',
'enabled',
'name',
'command',
'frequency',
'container',
'timeout',
'team_id',
'application_id',
'service_id',
];
public static function ownedByCurrentTeamAPI(int $teamId)
+1
View File
@@ -23,6 +23,7 @@ use OpenApi\Attributes as OA;
class ScheduledTaskExecution extends BaseModel
{
protected $fillable = [
'scheduled_task_id',
'status',
'message',
'finished_at',
+2 -1
View File
@@ -135,7 +135,7 @@ class Server extends BaseModel
$payload['ip_previous'] = $server->getOriginal('ip');
}
}
$server->forceFill($payload);
$server->fill($payload);
});
static::saved(function ($server) {
if ($server->wasChanged('private_key_id') || $server->privateKey?->isDirty()) {
@@ -265,6 +265,7 @@ class Server extends BaseModel
'detected_traefik_version',
'traefik_outdated_info',
'server_metadata',
'ip_previous',
];
use HasSafeStringAttribute;
+1
View File
@@ -54,6 +54,7 @@ use OpenApi\Attributes as OA;
class ServerSetting extends Model
{
protected $fillable = [
'server_id',
'is_swarm_manager',
'is_jump_server',
'is_build_server',
+5
View File
@@ -49,6 +49,7 @@ class Service extends BaseModel
private static $parserVersion = '5';
protected $fillable = [
'uuid',
'name',
'description',
'docker_compose_raw',
@@ -58,6 +59,10 @@ class Service extends BaseModel
'config_hash',
'compose_parsing_version',
'is_container_label_escape_enabled',
'environment_id',
'server_id',
'destination_id',
'destination_type',
];
protected $appends = ['server_status', 'status'];
+2 -1
View File
@@ -12,6 +12,7 @@ class ServiceApplication extends BaseModel
use HasFactory, SoftDeletes;
protected $fillable = [
'service_id',
'name',
'human_name',
'description',
@@ -39,7 +40,7 @@ class ServiceApplication extends BaseModel
});
static::saving(function ($service) {
if ($service->isDirty('status')) {
$service->forceFill(['last_online_at' => now()]);
$service->last_online_at = now();
}
});
}
+2 -1
View File
@@ -10,6 +10,7 @@ class ServiceDatabase extends BaseModel
use HasFactory, SoftDeletes;
protected $fillable = [
'service_id',
'name',
'human_name',
'description',
@@ -44,7 +45,7 @@ class ServiceDatabase extends BaseModel
});
static::saving(function ($service) {
if ($service->isDirty('status')) {
$service->forceFill(['last_online_at' => now()]);
$service->last_online_at = now();
}
});
}
+5 -1
View File
@@ -14,6 +14,7 @@ class StandaloneClickhouse extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [
'uuid',
'name',
'description',
'clickhouse_admin_user',
@@ -40,6 +41,9 @@ class StandaloneClickhouse extends BaseModel
'public_port_timeout',
'custom_docker_run_options',
'clickhouse_db',
'destination_type',
'destination_id',
'environment_id',
];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@@ -71,7 +75,7 @@ class StandaloneClickhouse extends BaseModel
});
static::saving(function ($database) {
if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]);
$database->last_online_at = now();
}
});
}
+1
View File
@@ -13,6 +13,7 @@ class StandaloneDocker extends BaseModel
use HasSafeStringAttribute;
protected $fillable = [
'server_id',
'name',
'network',
];
+5 -1
View File
@@ -14,6 +14,7 @@ class StandaloneDragonfly extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [
'uuid',
'name',
'description',
'dragonfly_password',
@@ -39,6 +40,9 @@ class StandaloneDragonfly extends BaseModel
'public_port_timeout',
'enable_ssl',
'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@@ -70,7 +74,7 @@ class StandaloneDragonfly extends BaseModel
});
static::saving(function ($database) {
if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]);
$database->last_online_at = now();
}
});
}
+5 -1
View File
@@ -14,6 +14,7 @@ class StandaloneKeydb extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [
'uuid',
'name',
'description',
'keydb_password',
@@ -40,6 +41,9 @@ class StandaloneKeydb extends BaseModel
'public_port_timeout',
'enable_ssl',
'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
];
protected $appends = ['internal_db_url', 'external_db_url', 'server_status'];
@@ -71,7 +75,7 @@ class StandaloneKeydb extends BaseModel
});
static::saving(function ($database) {
if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]);
$database->last_online_at = now();
}
});
}
+5 -1
View File
@@ -15,6 +15,7 @@ class StandaloneMariadb extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [
'uuid',
'name',
'description',
'mariadb_root_password',
@@ -43,6 +44,9 @@ class StandaloneMariadb extends BaseModel
'enable_ssl',
'is_log_drain_enabled',
'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@@ -74,7 +78,7 @@ class StandaloneMariadb extends BaseModel
});
static::saving(function ($database) {
if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]);
$database->last_online_at = now();
}
});
}
+5 -1
View File
@@ -14,6 +14,7 @@ class StandaloneMongodb extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [
'uuid',
'name',
'description',
'mongo_conf',
@@ -43,6 +44,9 @@ class StandaloneMongodb extends BaseModel
'is_log_drain_enabled',
'is_include_timestamps',
'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@@ -80,7 +84,7 @@ class StandaloneMongodb extends BaseModel
});
static::saving(function ($database) {
if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]);
$database->last_online_at = now();
}
});
}
+5 -1
View File
@@ -14,6 +14,7 @@ class StandaloneMysql extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [
'uuid',
'name',
'description',
'mysql_root_password',
@@ -44,6 +45,9 @@ class StandaloneMysql extends BaseModel
'is_log_drain_enabled',
'is_include_timestamps',
'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@@ -76,7 +80,7 @@ class StandaloneMysql extends BaseModel
});
static::saving(function ($database) {
if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]);
$database->last_online_at = now();
}
});
}
+5 -1
View File
@@ -14,6 +14,7 @@ class StandalonePostgresql extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [
'uuid',
'name',
'description',
'postgres_user',
@@ -46,6 +47,9 @@ class StandalonePostgresql extends BaseModel
'is_log_drain_enabled',
'is_include_timestamps',
'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@@ -92,7 +96,7 @@ class StandalonePostgresql extends BaseModel
});
static::saving(function ($database) {
if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]);
$database->last_online_at = now();
}
});
}
+5 -1
View File
@@ -14,6 +14,7 @@ class StandaloneRedis extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [
'uuid',
'name',
'description',
'redis_conf',
@@ -39,6 +40,9 @@ class StandaloneRedis extends BaseModel
'is_log_drain_enabled',
'is_include_timestamps',
'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@@ -69,7 +73,7 @@ class StandaloneRedis extends BaseModel
});
static::saving(function ($database) {
if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]);
$database->last_online_at = now();
}
});
+1
View File
@@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Model;
class Subscription extends Model
{
protected $fillable = [
'team_id',
'stripe_invoice_paid',
'stripe_subscription_id',
'stripe_customer_id',
+1
View File
@@ -7,6 +7,7 @@ use App\Support\ValidationPatterns;
class SwarmDocker extends BaseModel
{
protected $fillable = [
'server_id',
'name',
'network',
];
+1
View File
@@ -10,6 +10,7 @@ class Tag extends BaseModel
protected $fillable = [
'name',
'team_id',
];
protected function customizeName($value)
+4 -1
View File
@@ -49,6 +49,9 @@ class User extends Authenticatable implements SendsEmail
'password',
'force_password_reset',
'marketing_emails',
'pending_email',
'email_change_code',
'email_change_code_expires_at',
];
protected $hidden = [
@@ -409,7 +412,7 @@ class User extends Authenticatable implements SendsEmail
$expiryMinutes = config('constants.email_change.verification_code_expiry_minutes', 10);
$expiresAt = Carbon::now()->addMinutes($expiryMinutes);
$this->forceFill([
$this->fill([
'pending_email' => $newEmail,
'email_change_code' => $code,
'email_change_code_expires_at' => $expiresAt,