Store API tokens in the OS keyring instead of plaintext credentials.yml
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 3
- Forks
- 0
- Avg merge
- 8h 39m
- Merged PRs (30d)
- 9
Description
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 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.ymlstores 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).
The relevant code is in cli/cli internal/keyring and internal/config (AuthConfig.ActiveToken, TokenFromKeyringForUser). The underlying library is zalando/go-keyring (or 99designs/keyring, depending on the abstraction we want).
Proposed change
- Add a
keyringpackage wrapping a vendor library (likelyzalando/go-keyringfor its smaller surface area; revisit if we hit platform gaps). - Update the credentials file schema (post-#28) to store everything except the token. The keyring entry is keyed by
(host, account_id)or(context_name)— to be decided in implementation. - 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
.bakfile 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=plaintextenv var. - Honour
DNSIMPLE_TOKENenv var override exactly as today — env always wins over keyring.
Open questions
- Library choice.
zalando/go-keyringis simpler;99designs/keyring(whichghuses) 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_nameis 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.baksafety net.
Dependencies
- Blocked on dnsimple/dnsimple-cli#28. The named-context refactor is the right time to redesign the storage layer. Doing this before #28 lands would mean two disruptive credentials migrations back-to-back.
References
- dnsimple/dnsimple-cli#28 — named authentication contexts (blocker)
- dnsimple/dnsimple-cli#25 — the original auth fix that surfaced the credentials storage discussion
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the current credentials.yml handling and the named-context refactor in issue #28, which blocks this work. Trace how DNSIMPLE_TOKEN and stored credentials are resolved today. Done means tokens are handled through the agreed keyring design, migration and fallback behavior are specified, and tests cover the security-sensitive paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- authentication, cli, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100