From adab0f14085e5461f296ed452b9c105247005915 Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:33:20 +0000 Subject: [PATCH 1/2] Prompt SSH unknown host prompt on git push This fixes #4057. Lazygit got unresponsive on SSH unknown host prompt before this change. --- pkg/commands/oscommands/cmd_obj_runner.go | 2 ++ pkg/commands/oscommands/cmd_obj_runner_test.go | 11 +++++++++++ pkg/gui/controllers/helpers/credentials_helper.go | 2 ++ pkg/i18n/english.go | 2 ++ 4 files changed, 17 insertions(+) diff --git a/pkg/commands/oscommands/cmd_obj_runner.go b/pkg/commands/oscommands/cmd_obj_runner.go index 953937706b1..bf2bc60c27e 100644 --- a/pkg/commands/oscommands/cmd_obj_runner.go +++ b/pkg/commands/oscommands/cmd_obj_runner.go @@ -293,6 +293,7 @@ const ( Passphrase PIN Token + UnknownHostVerification ) // Whenever we're asked for a password we return a nil channel to tell the @@ -396,6 +397,7 @@ func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (Crede `Enter\s*PIN\s*for\s*.+\s*key\s*.+:`: PIN, `Enter\s*PIN\s*for\s*'.+':`: PIN, `.*2FA Token.*`: Token, + `(?i)Are\s+you\s+sure\s+you\s+want\s+to\s+continue\s+connecting\s*\(yes/no(?:/[^)]*)?\)\s*\?`: UnknownHostVerification, } compiledPrompts := map[*regexp.Regexp]CredentialType{} diff --git a/pkg/commands/oscommands/cmd_obj_runner_test.go b/pkg/commands/oscommands/cmd_obj_runner_test.go index 74e3a198590..a238efeca77 100644 --- a/pkg/commands/oscommands/cmd_obj_runner_test.go +++ b/pkg/commands/oscommands/cmd_obj_runner_test.go @@ -41,6 +41,8 @@ func TestProcessOutput(t *testing.T) { return "pin" case Token: return "token" + case UnknownHostVerification: + return "yes" default: panic("unexpected credential type") } @@ -118,6 +120,15 @@ func TestProcessOutput(t *testing.T) { output: "Password:\n", expectedToWrite: "", }, + { + name: "host verification prompt", + promptUserForCredential: defaultPromptUserForCredential, + output: "The authenticity of host 'github.com (140.82.113.3)' can't be established.\n" + + "ED25519 key fingerprint is SHA256:abc.\n" + + "This key is not known by any other names\n" + + "Are you sure you want to continue connecting (yes/no/[fingerprint])? ", + expectedToWrite: "yes", + }, } for _, scenario := range scenarios { diff --git a/pkg/gui/controllers/helpers/credentials_helper.go b/pkg/gui/controllers/helpers/credentials_helper.go index 0783e65f2ce..9c699db4d86 100644 --- a/pkg/gui/controllers/helpers/credentials_helper.go +++ b/pkg/gui/controllers/helpers/credentials_helper.go @@ -61,6 +61,8 @@ func (self *CredentialsHelper) getTitleAndMask(passOrUname oscommands.Credential return self.c.Tr.CredentialsPIN, true case oscommands.Token: return self.c.Tr.CredentialsToken, true + case oscommands.UnknownHostVerification: + return self.c.Tr.UnknownHostVerification, false } // should never land here diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index cb070b89207..0dab4b977a4 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -32,6 +32,7 @@ type TranslationSet struct { CredentialsPassphrase string CredentialsPIN string CredentialsToken string + UnknownHostVerification string PassUnameWrong string Commit string CommitTooltip string @@ -1122,6 +1123,7 @@ func EnglishTranslationSet() *TranslationSet { CredentialsPassphrase: "Enter passphrase for SSH key", CredentialsPIN: "Enter PIN for SSH key", CredentialsToken: "Enter Token for SSH key", + UnknownHostVerification: "SSH host verification (type 'yes', 'no', or fingerprint)", PassUnameWrong: "Password, passphrase and/or username wrong", Commit: "Commit", CommitTooltip: "Commit staged changes.", From a28fd382f626894620a048e54a76bb88c479f6cc Mon Sep 17 00:00:00 2001 From: seiya <20365512+seiyab@users.noreply.github.com> Date: Sun, 9 Nov 2025 06:40:02 +0000 Subject: [PATCH 2/2] Add integration test --- .../sync/push_with_unknown_host_prompt.go | 58 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + test/files/pre-push-unknown-host | 33 +++++++++++ 3 files changed, 92 insertions(+) create mode 100644 pkg/integration/tests/sync/push_with_unknown_host_prompt.go create mode 100755 test/files/pre-push-unknown-host diff --git a/pkg/integration/tests/sync/push_with_unknown_host_prompt.go b/pkg/integration/tests/sync/push_with_unknown_host_prompt.go new file mode 100644 index 00000000000..12e725ced2c --- /dev/null +++ b/pkg/integration/tests/sync/push_with_unknown_host_prompt.go @@ -0,0 +1,58 @@ +package sync + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var PushWithUnknownHostPrompt = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Push a commit to a pre-configured upstream, where the SSH host must be verified", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + }, + SetupRepo: func(shell *Shell) { + shell.EmptyCommit("one") + + shell.CloneIntoRemote("origin") + + shell.SetBranchUpstream("master", "origin/master") + + shell.EmptyCommit("two") + + // simulate pushing to an unknown host by using a pre-push hook that prompts for host verification. + shell.CopyHelpFile("pre-push-unknown-host", ".git/hooks/pre-push") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Status().Content(Equals("↑1 repo → master")) + + t.Views().Files(). + IsFocused(). + Press(keys.Universal.Push) + + t.ExpectPopup().Prompt(). + Title(Equals("SSH host verification (type 'yes', 'no', or fingerprint)")). + Type("no"). + Confirm() + + t.ExpectPopup().Alert(). + Title(Equals("Error")). + Content(Contains("Host key verification failed")). + Confirm() + + t.Views().Status().Content(Equals("↑1 repo → master")) + + t.Views().Files(). + IsFocused(). + Press(keys.Universal.Push) + + t.ExpectPopup().Prompt(). + Title(Equals("SSH host verification (type 'yes', 'no', or fingerprint)")). + Type("yes"). + Confirm() + + t.Views().Status().Content(Equals("✓ repo → master")) + + assertSuccessfullyPushed(t) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index da21d1f0d3c..7d1d9c26d64 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -419,6 +419,7 @@ var tests = []*components.IntegrationTest{ sync.PushNoFollowTags, sync.PushTag, sync.PushWithCredentialPrompt, + sync.PushWithUnknownHostPrompt, sync.RenameBranchAndPull, tag.Checkout, tag.CheckoutWhenBranchWithSameNameExists, diff --git a/test/files/pre-push-unknown-host b/test/files/pre-push-unknown-host new file mode 100755 index 00000000000..5b3809a248f --- /dev/null +++ b/test/files/pre-push-unknown-host @@ -0,0 +1,33 @@ +#!/bin/bash + +# test pre-push hook for exercising the SSH unknown host verification prompt in lazygit +# +# to enable, use: +# chmod +x .git/hooks/pre-push +# +# this will hang if you're using git from the command line, so only enable this +# when you are testing lazygit's verification popup + +exec < /dev/tty + +FLAG_FILE=".git/test-known-host" + +if [ ! -f "$FLAG_FILE" ]; then + echo "The authenticity of host 'fake.example.com (ED25519)' can't be established." + echo "ED25519 key fingerprint is SHA256:FAKEFINGERPRINT." + printf "Are you sure you want to continue connecting (yes/no/[fingerprint])? " + read response + + if [ "$response" = "yes" ] || [ "$response" = "SHA256:FAKEFINGERPRINT" ]; then + echo "Warning: Permanently added 'fake.example.com' (ED25519) to the list of known hosts." + touch "$FLAG_FILE" + echo "success" + exit 0 + fi + + >&2 echo "Host key verification failed" + exit 1 +fi + +echo "success" +exit 0