From 175570152d5e1f882997101ff910c6b87f6c38dc Mon Sep 17 00:00:00 2001 From: Felipe Balloni Ferreira Date: Sun, 27 Sep 2026 13:32:52 -0300 Subject: [PATCH 1/2] [Fix] generate_key_pair() fails loudly when both ssh-keygen attempts fail The PEM-then-OpenSSH fallback covers ssh-keygen builds that cannot write ed25519 as PEM, but if the fallback also fails nothing notices: chmod() warns on the missing path and the failure only surfaces later as an opaque SSH error. Check the fallback's exit code and the resulting file, log the ssh-keygen output, and throw a RuntimeException whose message omits the key path so it is safe to surface. Co-Authored-By: Claude Opus 5.5 --- app/Support/helpers.php | 14 +++++++++- tests/Unit/Support/HelpersTest.php | 42 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/Support/HelpersTest.php diff --git a/app/Support/helpers.php b/app/Support/helpers.php index 27f76d295..1fa412fb8 100755 --- a/app/Support/helpers.php +++ b/app/Support/helpers.php @@ -5,6 +5,7 @@ use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\File; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Route; use Illuminate\Support\ProcessUtils; use Symfony\Component\Process\PhpExecutableFinder; @@ -20,13 +21,24 @@ function generate_public_key(string $privateKeyPath, string $publicKeyPath): voi /** * ssh-keygen builds linked against LibreSSL (e.g. OpenSSH 10 on macOS) cannot write * ed25519 keys as PEM, so fall back to the OpenSSH format, which phpseclib also reads. + * + * @throws RuntimeException */ function generate_key_pair(string $path): void { exec("ssh-keygen -t ed25519 -m PEM -N '' -f ".escapeshellarg($path)); if (! file_exists($path)) { - exec("ssh-keygen -t ed25519 -N '' -f ".escapeshellarg($path)); + exec("ssh-keygen -t ed25519 -N '' -f ".escapeshellarg($path).' 2>&1', $output, $exitCode); + + if ($exitCode !== 0 || ! file_exists($path)) { + Log::error('Failed to generate SSH key pair', [ + 'exit_code' => $exitCode, + 'output' => implode("\n", $output), + ]); + + throw new RuntimeException('Failed to generate SSH key pair.'); + } } chmod($path, 0400); diff --git a/tests/Unit/Support/HelpersTest.php b/tests/Unit/Support/HelpersTest.php new file mode 100644 index 000000000..352f7b147 --- /dev/null +++ b/tests/Unit/Support/HelpersTest.php @@ -0,0 +1,42 @@ +keyPath = sys_get_temp_dir().'/vito-test-key-'.uniqid(); +}); + +afterEach(function (): void { + foreach ([$this->keyPath, $this->keyPath.'.pub'] as $path) { + if (file_exists($path)) { + unlink($path); + } + } +}); + +it('generates a key pair the ssh helper can load', function (): void { + generate_key_pair($this->keyPath); + + expect($this->keyPath)->toBeReadableFile() + ->and($this->keyPath.'.pub')->toBeReadableFile() + ->and(fileperms($this->keyPath) & 0777)->toBe(0400) + ->and(PublicKeyLoader::loadPrivateKey((string) file_get_contents($this->keyPath))) + ->toBeInstanceOf(PrivateKey::class); +}); + +it('fails loudly without leaking the key path when generation fails', function (): void { + Log::shouldReceive('error')->once(); + + $path = '/this-directory-does-not-exist/key'; + + try { + generate_key_pair($path); + $this->fail('Expected generate_key_pair() to throw.'); + } catch (RuntimeException $e) { + expect($e->getMessage()) + ->toBe('Failed to generate SSH key pair.') + ->not->toContain($path); + } +}); From bfde0041fa6d1596dafc7d59093acd67c7bede54 Mon Sep 17 00:00:00 2001 From: Felipe Balloni Ferreira Date: Sun, 27 Sep 2026 13:44:13 -0300 Subject: [PATCH 2/2] Migrate the database for the helpers tests and use a unique missing path TestCase::setUp() creates a user, so the unit test needs RefreshDatabase or it fails with "no such table: users" when run on a fresh database, as in CI. Co-Authored-By: Claude Opus 5.5 --- tests/Unit/Support/HelpersTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Support/HelpersTest.php b/tests/Unit/Support/HelpersTest.php index 352f7b147..f34be58bf 100644 --- a/tests/Unit/Support/HelpersTest.php +++ b/tests/Unit/Support/HelpersTest.php @@ -1,9 +1,12 @@ keyPath = sys_get_temp_dir().'/vito-test-key-'.uniqid(); }); @@ -29,7 +32,7 @@ it('fails loudly without leaking the key path when generation fails', function (): void { Log::shouldReceive('error')->once(); - $path = '/this-directory-does-not-exist/key'; + $path = sys_get_temp_dir().'/vito-missing-'.uniqid().'/key'; try { generate_key_pair($path);