getGuarded(); $fillable = $model->getFillable(); // Model must NOT have $guarded = [] (empty guard = no protection) // It should either have non-empty $guarded OR non-empty $fillable $hasProtection = $guarded !== ['*'] ? count($guarded) > 0 : true; $hasProtection = $hasProtection || count($fillable) > 0; expect($hasProtection) ->toBeTrue("Model {$modelClass} has no mass assignment protection (empty \$guarded and empty \$fillable)"); } }); test('Application model blocks mass assignment of relationship IDs', function () { $application = new Application; $dangerousFields = ['id', 'uuid', 'environment_id', 'destination_id', 'destination_type', 'source_id', 'source_type', 'private_key_id', 'repository_project_id']; foreach ($dangerousFields as $field) { expect($application->isFillable($field)) ->toBeFalse("Application model should not allow mass assignment of '{$field}'"); } }); test('Application model allows mass assignment of user-facing fields', function () { $application = new Application; $userFields = ['name', 'description', 'git_repository', 'git_branch', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'health_check_path', 'limits_memory', 'status']; foreach ($userFields as $field) { expect($application->isFillable($field)) ->toBeTrue("Application model should allow mass assignment of '{$field}'"); } }); test('Server model has $fillable and no conflicting $guarded', function () { $server = new Server; $fillable = $server->getFillable(); $guarded = $server->getGuarded(); expect($fillable)->not->toBeEmpty('Server model should have explicit $fillable'); // Guarded should be the default ['*'] when $fillable is set, not [] expect($guarded)->not->toBe([], 'Server model should not have $guarded = [] overriding $fillable'); }); test('Server model blocks mass assignment of dangerous fields', function () { $server = new Server; // These fields should not be mass-assignable via the API expect($server->isFillable('id'))->toBeFalse(); expect($server->isFillable('uuid'))->toBeFalse(); expect($server->isFillable('created_at'))->toBeFalse(); }); test('User model blocks mass assignment of auth-sensitive fields', function () { $user = new User; expect($user->isFillable('id'))->toBeFalse('User id should not be fillable'); expect($user->isFillable('email_verified_at'))->toBeFalse('email_verified_at should not be fillable'); expect($user->isFillable('remember_token'))->toBeFalse('remember_token should not be fillable'); expect($user->isFillable('two_factor_secret'))->toBeFalse('two_factor_secret should not be fillable'); expect($user->isFillable('two_factor_recovery_codes'))->toBeFalse('two_factor_recovery_codes should not be fillable'); expect($user->isFillable('pending_email'))->toBeFalse('pending_email should not be fillable'); expect($user->isFillable('email_change_code'))->toBeFalse('email_change_code should not be fillable'); expect($user->isFillable('email_change_code_expires_at'))->toBeFalse('email_change_code_expires_at should not be fillable'); }); test('User model allows mass assignment of profile fields', function () { $user = new User; expect($user->isFillable('name'))->toBeTrue(); expect($user->isFillable('email'))->toBeTrue(); expect($user->isFillable('password'))->toBeTrue(); }); test('Team model blocks mass assignment of internal fields', function () { $team = new Team; expect($team->isFillable('id'))->toBeFalse(); expect($team->isFillable('use_instance_email_settings'))->toBeFalse('use_instance_email_settings should not be fillable (migrated to EmailNotificationSettings)'); expect($team->isFillable('resend_api_key'))->toBeFalse('resend_api_key should not be fillable (migrated to EmailNotificationSettings)'); }); test('Team model allows mass assignment of expected fields', function () { $team = new Team; expect($team->isFillable('name'))->toBeTrue(); expect($team->isFillable('description'))->toBeTrue(); expect($team->isFillable('personal_team'))->toBeTrue(); expect($team->isFillable('show_boarding'))->toBeTrue(); expect($team->isFillable('custom_server_limit'))->toBeTrue(); }); test('standalone database models block mass assignment of relationship IDs', function () { $models = [ StandalonePostgresql::class, StandaloneRedis::class, StandaloneMysql::class, StandaloneMariadb::class, StandaloneMongodb::class, StandaloneKeydb::class, StandaloneDragonfly::class, StandaloneClickhouse::class, ]; foreach ($models as $modelClass) { $model = new $modelClass; $dangerousFields = ['id', 'uuid', 'environment_id', 'destination_id', 'destination_type']; foreach ($dangerousFields as $field) { expect($model->isFillable($field)) ->toBeFalse("Model {$modelClass} should not allow mass assignment of '{$field}'"); } } }); test('standalone database models allow mass assignment of config fields', function () { $model = new StandalonePostgresql; expect($model->isFillable('name'))->toBeTrue(); expect($model->isFillable('postgres_user'))->toBeTrue(); expect($model->isFillable('postgres_password'))->toBeTrue(); expect($model->isFillable('image'))->toBeTrue(); expect($model->isFillable('limits_memory'))->toBeTrue(); $model = new StandaloneRedis; expect($model->isFillable('redis_conf'))->toBeTrue(); $model = new StandaloneMysql; expect($model->isFillable('mysql_root_password'))->toBeTrue(); $model = new StandaloneMongodb; expect($model->isFillable('mongo_initdb_root_username'))->toBeTrue(); }); test('standalone database models allow mass assignment of public_port_timeout', function () { $models = [ StandalonePostgresql::class, StandaloneRedis::class, StandaloneMysql::class, StandaloneMariadb::class, StandaloneMongodb::class, StandaloneKeydb::class, StandaloneDragonfly::class, StandaloneClickhouse::class, ]; foreach ($models as $modelClass) { $model = new $modelClass; expect($model->isFillable('public_port_timeout')) ->toBeTrue("{$modelClass} should allow mass assignment of 'public_port_timeout'"); } }); test('standalone database models allow mass assignment of SSL fields where applicable', function () { $sslModels = [ StandalonePostgresql::class, StandaloneMysql::class, StandaloneMariadb::class, StandaloneMongodb::class, StandaloneRedis::class, StandaloneKeydb::class, StandaloneDragonfly::class, ]; foreach ($sslModels as $modelClass) { $model = new $modelClass; expect($model->isFillable('enable_ssl')) ->toBeTrue("{$modelClass} should allow mass assignment of 'enable_ssl'"); } // Clickhouse has no SSL columns expect((new StandaloneClickhouse)->isFillable('enable_ssl'))->toBeFalse(); $sslModeModels = [ StandalonePostgresql::class, StandaloneMysql::class, StandaloneMongodb::class, ]; foreach ($sslModeModels as $modelClass) { $model = new $modelClass; expect($model->isFillable('ssl_mode')) ->toBeTrue("{$modelClass} should allow mass assignment of 'ssl_mode'"); } }); test('Application fill ignores non-fillable fields', function () { $application = new Application; $application->fill([ 'name' => 'test-app', 'environment_id' => 999, 'destination_id' => 999, 'team_id' => 999, 'private_key_id' => 999, ]); expect($application->name)->toBe('test-app'); expect($application->environment_id)->toBeNull(); expect($application->destination_id)->toBeNull(); expect($application->private_key_id)->toBeNull(); }); test('Service model blocks mass assignment of relationship IDs', function () { $service = new Service; expect($service->isFillable('id'))->toBeFalse(); expect($service->isFillable('uuid'))->toBeFalse(); expect($service->isFillable('environment_id'))->toBeFalse(); expect($service->isFillable('destination_id'))->toBeFalse(); expect($service->isFillable('server_id'))->toBeFalse(); }); });