mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 18:18:29 +00:00
refactor(validation): tokenize shell-safe command pattern
Replace the flat character-class regex for SHELL_SAFE_COMMAND_PATTERN with a token-aware alternation. The parser now recognizes explicit tokens (`&&`, `||`, balanced single/double quotes, whitespace, and an unquoted safe-char run) instead of a bag of characters, which lets us extend the accepted grammar without loosening the guarantees. New surface area, with tests: - logical OR chaining (`make build || make clean`) - shell globs and bang (`rm *.tmp`, `cp src/?.js dist/`, `! grep -q foo`) - single-quoted arguments are now treated as balanced runs rather than rejected per-character Preserved surface area: - && chaining, balanced "..." and '...' quotes, the previous safe path / argument characters, and the existing error-path contract in ApplicationDeploymentJob::validateShellSafeCommand(). Also refreshes the user-facing validation messages in General.php so the allow/deny list shown on failure matches the new grammar. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
1cf6c7d0ae
commit
817128c5af
3 files changed
+153
-16
No files matched your search
@@ -414,7 +414,7 @@ describe('docker_compose_custom_command validation', function () {
|
||||
expect($validator->fails())->toBeTrue();
|
||||
});
|
||||
|
||||
test('rejects single quotes in docker_compose_custom_start_command', function () {
|
||||
test('allows single-quoted arguments in docker_compose_custom_start_command', function () {
|
||||
$rules = sharedDataApplications();
|
||||
|
||||
$validator = validator(
|
||||
@@ -422,7 +422,7 @@ describe('docker_compose_custom_command validation', function () {
|
||||
['docker_compose_custom_start_command' => $rules['docker_compose_custom_start_command']]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
expect($validator->fails())->toBeFalse();
|
||||
});
|
||||
|
||||
test('allows double quotes in docker_compose_custom_start_command', function () {
|
||||
@@ -474,6 +474,127 @@ describe('docker_compose_custom_command validation', function () {
|
||||
expect($method->invoke($instance, 'docker compose up -d --build', 'docker_compose_custom_start_command'))
|
||||
->toBe('docker compose up -d --build');
|
||||
});
|
||||
|
||||
test('rejects bare ampersand PoC payload (GHSA-chg4-63hm-xv9x)', function () {
|
||||
$rules = sharedDataApplications();
|
||||
$payload = 'true & docker run --rm -v /:/h alpine sh -c "cp /h/etc/shadow /h/tmp/leak"';
|
||||
|
||||
$validator = validator(
|
||||
['docker_compose_custom_start_command' => $payload],
|
||||
['docker_compose_custom_start_command' => $rules['docker_compose_custom_start_command']]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
});
|
||||
|
||||
test('rejects bare ampersand across every shell-safe field', function ($field) {
|
||||
$rules = sharedDataApplications();
|
||||
|
||||
$validator = validator(
|
||||
[$field => 'cmd1 & cmd2'],
|
||||
[$field => $rules[$field]]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
})->with([
|
||||
'install_command',
|
||||
'build_command',
|
||||
'start_command',
|
||||
'docker_compose_custom_build_command',
|
||||
'docker_compose_custom_start_command',
|
||||
'custom_docker_run_options',
|
||||
]);
|
||||
|
||||
test('rejects command substitution inside double quotes', function ($payload) {
|
||||
$rules = sharedDataApplications();
|
||||
|
||||
$validator = validator(
|
||||
['build_command' => "echo $payload"],
|
||||
['build_command' => $rules['build_command']]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
})->with(['"$(whoami)"', '"`whoami`"']);
|
||||
|
||||
test('rejects unbalanced quotes', function ($payload) {
|
||||
$rules = sharedDataApplications();
|
||||
|
||||
$validator = validator(
|
||||
['build_command' => $payload],
|
||||
['build_command' => $rules['build_command']]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
})->with(['echo "unterminated', "echo 'unterminated"]);
|
||||
|
||||
test('rejects backslash anywhere', function ($payload) {
|
||||
$rules = sharedDataApplications();
|
||||
|
||||
$validator = validator(
|
||||
['build_command' => $payload],
|
||||
['build_command' => $rules['build_command']]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
})->with(['echo \\;', 'echo \\$HOME']);
|
||||
|
||||
test('runtime validateShellSafeCommand rejects bare ampersand payload', function () {
|
||||
$job = new ReflectionClass(ApplicationDeploymentJob::class);
|
||||
$method = $job->getMethod('validateShellSafeCommand');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$instance = $job->newInstanceWithoutConstructor();
|
||||
|
||||
expect(fn () => $method->invoke($instance, 'true & whoami', 'docker_compose_custom_start_command'))
|
||||
->toThrow(RuntimeException::class, 'contains forbidden shell characters');
|
||||
});
|
||||
|
||||
test('allows logical OR chaining', function ($cmd) {
|
||||
$rules = sharedDataApplications();
|
||||
|
||||
$validator = validator(
|
||||
['build_command' => $cmd],
|
||||
['build_command' => $rules['build_command']]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeFalse();
|
||||
})->with([
|
||||
'make build || make clean',
|
||||
'npm run build || npm run fallback',
|
||||
'cmd-a || cmd-b && cmd-c',
|
||||
]);
|
||||
|
||||
test('allows glob and bang tokens', function ($cmd) {
|
||||
$rules = sharedDataApplications();
|
||||
|
||||
$validator = validator(
|
||||
['build_command' => $cmd],
|
||||
['build_command' => $rules['build_command']]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeFalse();
|
||||
})->with([
|
||||
'rm *.tmp',
|
||||
'cp src/?.js dist/',
|
||||
'! grep -q foo && echo missing',
|
||||
'docker build --tag app-v1!',
|
||||
]);
|
||||
|
||||
test('rejects bare pipe even though || is allowed', function ($cmd) {
|
||||
$rules = sharedDataApplications();
|
||||
|
||||
$validator = validator(
|
||||
['build_command' => $cmd],
|
||||
['build_command' => $rules['build_command']]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
})->with([
|
||||
'cmd | cat',
|
||||
'cmd|cat',
|
||||
'a |b',
|
||||
'a| b',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('custom_docker_run_options validation', function () {
|
||||
|
||||
Reference in new issue
Block a user