refactor(volumes): validate input and escape shell args

Tighten validation on volume name and host path inputs across Livewire + API storage endpoints and escape shell arguments in volume clone and compose preview cleanup paths.
This commit is contained in:
Andras Bacsai
2026-04-20 11:27:10 +02:00
parent a1b2ab124a
commit 410a9a6195
8 changed files with 148 additions and 29 deletions
@@ -96,3 +96,88 @@ it('generates volumeNameMessages with custom field name', function () {
expect($messages)->toHaveKey('volume_name.regex');
});
// --- escapeshellarg Defense Tests for docker volume create ---
it('escapeshellarg neutralizes injection in docker volume create command', function (string $maliciousName) {
$escaped = escapeshellarg($maliciousName);
$command = "docker volume create {$escaped}";
expect($command)->toStartWith('docker volume create ')
->and($escaped)->toStartWith("'")
->and($escaped)->toEndWith("'");
})->with([
'semicolon' => 'vol; rm -rf /',
'pipe' => 'vol | cat /etc/passwd',
'ampersand' => 'vol && whoami',
'backtick' => 'vol`id`',
'command substitution' => 'vol$(whoami)',
]);
// --- escapeshellarg Defense Tests for docker run -v ---
it('escapeshellarg neutralizes injection in docker run -v command', function (string $maliciousName) {
$escaped = escapeshellarg($maliciousName);
$command = "docker run --rm -v {$escaped}:/source -v {$escaped}:/target alpine sh -c 'cp -a /source/. /target/'";
expect($command)->toContain('docker run --rm -v ')
->and($escaped)->toStartWith("'")
->and($escaped)->toEndWith("'");
})->with([
'semicolon' => 'vol; rm -rf /',
'pipe' => 'vol | cat /etc/passwd',
'command substitution' => 'vol$(whoami)',
]);
// --- escapeshellarg Defense Tests for docker network commands ---
it('escapeshellarg neutralizes injection in docker network disconnect command', function (string $maliciousName) {
$escaped = escapeshellarg($maliciousName);
$command = "docker network disconnect {$escaped} coolify-proxy";
expect($command)->toStartWith('docker network disconnect ')
->and($escaped)->toStartWith("'")
->and($escaped)->toEndWith("'");
})->with([
'semicolon' => 'net; rm -rf /',
'pipe' => 'net | cat /etc/passwd',
'command substitution' => 'net$(whoami)',
]);
it('escapeshellarg neutralizes injection in docker network rm command', function (string $maliciousName) {
$escaped = escapeshellarg($maliciousName);
$command = "docker network rm {$escaped}";
expect($command)->toStartWith('docker network rm ')
->and($escaped)->toStartWith("'")
->and($escaped)->toEndWith("'");
})->with([
'semicolon' => 'net; rm -rf /',
'pipe' => 'net | cat /etc/passwd',
'command substitution' => 'net$(whoami)',
]);
// --- DIRECTORY_PATH_PATTERN Tests ---
it('accepts valid directory paths', function (string $path) {
expect(preg_match(ValidationPatterns::DIRECTORY_PATH_PATTERN, $path))->toBe(1);
})->with([
'root' => '/',
'simple path' => '/data',
'nested path' => '/data/coolify/volumes',
'with dots' => '/data/my.app/storage',
'with hyphens' => '/data/my-app/storage',
'with underscores' => '/data/my_app/storage',
]);
it('rejects directory paths with shell metacharacters', function (string $path) {
expect(preg_match(ValidationPatterns::DIRECTORY_PATH_PATTERN, $path))->toBe(0);
})->with([
'semicolon injection' => '/etc; rm -rf /',
'pipe injection' => '/etc | cat /etc/passwd',
'command substitution' => '/etc$(whoami)',
'backtick injection' => '/etc`id`',
'space injection' => '/etc /tmp',
'relative traversal' => '../../../etc/passwd',
'no leading slash' => 'etc/passwd',
]);