Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions app/Helpers/SSH.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,11 @@ public function exec(string|View $command, string $log = '', ?int $siteId = null

try {
if ($this->asUser !== null && $this->asUser !== '' && $this->asUser !== '0') {
$command = base64_encode((string) $command);
$command = "sudo su - {$this->asUser} -c 'bash -c \"echo {$command} | base64 -d | bash\"'";
$command = <<<BASH
sudo -u {$this->asUser} bash <<'EOF'
{$command}
EOF
BASH;
Comment on lines +174 to +177
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The heredoc assigned to $command is indented inside the method, so the generated string will contain leading spaces before sudo -u ..., the embedded command, and EOF. In contrast, the new unit test in ServerModelTest expects those lines to start at column 1 with no leading spaces, so this mismatch will cause the assertion to fail and also changes the exact command sent over SSH. Consider left-aligning the heredoc contents (or otherwise normalizing whitespace) so that the produced command matches the expected format.

Suggested change
sudo -u {$this->asUser} bash <<'EOF'
{$command}
EOF
BASH;
sudo -u {$this->asUser} bash <<'EOF'
{$command}
EOF
BASH;

Copilot uses AI. Check for mistakes.
Comment on lines +173 to +177
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new wrapper switches from sudo su - {user} -c ... to sudo -u {user} bash <<'EOF' ..., which changes how the target user's environment and working directory are initialized (login shell vs non-login shell semantics). This can impact commands that rely on the user's login profile, environment variables, or home-directory-relative paths, which conflicts with the claim that there is no functional behavior change in command execution; consider either preserving the previous login-shell behavior or documenting and verifying this behavioral change with additional tests.

Copilot uses AI. Check for mistakes.
}

$this->connection->setTimeout(0);
Expand Down
60 changes: 60 additions & 0 deletions tests/Unit/Models/ServerModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

use App\Enums\ServerStatus;
use App\Facades\SSH;
use App\Helpers\SSH as SSHHelper;
use Illuminate\Foundation\Testing\RefreshDatabase;
use phpseclib3\Net\SSH2;
use ReflectionProperty;
use Tests\TestCase;

class ServerModelTest extends TestCase
Expand Down Expand Up @@ -47,4 +50,61 @@ public function test_connection_failed(): void
'status' => ServerStatus::DISCONNECTED,
]);
}

public function test_exec_wraps_command_when_using_custom_user(): void
{
$ssh = (new SSHHelper)->init($this->server, 'deploy');

$connection = $this->getMockBuilder(SSH2::class)
->disableOriginalConstructor()
->onlyMethods(['setTimeout', 'exec', 'getExitStatus', 'disconnect'])
->getMock();

$executedCommand = null;

$connection->expects($this->once())
->method('setTimeout')
->with(0);

$connection->expects($this->once())
->method('exec')
->with(
$this->isType('string'),
$this->isType('callable')
)
->willReturnCallback(function ($command, $callback) use (&$executedCommand) {
$executedCommand = $command;
$callback('');

return '';
});

$connection->expects($this->once())
->method('getExitStatus')
->willReturn(0);

$connection->method('disconnect');

$reflection = new ReflectionProperty(SSHHelper::class, 'connection');
$reflection->setAccessible(true);
$reflection->setValue($ssh, $connection);

$command = <<<'BASH'
pwd
ls -la
BASH;

$output = $ssh->exec($command);
$ssh->disconnect();

$expected = <<<'BASH'
sudo -u deploy bash <<'EOF'
pwd
ls -la
EOF
BASH;

$this->assertSame('', $output);
$this->assertSame($expected, $executedCommand);
}
}