You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Today the CLI stores DNSimple API tokens in plaintext at ~/Library/Application Support/dnsimple/credentials.yml (and equivalents on Linux/Windows). Anyone with read access to that file — including malware running as the user, accidental backups, screen-shared terminals running cat, etc. — gets the token.
We should move tokens out of the file and into the OS keyring (macOS Keychain, Linux Secret Service, Windows Credential Manager), with the on-disk file storing only non-sensitive metadata (host, account ID, context name, user email).
Motivation
Tokens are bearer credentials with full account access. Plaintext-on-disk is the lowest possible bar.
Users on shared workstations, devs running with set -x, and folks who commit dotfiles have all hit this category of leak in other CLIs.
This is the standard pattern for CLIs that handle long-lived API tokens (gh, aws, op, glab, doctl).
Reference: how gh does it
The gh CLI uses the same pattern we'd want:
~/.config/gh/hosts.yml stores hostname → user → non-sensitive metadata.
The actual OAuth tokens live in the OS keyring, keyed by (hostname, user).
A plaintext fallback exists for environments without a keyring (containers, headless CI), gated behind a configuration option.
Resolution chain at runtime: env var → keyring(host, active user) → keyring(host) (legacy fallback).
Migrate existing plaintext tokens into the keyring on first run, then strip them from the file. Migration should be silent on success and leave a .bak file for one cycle.
Add a fallback mode for environments where the keyring is unavailable (CI, containers without dbus, SSH sessions without an unlocked keychain). The fallback should be opt-in, not automatic, and emit a warning the first time it's used. Suggested gate: DNSIMPLE_KEYRING=plaintext env var.
Honour DNSIMPLE_TOKEN env var override exactly as today — env always wins over keyring.
Open questions
Library choice.zalando/go-keyring is simpler; 99designs/keyring (which gh uses) supports more backends including encrypted file fallback. Decision deferred to implementation.
Keyring entry key.(host, account_id) is the most explicit but couples the keyring schema to the credentials schema. context_name is cleaner but means renaming a context requires re-keying the keyring. Decision deferred.
Headless / CI behaviour. Hard-fail or fall back to plaintext with a warning? Recommend the env-var-gated fallback in step 4.
Migration of existing plaintext tokens. Run on first load after upgrade, or on first explicit auth login? Recommend first load, with the .bak safety net.
Summary
Today the CLI stores DNSimple API tokens in plaintext at
~/Library/Application Support/dnsimple/credentials.yml(and equivalents on Linux/Windows). Anyone with read access to that file — including malware running as the user, accidental backups, screen-shared terminals runningcat, etc. — gets the token.We should move tokens out of the file and into the OS keyring (macOS Keychain, Linux Secret Service, Windows Credential Manager), with the on-disk file storing only non-sensitive metadata (host, account ID, context name, user email).
Motivation
set -x, and folks who commit dotfiles have all hit this category of leak in other CLIs.gh,aws,op,glab,doctl).Reference: how
ghdoes itThe
ghCLI uses the same pattern we'd want:~/.config/gh/hosts.ymlstores hostname → user → non-sensitive metadata.(hostname, user).The relevant code is in cli/cli
internal/keyringandinternal/config(AuthConfig.ActiveToken,TokenFromKeyringForUser). The underlying library iszalando/go-keyring(or99designs/keyring, depending on the abstraction we want).Proposed change
keyringpackage wrapping a vendor library (likelyzalando/go-keyringfor its smaller surface area; revisit if we hit platform gaps).(host, account_id)or(context_name)— to be decided in implementation..bakfile for one cycle.DNSIMPLE_KEYRING=plaintextenv var.DNSIMPLE_TOKENenv var override exactly as today — env always wins over keyring.Open questions
zalando/go-keyringis simpler;99designs/keyring(whichghuses) supports more backends including encrypted file fallback. Decision deferred to implementation.(host, account_id)is the most explicit but couples the keyring schema to the credentials schema.context_nameis cleaner but means renaming a context requires re-keying the keyring. Decision deferred.auth login? Recommend first load, with the.baksafety net.Dependencies
References
auth switchsilently accepts invalid accounts and auth status ignores stored default #25 — the original auth fix that surfaced the credentials storage discussion