Security
k9-ssh is designed around a defense-in-depth model. This page describes the security controls built into the program and the recommended hardening steps for your deployment.
Built-In Security Controls
Dedicated Unprivileged User
k9-ssh enforces that it runs as the user specified in run_as (typically key9). If the process user at runtime does not match, k9-ssh logs the mismatch to syslog and exits with a non-zero status. This prevents privilege escalation if the binary is somehow called by an unexpected user or process.
The key9 account should be configured with no home directory, no login shell, no password, and no sudo access.
Username Validation
Every username passed via --user is validated against the pattern ^[a-z_][a-z0-9_\-]{0,31}$ before the API is contacted. This prevents:
Path traversal (e.g.,
../../etc/passwdwould be rejected immediately)Shell injection via username
API endpoint manipulation
If validation fails, k9-ssh exits silently (no API call, no key output) and writes a warning to syslog.
Public Key Validation
Every key returned by the Key9 API is parsed through Go's golang.org/x/crypto/ssh library before being printed to stdout. Keys that fail to parse or are of an unrecognized type are silently dropped and logged. This ensures sshd never receives malformed data that could exploit a parsing bug in older OpenSSH versions.
API Communication
By default, all communication with the Key9 cloud API uses HTTPS. When k9-proxy is deployed as a local cache on the same host, k9-ssh can use a plain http:// URL to reach it because traffic is confined to the loopback interface and never crosses the network. TLS is only required when k9-proxy is serving multiple hosts over a network.
Fail-Safe Error Handling
On any error — network failure, non-200 status, JSON parse error, API error response — k9-ssh logs the problem to syslog and exits cleanly with no output. From sshd's perspective, no keys means authentication falls through to the next method. This prevents transient errors from granting inadvertent access and avoids leaking diagnostic information to the connecting user.
Audit Logging
All operations are logged to syslog using the AUTH facility and INFO priority under the tag k9-ssh. Logged events include:
Invalid username
Warning with the rejected value
API connection error
Error with URL and Go error string
Non-200 HTTP status
Status code received
JSON parse error
Error from Go's JSON decoder
API-returned error
Error string from Key9
Key validation failure
Rejected key fingerprint
Process user mismatch
Expected vs. actual user
On most systems these logs appear in /var/log/auth.log (Debian/Ubuntu), /var/log/secure (RHEL/Fedora), or are readable via journalctl -t k9-ssh.
Static Binaries
k9-ssh is compiled with CGO_ENABLED=0, producing a fully static binary with no dependency on system C libraries (glibc, musl, etc.). This eliminates an entire class of library-based vulnerabilities and makes the binary identical across installations of the same OS/arch.
Recommended Hardening
File Permissions
Restrict key9 Account
Disable Password Authentication
Once k9-ssh is working, disable password-based SSH login:
Disable Local authorized_keys (Optional)
To make Key9 the sole source of authorized keys:
This prevents local ~/.ssh/authorized_keys files from bypassing Key9 access controls.
Network Egress
k9-ssh only needs outbound HTTPS access to the Key9 API host (ssh-api.k9.io, port 443). If you run a host-based firewall, restrict outbound connections from the key9 user accordingly:
iptables (Linux)
pf (OpenBSD / FreeBSD)
API Key Rotation
Rotate your Key9 API key periodically and after any suspected compromise. After updating the key in the Key9 dashboard:
Update
/opt/k9/etc/k9.yamlwith the new key.Reload sshd if it caches the key (typically it does not, since k9-ssh is re-executed per login).
Verify with
sudo -u key9 /opt/k9/bin/k9-ssh --user=<test-user>.
Threat Model
Attacker supplies crafted username to manipulate API URL
Username regex rejects anything that is not a valid Unix username
Malicious API response contains invalid key data
All keys are validated before output
k9-ssh called by unexpected user
run_as check causes immediate exit
API credentials extracted from config
File permissions (640, root:key9); config not readable by world
Network interception of API traffic
HTTPS enforced for cloud API; plain HTTP acceptable only for loopback-bound k9-proxy (traffic never leaves the host)
k9-ssh binary tampered with
SHA-256 checksums provided for all release binaries; install from trusted source only
Key9 service unavailable
Fail-safe: no keys returned means authentication falls through; combine with a local break-glass account if needed
Last updated