mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
Remove tests that copy app logic into the test, grep PHP source as text, only check literals, constants, class_exists or PHP built-ins, duplicate other tests, or are always skipped. Keep source checks that guard security and authorization rules. Rename a duplicate Pest helper so the full suite loads, drop the missing v5/Browser path, and remove the CLAUDE.md symlink in favor of AGENTS.md. Correct AGENTS.md: Dusk is not installed and the upgrade script is scripts/upgrade.sh.
129 lines
4.3 KiB
PHP
129 lines
4.3 KiB
PHP
<?php
|
|
|
|
use App\Helpers\SshMultiplexingHelper;
|
|
use App\Models\Server;
|
|
use App\Rules\ValidHostname;
|
|
use App\Rules\ValidServerIp;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ValidServerIp rule
|
|
// -------------------------------------------------------------------------
|
|
|
|
it('accepts a valid IPv4 address', function () {
|
|
$rule = new ValidServerIp;
|
|
$failed = false;
|
|
$rule->validate('ip', '192.168.1.1', function () use (&$failed) {
|
|
$failed = true;
|
|
});
|
|
expect($failed)->toBeFalse();
|
|
});
|
|
|
|
it('accepts a valid IPv6 address', function () {
|
|
$rule = new ValidServerIp;
|
|
$failed = false;
|
|
$rule->validate('ip', '2001:db8::1', function () use (&$failed) {
|
|
$failed = true;
|
|
});
|
|
expect($failed)->toBeFalse();
|
|
});
|
|
|
|
it('accepts a valid hostname', function () {
|
|
$rule = new ValidServerIp;
|
|
$failed = false;
|
|
$rule->validate('ip', 'my-server.example.com', function () use (&$failed) {
|
|
$failed = true;
|
|
});
|
|
expect($failed)->toBeFalse();
|
|
});
|
|
|
|
it('rejects injection payloads in server ip', function (string $payload) {
|
|
$rule = new ValidServerIp;
|
|
$failed = false;
|
|
$rule->validate('ip', $payload, function () use (&$failed) {
|
|
$failed = true;
|
|
});
|
|
expect($failed)->toBeTrue("ValidServerIp should reject: $payload");
|
|
})->with([
|
|
'semicolon' => ['192.168.1.1; rm -rf /'],
|
|
'pipe' => ['192.168.1.1 | cat /etc/passwd'],
|
|
'backtick' => ['192.168.1.1`id`'],
|
|
'dollar subshell' => ['192.168.1.1$(id)'],
|
|
'ampersand' => ['192.168.1.1 & id'],
|
|
'newline' => ["192.168.1.1\nid"],
|
|
'null byte' => ["192.168.1.1\0id"],
|
|
]);
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Server model setter casts
|
|
// -------------------------------------------------------------------------
|
|
|
|
it('strips dangerous characters from server ip on write', function () {
|
|
$server = new Server;
|
|
$server->ip = '192.168.1.1;rm -rf /';
|
|
// Regex [^0-9a-zA-Z.:%-] removes ; space and /; hyphen is allowed
|
|
expect($server->ip)->toBe('192.168.1.1rm-rf');
|
|
});
|
|
|
|
it('strips dangerous characters from server user on write', function () {
|
|
$server = new Server;
|
|
$server->user = 'root$(id)';
|
|
expect($server->user)->toBe('rootid');
|
|
});
|
|
|
|
it('strips non-numeric characters from server port on write', function () {
|
|
$server = new Server;
|
|
$server->port = '22; evil';
|
|
expect($server->port)->toBe(22);
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// escapeshellarg() in generated SSH commands (source-level verification)
|
|
// -------------------------------------------------------------------------
|
|
|
|
it('wraps port with escapeshellarg in getCommonSshOptions', function () {
|
|
$reflection = new ReflectionClass(SshMultiplexingHelper::class);
|
|
$source = file_get_contents($reflection->getFileName());
|
|
|
|
expect($source)->toContain('escapeshellarg((string) $server->port)');
|
|
});
|
|
|
|
it('has no raw user@ip string interpolation in SshMultiplexingHelper', function () {
|
|
$reflection = new ReflectionClass(SshMultiplexingHelper::class);
|
|
$source = file_get_contents($reflection->getFileName());
|
|
|
|
expect($source)->not->toContain('{$server->user}@{$server->ip}');
|
|
});
|
|
|
|
it('escapes scp source and destination operands', function () {
|
|
$reflection = new ReflectionClass(SshMultiplexingHelper::class);
|
|
$source = file_get_contents($reflection->getFileName());
|
|
|
|
expect($source)
|
|
->toContain('escapeshellarg($source)')
|
|
->toContain('escapeshellarg($dest)')
|
|
->not->toContain('"{$source} "')
|
|
->not->toContain('":{$dest}"');
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// ValidHostname rejects shell metacharacters
|
|
// -------------------------------------------------------------------------
|
|
|
|
it('rejects semicolon in hostname', function () {
|
|
$rule = new ValidHostname;
|
|
$failed = false;
|
|
$rule->validate('hostname', 'example.com;id', function () use (&$failed) {
|
|
$failed = true;
|
|
});
|
|
expect($failed)->toBeTrue();
|
|
});
|
|
|
|
it('rejects backtick in hostname', function () {
|
|
$rule = new ValidHostname;
|
|
$failed = false;
|
|
$rule->validate('hostname', 'example.com`id`', function () use (&$failed) {
|
|
$failed = true;
|
|
});
|
|
expect($failed)->toBeTrue();
|
|
});
|