From 25e61deefd1db97d3c5bb37802ba8b20574bf35e Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:06:18 +0200 Subject: [PATCH] fix(api): reject unknown fields on database import Run validator fails() before adding extra-field errors so Laravel does not replace the message bag and accept undocumented properties. --- .../Api/Concerns/HandlesDatabaseImportsApi.php | 13 ++++++++----- tests/Feature/Api/DatabaseImportApiTest.php | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/Concerns/HandlesDatabaseImportsApi.php b/app/Http/Controllers/Api/Concerns/HandlesDatabaseImportsApi.php index c4dc363e6e..a2ec2b93be 100644 --- a/app/Http/Controllers/Api/Concerns/HandlesDatabaseImportsApi.php +++ b/app/Http/Controllers/Api/Concerns/HandlesDatabaseImportsApi.php @@ -70,11 +70,14 @@ trait HandlesDatabaseImportsApi 'dump_all' => ['sometimes', 'boolean'], 'replace_existing' => ['sometimes', 'boolean'], ]); - foreach (array_diff(array_keys($payload), $allowed) as $field) { - $validator->errors()->add($field, 'This field is not allowed.'); - } - if ($validator->fails() || $validator->errors()->isNotEmpty()) { - return response()->json(['message' => 'Validation failed.', 'errors' => $validator->errors()], 422); + $extraFields = array_diff(array_keys($payload), $allowed); + if ($validator->fails() || ! empty($extraFields)) { + $errors = $validator->errors(); + foreach ($extraFields as $field) { + $errors->add($field, 'This field is not allowed.'); + } + + return response()->json(['message' => 'Validation failed.', 'errors' => $errors], 422); } try { diff --git a/tests/Feature/Api/DatabaseImportApiTest.php b/tests/Feature/Api/DatabaseImportApiTest.php index dc2c18d05c..fc1db1333e 100644 --- a/tests/Feature/Api/DatabaseImportApiTest.php +++ b/tests/Feature/Api/DatabaseImportApiTest.php @@ -102,6 +102,23 @@ test('validates replace existing as a boolean', function () { ->assertJsonValidationErrors('replace_existing'); }); +test('rejects unknown fields on standalone import', function () { + $database = StandalonePostgresql::create(['uuid' => (string) Str::uuid(), 'name' => 'db', 'postgres_user' => 'postgres', 'postgres_password' => 'password', 'postgres_db' => 'db', 'image' => 'postgres:17', 'status' => 'running', 'environment_id' => $this->environment->id, 'destination_id' => $this->destination->id, 'destination_type' => $this->destination->getMorphClass()]); + $activity = Activity::create(['log_name' => 'default', 'description' => 'queued', 'properties' => ['status' => 'queued']]); + $action = Mockery::mock(StartDatabaseImport::class); + $action->shouldReceive('handle')->andReturn($activity); + app()->instance(StartDatabaseImport::class, $action); + + $this->withHeaders($this->headers) + ->postJson("/api/v1/databases/{$database->uuid}/imports", [ + 'source' => 'server', + 'path' => '/tmp/backup.sql', + 'unknown_field' => 'nope', + ]) + ->assertUnprocessable() + ->assertJsonPath('errors.unknown_field.0', 'This field is not allowed.'); +}); + test('returns only a team and resource scoped import activity', function () { $database = StandalonePostgresql::create(['uuid' => (string) Str::uuid(), 'name' => 'db', 'postgres_user' => 'postgres', 'postgres_password' => 'password', 'postgres_db' => 'db', 'image' => 'postgres:17', 'status' => 'running', 'environment_id' => $this->environment->id, 'destination_id' => $this->destination->id, 'destination_type' => $this->destination->getMorphClass()]); $activity = Activity::create(['log_name' => 'default', 'description' => json_encode([['order' => 1, 'output' => 'restored', 'type' => 'stdout']]), 'properties' => ['team_id' => $this->team->id, 'type_uuid' => $database->uuid, 'operation' => 'database_import', 'status' => 'finished', 'exitCode' => 0]]);