Coding agents need API keys. API keys in plain-text config files are a recurring foot-gun — they get committed to git, included in screen recordings, and leaked through error messages. The OS-native keychain stops all of that. Ringlet uses it by default.

What the keychain stores

Every API key Ringlet manages is stored as a generic password in the OS-native credential store. The Ringlet binary holds no secret material in memory longer than it needs; on every launch, it looks up the key by service + account name and uses it to construct the env block for the agent.

The storage format is:

OSBackendService nameAccount name
macOSKeychain Services (Security.framework)ringlet<provider>/<alias>
LinuxSecret Service API (GNOME Keyring, KWallet)ringlet<provider>/<alias>
WindowsCredential Manager (generic credentials, wincred)ringlet/<provider>/<alias>(alias)

Examples:

ringlet/anthropic/default
ringlet/anthropic/work
ringlet/anthropic/personal
ringlet/openai/default
ringlet/minimax/default
ringlet/openrouter/default

The cross-platform abstraction is the Rust keyring crate, which we picked because:

  • It’s actively maintained.
  • It handles the three platforms with a single API.
  • It doesn’t shell out to platform tools; it links the native libraries.

Adding and inspecting keys

# Interactive: Ringlet prompts, key is read via getpass()
ringlet keychain set anthropic default

# Non-interactive (from env)
ANTHROPIC_API_KEY=sk-ant-... ringlet keychain set anthropic default --from-env ANTHROPIC_API_KEY

# List entries (names only — no secret values shown)
ringlet keychain list
# anthropic/default
# anthropic/work
# openai/default

# Remove
ringlet keychain remove anthropic work

# Rotate (replace existing)
ringlet keychain set anthropic default --replace

ringlet keychain show is deliberately not a command. If you want to see the raw secret, use the OS’s own tool (Keychain Access, secret-tool, Credential Manager). Ringlet treats keys as write-only.

Aliases and how profiles reference them

A profile references a keychain entry by alias, not by value:

# ~/.ringlet/profiles/work/ringlet.toml
agent = "claude"
provider = "anthropic"

[provider-config]
key-alias = "work"       # → ringlet/anthropic/work

Two profiles can share an alias if you want:

# both ~/.ringlet/profiles/work/ringlet.toml
# and  ~/.ringlet/profiles/work-staging/ringlet.toml
[provider-config]
key-alias = "work"

Both will load the same key on launch. Useful for “different config, same billing account.”

What the file-backed fallback looks like

On headless Linux without a Secret Service implementation, Ringlet falls back to a TOML file:

# ~/.config/ringlet/credentials.toml — file mode 0600

[anthropic.default]
key = "sk-ant-..."

[anthropic.work]
key = "sk-ant-..."

[openai.default]
key = "sk-..."

The file is created with 0600 permissions; Ringlet refuses to read it if the mode is more permissive. There’s a startup warning:

⚠ Using file-backed credentials (~/.config/ringlet/credentials.toml).
  For better security on multi-user systems, install GNOME Keyring or set up a service-mode keychain.

You can opt out of the fallback in ~/.config/ringlet/config.toml:

[credentials]
backend = "system"   # "system" | "file" | "system-only"

system-only refuses to start if the system keychain isn’t available — safer for production-style deploys.

Daemon authentication is separate

Don’t confuse credentials (API keys for providers) with daemon authentication (the bearer token for 127.0.0.1:8765). They’re two different things stored in two different places:

  • API keys: keychain, accessed by aliases.
  • Daemon bearer token: ~/.config/ringlet/http_token (file mode 0600). Rotated via ringlet daemon rotate-token.

The bearer token lives in a file because the daemon needs to read it on startup before any user is logged into the session keychain (think: cron-launched daemon on a server). The keys live in the keychain because they’re user-scoped and read on-demand.

What this protects against

  • Plain-text leakage. Keys aren’t in your shell history, your .env files, or your git commits.
  • Process-level access on macOS/Windows. A separate process can’t read another user’s keychain without authentication.
  • Accidental copy-paste. Ringlet never logs the key value, never prints it, never sends it in an error message. If you strings the binary you’ll find the URL fragments but not your key.

What this does NOT protect against

  • An attacker with your shell. If they can run commands as you, they can ask Ringlet to launch an agent — which means they get the same effective access to the provider API as you do.
  • A keylogger. If the keychain is unlocked when Ringlet asks for the key, a keylogger or similar wouldn’t see the key but would see whatever the agent does.
  • The agent itself. Once the agent is running, it has the key in its own process memory. That’s how the API call gets made. The agent is trusted (it’s the binary you apt/brew installed).

Migrating from existing setups

If you’ve been running agents with ANTHROPIC_API_KEY in your shell rc, migrate is one step:

# Pick up the key from your current env
ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY ringlet keychain set anthropic default --from-env ANTHROPIC_API_KEY

# Remove it from shell rc
sed -i '' '/ANTHROPIC_API_KEY/d' ~/.zshrc

# Restart your shell, verify
source ~/.zshrc
echo $ANTHROPIC_API_KEY  # → (empty)
ringlet profiles run default  # → claude launches with key from keychain

Same for OPENAI_API_KEY, XAI_API_KEY, etc.

Auditing keychain access

macOS Keychain Access shows which apps accessed each item. Linux’s seahorse (GNOME Keyring frontend) does similar. Windows Credential Manager is more opaque, but the API access is auditable via the Event Log.

For Ringlet-side audit, the --audit-log flag records every keychain lookup (with the alias name, never the value). Useful for “who/what asked for the work-account Anthropic key this week.”

What’s next

The current keychain implementation is single-machine, single-user. The team tier will add:

  • HSM-backed key storage (AWS KMS, HashiCorp Vault, etc.) for shared infrastructure.
  • Time-bound key lease (key valid for N hours, then re-derived).
  • Per-agent capability scoping (key X can only be used for tool Y).

For now, the OS keychain is the right default and the right place to look if a key isn’t behaving the way you expect.