SSH KeySelect is an interactive SSH agent proxy that lets you choose which keys an SSH client can offer from your existing agent. By rejecting signing requests for unselected keys, it helps mitigate SSH Agent Hijacking.
Private keys stay in your existing agent. Offering fewer keys can reduce the risk of reaching an SSH server's MaxAuthTries limit.
SSH connections from your machine
SSH clients that use the SSH_AUTH_SOCK environment variable can use KeySelect when connecting to an SSH server. That includes direct use of ssh and OpenSSH launched by git, rsync, or the VS Code Remote - SSH extension.
The client requests available identities, and the picker controls which ones it receives. When the client needs a signature, KeySelect forwards the request to the upstream agent. Private key material stays there.
Through agent forwarding
With agent forwarding and Auto Select Off, a remote process such as Git or a nested SSH client uses its forwarded SSH_AUTH_SOCK. Its requests travel through the SSH connection to your local SSH client and KeySelect. The picker runs locally, and permitted signing requests go to your local upstream agent.
[!IMPORTANT]
Forwarding your agent creates an SSH Agent Hijacking risk: even though private keys stay on your machine, a remote host can request signatures from your agent. A malicious or compromised host can use those signatures to access SSH services and Git hosts reachable with your credentials, including reading or pushing to repositories. Forward your agent only to hosts you trust. See the OpenSSH manual for its warning about agent forwarding.
Screenshots
Main Window
Windows (Light Theme)
Windows (Dark Theme)
macOS
Linux
Settings
Key Select
This view shows the signing request tree for a multi-hop SSH connection using agent forwarding. The key filter narrows the list as you type.
Key Select (Light Theme)
Key Select (Dark Theme)
Installation
SSH KeySelect does not install a background service or enable automatic startup. It creates its Listen endpoint while running.
Windows
From Releases, download the SSH-KeySelect-Setup-<version>-<architecture>.msi installer or a Windows archive. The MSI installs ssh-keyselect.exe and the GUI app for all users under C:\Program Files\SSH KeySelect by default, lets you choose another folder with Browse, and requests administrator approval through UAC. The archive includes both executables too.
Verify the downloaded file against checksums.txt.
To use the CLI from the MSI, run it from the installation folder or add that folder to PATH. For an archive install, extract it into a directory you control and add that directory to PATH.
Windows release executables and installers are currently unsigned. See the Code signing policy for the current status.
macOS
From Releases, download SSH KeySelect <version>.dmg for the universal GUI app, or a macOS archive if you also need the ssh-keyselect CLI.
Verify the downloaded file against checksums.txt.
For the DMG, drag SSH KeySelect.app to Applications. For the archive, extract it into a directory you control and add that directory to PATH to run the CLI.
Follow the note below before opening the app or CLI.
[!CAUTION]
The macOS app and CLI are not signed with a Developer ID or notarized, so Gatekeeper may block them after download. After verifying the DMG, moving its app to Applications, and opening the disk image, remove the quarantine attribute from the installed app:
These commands remove the download quarantine attribute; they do not add a trusted code signature. Once Developer ID signing and notarization are enabled, you can skip them but should continue verifying downloads. See the Code signing policy for the current status.
Linux
Download a Linux archive from Releases. It includes ssh-keyselect for terminal use and ssh-keyselect-gui.
Extract it into a directory you control.
Add the executable directory to PATH to run the CLI.
RHEL-compatible distributions
Download an RPM from Releases and install it directly, or configure the repository for DNF-managed installation.
RPM installation places the executables in /usr/bin and installs a desktop launcher, icons, and license notices in system directories. Run RPM and repository setup commands with administrative privileges.
To configure the repository, first install dnf-plugin-anyrepo, then run the commands below. They add jfut's public signing key to the RPM keyring, register an AnyRepo repository, and install the package.
The Linux GUI requires GTK 3. A system tray icon also requires libayatana-appindicator3 and a desktop environment that displays AppIndicator icons. GNOME Shell does not show these icons by default.
On AlmaLinux 9 with GNOME, enable EPEL and install the AppIndicator library and GNOME Shell extension:
dnf config-manager --set-enabled crb
dnf install epel-release
dnf install libayatana-appindicator-gtk3 gnome-shell-extension-appindicator
# Enable the extension for each user
gnome-extensions enable appindicatorsupport@rgcjonas.gmail.com
Sign out and back in if the icon does not appear after enabling the extension. The main window works without the AppIndicator library or extension.
Other Linux package formats
Releases also include native packages for Debian and Ubuntu (.deb), Alpine Linux (.apk), and Arch Linux (.pkg.tar.zst). Termux packages are provided as .deb files. Download the package for your architecture from Releases and install it with apt, apk, or pacman, as appropriate.
The Alpine .apk is unsigned. Download checksums.txt with the package and verify the package checksum before installing it. Replace the example filename below with the exact .apk asset you downloaded:
Start the GUI first, then set SSH_AUTH_SOCK to the Listen endpoint shown in its main window. On macOS and Linux, the default endpoint is $HOME/.ssh/ssh-keyselect-agent.sock.
Upstream is the endpoint for your existing SSH agent; Listen is where SSH clients connect to KeySelect. The cards show these paths, and their Copy export command buttons copy POSIX export assignments with shell-quoted paths.
The GUI can start without an upstream agent. Configure its endpoints in Settings.
Shell
For Git Bash (Git for Windows) and Linux, start the GUI and set SSH_AUTH_SOCK to its default Listen endpoint:
# Launch the GUI Version of SSH KeySelect
export SSH_AUTH_SOCK="$HOME/.ssh/ssh-keyselect-agent.sock"
ssh user@example.org
VS Code on Windows
Configure VS Code's terminal profile to use Git Bash. The bash.exe path below is for the Scoop git-with-openssh package; replace it with the path to your Git Bash executable if you installed Git Bash another way. Start the GUI with Listen mode set to cygwin and a Listen endpoint that matches SSH_AUTH_SOCK below. See the Git Bash configuration example for setting the endpoint. Open User Settings (JSON) and add the following properties inside the existing top-level object. Keep your other settings. If any of these keys already exist, update them instead of adding duplicates; merge the Git Bash profile and SSH_AUTH_SOCK into the existing profile and environment objects. Replace USERNAME with your Windows account name:
start "" ssh-keyselect-gui.exe
set SSH_AUTH_SOCK=\\.\pipe\ssh-keyselect-agent.socket
ssh user@example.org
CLI: ssh-keyselect
Run OpenSSH through a temporary agent proxy. The picker appears in the current terminal, and the temporary endpoint is removed when SSH exits.
An upstream agent must be available through UPSTREAM_SSH_AUTH_SOCK or SSH_AUTH_SOCK, or set explicitly with --upstream.
For scp and sftp, use an executable wrapper that runs ssh-keyselect ssh, then pass its path with -S.
[!NOTE]
The quick-start examples can change SSH_AUTH_SOCK, add an ssh alias, or set GIT_SSH_COMMAND or core.sshCommand. See Uninstallation to restore or remove those changes.
ssh-keyselect list and ssh-keyselect test: --upstream, --upstream-mode
The terminal commands ssh, list, and test do not read TOML configuration files.
Unless --upstream is set, they use the first non-empty value from UPSTREAM_SSH_AUTH_SOCK, then SSH_AUTH_SOCK. Paths support environment-variable expansion and a leading ~.
Logging
Logging is off by default. Pass --log-level to use debug, info, warn, or error.
For an SSH session, pass --log-file FILE to write KeySelect diagnostic logs to a file instead of the terminal:
The log file is replaced on each run and uses mode 0600 on Unix-like systems. OpenSSH output remains in the terminal. Review logs before sharing them; they can include SSH host key fingerprints and request metadata.
Terminal picker
The terminal picker displays, in order:
A blank line and [ssh-keyselect - YYYY-MM-DD HH:MM:SS TZ].
The request context and a cyan Select an SSH key to allow: prompt above the key table.
A gray Filter (If you don't recognize this request, press Esc to cancel.) hint on the empty filter line. The cursor starts at the beginning, and the hint disappears when you type.
Host details indented beneath their tree labels.
Key
Action
Type
Filter the list.
Enter
Select the highlighted identity.
Esc or Ctrl+C
Cancel.
Backspace or Delete
Edit the filter.
Ctrl+U
Clear the filter.
GUI
Command options
On Windows and Linux, run ssh-keyselect-gui to start the GUI and pass command options. macOS users launch SSH KeySelect.app from Finder and configure it through the GUI menus and Settings.
If XDG_CONFIG_HOME is set, it must be an absolute path and the GUI reads TOML from $XDG_CONFIG_HOME/ssh-keyselect/config.toml. If it is unset, the GUI uses the platform's user configuration directory:
On Windows and Linux, use --config FILE to select another file; command-line values override file values. On macOS, open or save configuration files through the GUI's File menu. The following environment variables also affect endpoint paths:
SSH_KEYSELECT_LISTEN overrides agent.listen.
When agent.upstream is empty, UPSTREAM_SSH_AUTH_SOCK takes precedence over SSH_AUTH_SOCK. If neither is set, the GUI uses the native OpenSSH agent endpoint: \\.\pipe\openssh-ssh-agent on Windows and ~/.ssh/ssh-agent.sock on macOS and Linux.
To keep the upstream endpoint tied to a changing environment variable, set agent.upstream = "env:SSH_AUTH_SOCK", or choose Environment variable in GUI Settings and enter its name. The GUI resolves this reference at startup and when Settings are applied, while saving the env:NAME reference in the config file. The variable must be set and non-empty.
Environment variables in ordinary paths and a leading ~ are expanded using the GUI process environment.
If the configured Listen endpoint is active or blocked by a non-socket file, the GUI starts with the proxy Not configured. A stale socket owned by the current user is replaced when KeySelect confirms that it is no longer listening.
The configuration loader rejects unknown keys. Logging is off by default. Set log.level to enable it and optionally set log.file to append logs to a file. When log.file is empty, logs go to standard error.
The key selection timeout defaults to 120 seconds. In GUI Settings, set agent.selection_timeout; for ssh-keyselect ssh, pass --selection-timeout SECONDS.
If, after a timeout, the SSH client prints the errors below and the server's /var/log/secure contains penalty: exceeded LoginGraceTime, set the Key selection Timeout to match that server's LoginGraceTime value in seconds.
kex_exchange_identification: read: Software caused connection abort
banner exchange: Connection to <IP> port <Port>: Software caused connection abort
Example configuration for Git Bash on Windows, using a Cygwin-compatible listener:
[agent]
# Replace USERNAME with your Windows user name:
upstream = 'C:\Users\USERNAME\.ssh\ssh-upstream-agent.sock'
listen = 'C:\Users\USERNAME\.ssh\ssh-keyselect-agent.sock'
# macOS and Linux:
# upstream = "$SSH_AUTH_SOCK"
# listen = "$HOME/.ssh/ssh-keyselect-agent.sock"
upstream_mode = "auto"
listen_mode = "cygwin"
selection_timeout = 120 # seconds (1 to 86399)
# Allows signing without upstream destination binding after an explicit refusal.
allow_unbound_signing_on_bind_failure = false
[log]
file = ""
level = "off"
[gui]
theme = "light" # "light" or "dark"
Auto Select
Auto Select temporarily bypasses the per-connection picker. When On, the proxy returns every upstream identity and forwards signing requests for any of them.
[!CAUTION]
When Auto Select is On, KeySelect no longer mitigates SSH Agent Hijacking: any client that can access the proxy can request signatures from every key in the upstream agent.
The GUI has an Auto Select switch beside Agent Proxy. Enabling it requires confirmation. Use it only for trusted work, then turn it off. The setting is temporary, is not saved in TOML, and applies to new identity requests. A client that already received all identities while Auto Select was on keeps that selection until its connection closes.
GUI picker
The window title combines the action, application name, and local display time. The request-context area begins with the same timestamp followed by host details. It lets you scroll to earlier hops. Use the arrow keys to move through matches, Enter to select, and Esc to cancel.
Right-click a key row and choose Copy, or press Ctrl+C, to copy its Comment, Type, Size, and Fingerprint as tab-separated text.
Shared behavior
Endpoint modes
--upstream-mode controls how KeySelect connects to the existing agent. --listen-mode controls how the SSH client connects to the proxy.
Mode
Endpoint
auto
Detect a compatible endpoint for the current shell and path.
unix
Unix-domain socket or Windows AF_UNIX socket.
cygwin
Cygwin and Git for Windows socket files.
wsl1
WSL1-compatible socket paths on Windows.
named-pipe
Named Pipe (Windows OpenSSH), such as \\.\pipe\openssh-ssh-agent.
Both modes default to auto in the CLI and configuration. GUI Settings resolves an automatic listener mode to a concrete mode for display. Set a mode explicitly when connecting across shells or when automatic detection does not match the endpoint.
Private key selection
The picker lets you choose a key available through the agent. The SSH agent protocol calls this entry an identity: a public key and comment that identify a key the agent can use to sign.
KeySelect forwards signing requests for selected keys to the upstream agent and never receives private key material.
The picker shows each key's comment, key type, size in bits, and SHA256 fingerprint. Selection is scoped to an SSH session-binding chain:
With Auto Select Off, a new connection starts a fresh selection. A forwarding hop that follows an authentication binding starts a new selection.
The selected keys are cached for that chain. Signing requests for unselected keys are rejected.
Both pickers show request context as selectable, copyable text. For accepted session-bind@openssh.com messages, it includes verified host key types and fingerprints, and whether each connection is marked as forwarded. Forwarding hops and the current target appear in a tree to show connection boundaries.
The agent protocol does not provide a hostname. Names matched from the default local known_hosts files are hints, not verified hostnames. If no session binding is available or accepted, the picker says no verified host key was provided. Press Esc to cancel an unrecognized request.
Security and limitations
The proxy supports listing identities, signing with selected identities, and verified OpenSSH session bindings. Key-management and unknown agent requests are always rejected.
If an upstream agent explicitly refuses a verified session binding, KeySelect normally keeps signing blocked for that connection. The opt-in agent.allow_unbound_signing_on_bind_failure setting (or the --allow-unbound-signing-on-bind-failure option) lets KeySelect list identities and sign without sending the binding upstream. KeySelect still verifies the host context locally for the picker, but this can bypass destination restrictions enforced by the upstream agent. Leave it off unless the upstream agent cannot support session bindings and you accept that tradeoff.
The proxy never stores private keys or signs data. It forwards signing requests for permitted identities to the upstream agent.
KeySelect can only filter identities provided by the upstream agent. Identity-listing and signing requests omit the destination host, user, and port. A session binding can provide a signed host key, but not a hostname, user, or port, so KeySelect cannot select keys by destination.
On macOS and Linux, Unix socket files use mode 0600, and a created parent directory uses mode 0700.
Keep Cygwin-compatible socket files in a directory accessible only to your account.
Uninstallation
Quit the GUI and finish SSH sessions started through the CLI wrapper.
Restore shell and Git changes:
Restore the previous SSH_AUTH_SOCK value or remove the setting from shell startup files and VS Code User Settings (JSON). If you changed VS Code's default profile for this setup, restore its previous value; remove the Git Bash profile if you added it only for KeySelect and no longer need it.
Restore earlier ssh, GIT_SSH_COMMAND, and core.sshCommand values. If you added only this README's examples, run unalias ssh and, in each affected repository, git config --local --unset core.sshCommand.
Remove KeySelect wrappers, PATH entries, shortcuts, and program files. For archive installs, delete the distribution directory with its LICENSE and CREDITS files, plus any copied SSH KeySelect.app. For package installs, remove the package with the package manager you used to install it.
Optionally delete the GUI configuration files (see Configuration), including files saved with Save As or --config, and diagnostic logs created with --log-file. Keep your upstream agent and SSH keys; they belong to your SSH setup.
For RHEL-compatible Linux distributions:
dnf remove ssh-keyselect
If you added the DNF repository during installation, remove it with:
dnf-anyrepo remove ssh-keyselect
The imported jfut RPM signing key and dnf-plugin-anyrepo can be shared by other packages and repositories. Keep them if you still use them.
Code signing policy
See CODE_SIGNING_POLICY.md for the current status, planned scope, and team responsibilities.
Privacy policy
SSH KeySelect has no telemetry, analytics, automatic update checks, or diagnostic uploads. It communicates with other systems only for agent and SSH operations requested or configured by the user or operator.
The proxy communicates with SSH clients and the upstream agent configured through its endpoints. SSH clients receive public key identities and permitted authentication signatures. With SSH agent forwarding, remote processes can receive these responses through your SSH connection. Private key material stays in the upstream agent and is never received by KeySelect. See Security and limitations and the agent forwarding guidance.
The GUI stores configuration locally when you save it. The pickers can read local known_hosts files to display host-name hints.
Diagnostic logging is off by default. When enabled, logs go to local output or the CLI log file you specify and can contain endpoint paths, host key fingerprints, and request metadata. Review logs before sharing them. Bundled libraries do not add third-party online services to the application. Your upstream agent and SSH client have their own settings and privacy policies.
Code of Conduct
Contributors are expected to follow the project's Code of Conduct.
License
Apache-2.0
Copyright contributors to the ssh-keyselect project.