All posts
Security July 12, 2026 3 min Bastion contributors

Storing private keys in the OS keychain

Why Bastion uses the platform keychain for SSH keys, how the integration works on each OS, and the threat model it leaves intact.

security ssh keys

If you use Bastion Terminal with private-key auth, the key material never touches application storage. It lives in the same place your browser already trusts: the OS keychain.

This post is the long form. The short version is on the security page. Here, we walk through why we chose the keychain, how the integration works on each platform, and the threat model that approach does and does not cover.

Why the OS keychain

The reasonable alternatives for storing SSH private keys on a desktop are:

  1. Encrypted file on disk with a passphrase the user supplies at startup.
  2. A separate cloud sync keyed to the user’s account.
  3. The OS credential store (Credential Manager on Windows, Secret Service on Linux).

Each has different properties.

Option 1 is what ssh-agent does. It’s fine, but it forces the user to re-enter a passphrase on every cold start and to manage that file’s permissions themselves. A casual chmod 644 mistake exposes the key.

Option 2 makes the key recoverable across devices. It also means the key material lives on infrastructure we operate, which contradicts the local-first posture. We rejected it.

Option 3 is the right fit. The keychain is already where the user stores the passwords for their email, their bank, and their password manager. It is hardened against memory dumps by a logged-in user, encrypted at rest with the user’s login key, and audited by the platform vendor. We get all of that for free.

How it works on each platform

The Rust core talks to the platform keychain through thin, well-trodden crates. The frontend never sees the key material. It just gets a key ID back, and the SSH service resolves the ID to a key inside the keychain when it needs to sign.

Windows: Credential Manager

We use windows-native to talk to the Credential Manager API. The generic credential target name is bastion-terminal:key:<id>. The credential blob holds the OpenSSH-format key body.

Credential Manager scopes the credential to the user’s profile. Other Windows users on the same machine can’t see it. Windows encrypts the blob with DPAPI, keyed to the user’s login credentials.

Linux: Secret Service

We use the secret-service crate, which talks to GNOME Keyring, KWallet, or any other Secret Service implementation over D-Bus. The collection defaults to “default” and the item is namespaced to bastion-terminal.

The Linux desktop experience varies by distribution, but in practice the keyring unlocks when the user logs in, and the key is available to Bastion until the screen locks.

The threat model

The keychain approach gives us:

  • No plaintext key on disk anywhere we control.
  • No key material crossing the IPC boundary: the frontend only sees key IDs.
  • No key material in logs, crash dumps, or scrollback.
  • The platform’s existing unlock semantics: if your screen is locked, the key is unavailable.

It does not protect against:

  • A user-level attacker who is already logged in as you. If they unlock the keychain (or you left it unlocked), they can read the key. This is fundamental to any local-only scheme.
  • A compromised app that the user willingly runs. The credential item is tied to the user’s session.
  • A platform credential-store bug. This is a “trust the platform vendor” assumption. Microsoft and the Linux desktop keyring projects have a strong interest in keeping those stores correct.

What you should do

  • Use a passphrase on your SSH key. The keychain protects the key at rest; the passphrase protects the key if the keychain is ever exposed.
  • Review your stored keys periodically. Old keys accumulate. Remove the ones you’re not using.
  • Verify fingerprints on first connect. Bastion blocks mismatches with a hard confirmation prompt. Read it.

What 1.1.0 does

Version 1.1.0 stores SSH private keys in the OS credential store. Keys are not written to an in-app file and are not backed up to a Bastion cloud. The security model describes that storage.