Dev
Your Git GUI Isn't Using Your SSH
Dev.toUnited States · NORTH AMERICA
In the previous post I was building a small GUI for creating SSH keys, and the first decision was how to pass a passphrase to ssh-keygen without leaking it. The second decision came right after: shoul...
In the previous post I was building a small GUI for creating SSH keys, and the first decision was how to pass a passphrase to ssh-keygen without leaking it. The second decision came right after: should the program talk SSH itself, through a library, or just run the system ssh?
I chose the system binary. Not because the libraries are bad, but because of what I found when I looked at what "use a library" actually means.
"SSH" is two different things
When you type git clone [email protected]:... in a terminal, git runs ssh, the OpenSSH client. That client reads ~/.ssh/config. It checks ~/.ssh/known_hosts. It asks your ssh-agent for keys. It follows ProxyJump through a bastion. All the behaviour you've tuned over years lives there.
Many GUI clients, IDE plugins and deploy tools don't run that binary. They link an SSH library and speak the protocol themselves. The protocol is the same. The behaviour around it is whatever the application's authors wrote.
Take libssh2, one of the common ones. It's a solid library, and it has the building blocks: a known-hosts API, an agent API, even security-key signing. But they're building blocks. Each OpenSSH behaviour you rely on becomes a feature somebody had to remember to implement. And some aren't there at all — the library has no ssh_config parser and no ProxyJump.
The most telling line is in libssh2's own example program. Right after connecting, a comment says to check the host key against your known hosts — "that is your call". The example then prints the fingerprint and carries on.
That's fine for an example. It's a problem when an application ships the same shape.
What can quietly differ
Host key verification. In the terminal, a changed server key stops you with a wall of @@@ and "REMOTE HOST IDENTIFICATION HAS CHANGED". That warning is the whole defence against someone sitting between you and the server. An embedded client might check its own store instead of known_hosts, accept any new key silently, or not check at all.
Your config. Host aliases, IdentityFile, IdentitiesOnly, ProxyJump, algorithm restrictions — if the client doesn't read ~/.ssh/config, none of it applies, and nothing tells you. You hardened something; the tool doesn't know.
Where the passphrase lives. OpenSSH hands that job to ssh-agent: the key is unlocked once, the passphrase isn't stored anywhere. An application with its own stack may offer to "remember" the passphrase — in its own settings, encrypted with who knows what.
Which keys work. Newer key types and hardware keys (ed25519-sk, ecdsa-sk) work in OpenSSH long before every embedded stack catches up.
On Windows there's a bonus version of this: Git for Windows ships its own ssh.exe, separate from the OpenSSH built into Windows. Two clients, two configs in practice, two agents. A key added to one agent is invisible to the other.
Check your own tools
I'm not going to name clients here — versions change, and what I'd write today could be wrong next month. Instead, here's how to find out about the tools you actually use. Each check takes a minute.
1. Does it read ~/.ssh/config? Add an alias:
Host gh-alias
HostName github.com
User git
ssh -T gh-alias should greet you. Now clone gh-alias:you/some-repo.git in the GUI. If it fails with "could not resolve host", it isn't reading your config. (ssh -G gh-alias shows exactly what OpenSSH would use, for comparison.)
2. Does it check host keys? This is the important one. Back up your known_hosts, then put a wrong key under github.com's name — GitLab's key will do:
cp ~/.ssh/known_hosts ~/.ssh/known_hosts.bak
ssh-keygen -R github.com
ssh-keyscan -t ed25519 gitlab.com 2>/dev/null | sed 's/^gitlab.com/github.com/' >> ~/.ssh/known_hosts
ssh -T [email protected] must now refuse loudly. Fetch from a GitHub repo in the GUI. If it connects without complaint, it would connect just as happily to an impostor. Restore afterwards:
mv ~/.ssh/known_hosts.bak ~/.ssh/known_hosts
3. Does it use your agent? With a passphrase-protected key, empty the agent with ssh-add -D and fetch in the GUI. If it connects without asking for the passphrase, it has the passphrase stored somewhere. Find out where.
What to do with the answer
If a tool fails these checks, look for a setting before looking for a replacement. Many have an option like "use system SSH" or "use system Git", and plain git lets you choose the client outright with core.sshCommand or GIT_SSH_COMMAND. Tools that simply run your system git are already using your system ssh.
For my own little program that settled it. Running the system ssh means every check above passes by construction — not because I implemented them, but because I didn't have to. It also means one less library to track for security fixes.
The safest SSH code is the SSH code you didn't write. Before trusting a tool with your keys, spend three minutes checking whose SSH it's actually using.