From 8d44c6a1e81a133f7daf39babc4455e1802d5998 Mon Sep 17 00:00:00 2001 From: weibeu Date: Wed, 1 Jul 2026 03:43:40 +0530 Subject: [PATCH] fix: don't let ssh-keyscan abort SSH git clones (#4605) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cloneGitRepository runs `ssh-keyscan >> known_hosts` as one step of a `set -e` script. Hosts whose SSH endpoint waits for the client's identification string first — Hugging Face's hf.co among them — never complete the keyscan handshake, so it exits 1 and `set -e` aborts the deploy before `git clone` ever runs. Make ssh-keyscan non-fatal and let the real ssh client record the host key on first connect (StrictHostKeyChecking=accept-new), which reaches hosts ssh-keyscan can't scan. Same TOFU trust model, so no regression; GitHub/GitLab/Gitea still pre-seed and verify known_hosts as before. --- packages/server/src/utils/providers/git.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/providers/git.ts b/packages/server/src/utils/providers/git.ts index 4c0610921..8e7c71839 100644 --- a/packages/server/src/utils/providers/git.ts +++ b/packages/server/src/utils/providers/git.ts @@ -73,7 +73,7 @@ export const cloneGitRepository = async ({ if (customGitSSHKeyId) { const sshKey = await findSSHKeyById(customGitSSHKeyId); const { port } = sanitizeRepoPathSSH(customGitUrl); - const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`; + const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath} -o StrictHostKeyChecking=accept-new`; command += `echo "${sshKey.privateKey}" > /tmp/id_rsa;`; command += "chmod 600 /tmp/id_rsa;"; command += `export GIT_SSH_COMMAND="${gitSshCommand}";`; @@ -111,7 +111,10 @@ const addHostToKnownHostsCommand = (repositoryURL: string) => { const { domain, port } = sanitizeRepoPathSSH(repositoryURL); const knownHostsPath = path.join(SSH_PATH, "known_hosts"); - return `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath};`; + // ssh-keyscan is best-effort: some Git hosts (e.g. Hugging Face) never answer + // it, and its exit code must not abort the clone under `set -e`. The clone's + // own host-key check (StrictHostKeyChecking=accept-new) is the real boundary. + return `ssh-keyscan -p ${port} ${domain} >> ${knownHostsPath} || true;`; }; const sanitizeRepoPathSSH = (input: string) => { const SSH_PATH_RE = new RegExp(