Security & threat model
sshelf stores SSH passwords so it can auto-supply them. This document states exactly what
that protects against and what it does not. The shipped root SECURITY.md (M8) is a
user-facing summary of this.
Strongly prefer SSH keys / agent over stored passwords. Password storage exists for hosts you can’t use keys with; it is the least secure option
sshelfoffers.
Where secrets live
- Primary — OS keyring: macOS Keychain (
security-framework) or Linux Secret Service over D-Bus (keyringcrate). Servicesshelf, account = hostid. agevault (opt-in / headless): ifSSHELF_VAULT_PASSPHRASEis set, secrets go in the XDG data dir asvault.age, encrypted with that passphrase (agepassphrase mode = scrypt KDF + ChaCha20-Poly1305). This is the path for headless Linux with no Secret Service daemon, and for automation/CI. v1 reads the passphrase from the env var (deterministic, scriptable); an interactive prompt + auto-detection of a missing keyring are future enhancements. Env-inheritance tradeoff: the askpass helper runs as ssh’s child and reads the env var to unlock the vault, so for hosts with a stored secret the passphrase is necessarily in the ssh process tree’s environment (/proc/<pid>/environ, same-user readable). For hosts with no stored secret,ssh.rs::configure_askpassstrips the variable before the exec.- Provisioning:
sshelf set-password <name|id>stores a secret from stdin (so it can be piped in headless setups) without going through the TUI. - Never in
hosts.toml,state.json, logs, shell history, or process arguments.
The host-key id is the lookup key in both stores, so renaming a host keeps its secret.
How the password reaches ssh
Via SSH_ASKPASS — ssh calls our helper, which prints the secret on stdout. The password
is never passed as a CLI argument (no sshpass -p), so it never appears in ps/argv. See
ssh-command.md. The helper matches the shape of OpenSSH’s standard
prompts — a login password (…password:) or a key passphrase (Enter passphrase for key …) —
and declines host-key confirmations, OTP/verification codes, and arbitrary server text, so a
keyboard-interactive server can’t phish the stored secret by merely mentioning “password”.
Threat model
Protected against
- On-disk plaintext exposure — secrets are in the OS keyring or encrypted at rest in the vault.
- Process-listing / argv leakage — password is delivered via stdin/stdout to
ssh, not argv. - Shell-history leakage —
sshelfnever echoes the command containing a password. - Casual file snooping — the vault requires the master passphrase (memory-hard KDF).
hosts.tomlsharing — it contains no secrets, so it’s safe to commit/share/back up.- Config-file corruption — atomic writes; a crash mid-write leaves the prior file intact.
NOT protected against (out of scope)
- A root/admin attacker or malware on the machine — can read process memory, the keyring,
or keystrokes.
sshelfassumes you trust your own machine. - Keyloggers — can capture the master passphrase as you type it.
- A compromised OS keyring daemon — we trust the platform’s secret service.
- Physical theft without full-disk encryption — use FDE; that’s an OS-level control.
- Unencrypted backups / cloud sync of
vault.age— the vault is encrypted, but treat it as sensitive; don’t rely on it as your only protection in an untrusted backup.
Assumption: sshelf targets a developer/operator’s own (trusted) machine, not shared or
hostile hosts.
Operational notes
- No password recovery. Forgetting the vault master passphrase means losing vault secrets. Use a passphrase you can recover (e.g. from another password manager).
- macOS unsigned builds: the re-exec’d askpass child reading Keychain can trigger an OS approval prompt on each connect (Keychain ACLs are keyed to code signature). Ad-hoc sign dev builds; release builds should be signed.
StrictHostKeyChecking=accept-newtrusts a new host’s key on first connect but still hard-fails if a known host’s key changes (MITM protection retained).- Network:
sshelfmakes no network connections of its own and has no telemetry; it only ever launches the OpenSSH tools —sshto connect, andssh/sftpfor the file-transfer screen. Transfers authenticate exactly as connect does (keys/agent, or the stored secret viaSSH_ASKPASS) by opening one multiplexedsshControlMaster and runningsftpover it — so no extra secret handling, and the secret still never reaches argv. Remote paths are quoted forsftp’s parser, control characters are stripped from displayed names, andStrictHostKeyChecking=accept-newapplies there too. The optional transfer log (--transfer-log/$SSHELF_TRANSFER_LOG) records thessh/sftpcommands and their stderr for troubleshooting — it contains no secrets (same reason: the password goes via askpass).
Reporting
(M8) Add a SECURITY.md at the repo root with a disclosure contact before public release.