SSH to one of the servers failed: “Too many authentication failures.” the server wasn’t rejecting my key — it was rejecting me before it even got to my key.
the problem
the SSH agent had 14 keys loaded. the server’s MaxAuthTries was set to 6. SSH tries agent keys sequentially until one works. if your correct key is 7th or later in the list, you’re locked out before it’s tried.
the standard fix is IdentitiesOnly yes in SSH config, which tells the client to only offer the key specified by IdentityFile. this works perfectly in the terminal because the normal ssh client reads ~/.ssh/config.
but i don’t use the normal ssh client. i use a custom SSH extension that connects via the ssh2 library directly. it reads SSH config for host resolution and key paths, but it wasn’t implementing IdentitiesOnly.
the agent filter
the fix was a filtered agent — a proxy that wraps the real SSH agent and only exposes keys matching the configured identity:
const filteredAgent = Object.create(realAgent);
filteredAgent.getIdentities = (cb) => {
realAgent.getIdentities((err, keys) => {
const match = keys.filter(k =>
k.getPublicSSH().toString("base64") === expectedKeyBase64
);
cb(null, match);
});
};
reads the .pub file from the SSH config’s IdentityFile, extracts the base64, and only presents that one key to the server. 14 keys in the agent, but the server only sees the one it needs.
the reason there are 14 keys: all SSH keys live in bitwarden and get loaded into the macOS agent on login. each server, service, and git host has its own key. good security practice, annoying side effect.
nyan