Skip to content

Store a secret in the OS keychain

KeyringStore writes to the macOS Keychain, the Windows Credential Manager, or the freedesktop Secret Service on Linux, depending on the target it was built for.

Write a secret

use rtb_credentials::{CredentialStore, KeyringStore, SecretString};

let store = KeyringStore::new();
store.set("mytool", "anthropic", SecretString::from("sk-ant-...".to_string())).await?;

The two strings are the keychain's service and account identifiers. service is conventionally your tool's name, account the provider or user. Neither is interpreted by this crate; both are passed straight to the platform.

set overwrites an existing entry without complaint.

Read it back

let secret = store.get("mytool", "anthropic").await?;

An absent entry returns CredentialError::NotFound with the name "mytool/anthropic". Any other backend failure returns CredentialError::Keychain carrying the platform's message.

Delete it

store.delete("mytool", "anthropic").await?;

Deleting an entry that does not exist returns Ok(()). There is no need to check for existence first, and no way to distinguish "deleted" from "was not there".

Point a config at the entry you just wrote

anthropic:
  keychain:
    service: mytool
    account: anthropic

Both subkeys are required. Omitting either is a config load error, not a resolution-time miss.

Run it on a Tokio runtime

Every KeyringStore method wraps a blocking call in tokio::task::spawn_blocking, which panics outside a Tokio runtime.

#[tokio::main]
async fn main() -> miette::Result<()> {
    let store = KeyringStore::new();
    // ...
    Ok(())
}

There is no way to use KeyringStore on async-std or smol. If you need one of those, use EnvStore or write your own CredentialStore.

Expect this to fail on servers, containers and CI

Keychain access needs a platform keychain to be present and unlocked. Headless Linux servers, containers and CI runners generally have neither, and get fails there with CredentialError::Keychain rather than NotFound.

That distinction matters when the credential is resolved through a Resolver: a Keychain error aborts the whole resolution, so a configured fallback_env will not rescue it. On machines without a usable keychain, leave the keychain key out of the config entirely and use an environment variable.

On Linux there is a second constraint. The Secret Service needs a D-Bus session bus and a running provider such as GNOME Keyring or KWallet, and a headless server, container or CI runner usually has neither. See persistent keychain storage on Linux.

Handle the platform differences you cannot avoid

Platform Persistence Common failure
macOS across reboots user declines the unlock prompt
Windows across reboots rare; credential manager is generally available
Linux across reboots no D-Bus session or no keyring daemon

A tool that must work everywhere should treat the keychain as one optional source among four, not as its storage layer.