Skip to content

feat(config): store API keys in OS keychain instead of plaintext#2480

Open
officialasishkumar wants to merge 1 commit intocharmbracelet:mainfrom
officialasishkumar:feat/keyring-api-key-storage
Open

feat(config): store API keys in OS keychain instead of plaintext#2480
officialasishkumar wants to merge 1 commit intocharmbracelet:mainfrom
officialasishkumar:feat/keyring-api-key-storage

Conversation

@officialasishkumar
Copy link
Copy Markdown

@officialasishkumar officialasishkumar commented Mar 24, 2026

This pull request introduces secure credential storage using the operating system's keychain, migrating API keys and OAuth tokens out of plaintext JSON config files where possible. The main changes include the addition of a KeyringStore abstraction, automatic migration of existing credentials to the OS keychain, updates to the configuration logic to leverage secure storage, and improved user messaging about where credentials are stored.

Secure credential storage and migration:

  • Added internal/config/keyring.go, implementing KeyringStore for storing API keys and OAuth tokens in the OS keychain (macOS Keychain, Windows Credential Manager, or Linux Secret Service), with logic to detect availability and fall back to file-based storage if needed. ([internal/config/keyring.goR1-R156](https://github.com/charmbracelet/crush/pull/2480/files#diff-f36b46e3dd70e8c116136433c381b610392ed6e6d61f6a5ed7076f7f3851945aR1-R156))
  • On config load, automatically migrates plaintext API keys and tokens from the JSON config file to the OS keychain, replacing them with a marker value in the config. This ensures future reads use the secure store and that secrets are not left in plaintext. (Ff75ee58L169R169, Ff75ee58L332R332, Ff75ee58L863R863)

Configuration logic and API changes:

  • Updated ConfigStore to use the keyring for storing and retrieving provider credentials, including new helper methods for persisting API keys and OAuth tokens securely. (Fd22d490L167R167, Fd22d490L265R265, Fd22d490L333R333)
  • Refactored login and credential-setting logic to use the new secure storage APIs, ensuring new credentials are stored securely when possible. ([internal/cmd/login.goL114-R114](https://github.com/charmbracelet/crush/pull/2480/files#diff-1e6a22c2133efaeb4e774b02902eaeacea5b7999c4a33c8f909471e6342b7addL114-R114), Fc0b836bL174R174, Fd22d490L167R167, Fd22d490L265R265, Fd22d490L333R333)

User experience improvements:

  • Updated the API key input dialog to inform users whether their credentials will be stored securely in the OS keychain or written to the config file, providing transparency about credential handling. ([internal/ui/dialog/api_key_input.goR172-R182](https://github.com/charmbracelet/crush/pull/2480/files#diff-b06a1c11c12e59d8b4b2fd564b34a81445c41cdd2ee9390a31c9c0776665d629R172-R182))

Dependency and plumbing updates:

  • Added new dependencies for keychain support (github.com/zalando/go-keyring, github.com/danieljoos/wincred) in go.mod. ([go.modR64](https://github.com/charmbracelet/crush/pull/2480/files#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6R64), F4ac52a9L106R106)
  • Updated the configuration store and loader to initialize and expose the keyring, and to use it throughout the codebase where credentials are handled. ([[1]](https://github.com/charmbracelet/crush/pull/2480/files#diff-8444bfa4415d597e85ba47f294904015a6a49089e5ed5f7e613c0410b749f7f5R49), [[2]](https://github.com/charmbracelet/crush/pull/2480/files#diff-4aa2cb4ec8ace243da263207905baec16af83599529dc56d987049854ee9dea9R31), Fd22d490L59R59)

These changes significantly improve the security of credential storage in the application by leveraging the user's OS keychain when available, while maintaining compatibility with environments where secure storage is not possible.

Closes #2477

  • I have read CONTRIBUTING.md.
  • I have created a discussion that was approved by a maintainer (for new features).

API keys and OAuth tokens are now stored in the operating system's
native keychain (macOS Keychain, Windows Credential Manager, Linux
Secret Service) instead of plaintext in crush.json. Existing plaintext
keys are automatically migrated to the keychain on first load.

A placeholder marker (__keyring__) is written to the JSON config file
so the config system still recognizes that a provider is configured.
When the keychain is unavailable (headless servers, CI, containers),
the system falls back to file-based storage with a warning. Set
CRUSH_DISABLE_KEYRING=1 to force file-based storage.

Closes charmbracelet#2477

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@officialasishkumar officialasishkumar requested a review from a team as a code owner March 24, 2026 21:45
@officialasishkumar officialasishkumar requested review from andreynering and aymanbagabas and removed request for a team March 24, 2026 21:45
@officialasishkumar officialasishkumar force-pushed the feat/keyring-api-key-storage branch 2 times, most recently from d30c2b3 to 5f0db27 Compare March 24, 2026 21:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Store API keys in OS keychain system

1 participant