Store API credentials in the OS secret store instead of plaintext

Status Open
Maintainer reply None cached
Activity 4 comments · opened Jul 2, 2026

Proposal: Store API credentials in the OS secret store instead of plaintext

Summary

Claude Code currently persists API credentials (Anthropic API keys, OAuth tokens, and any third-party tokens picked up via MCP server configs) to plaintext files on disk (e.g. under ~/.claude/ / ~/.config/claude-code/). This proposal asks that Claude Code adopt OS-native secret storage as the default credential backend, with plaintext file storage kept only as an explicit opt-in fallback for headless/CI environments where no secret store is available.

This mirrors a change the Rust toolchain shipped for Cargo: cargo login now supports a global-credential-providers config that routes registry tokens through cargo:libsecret (Linux/Secret Service), and equivalent providers exist for macOS Keychain and Windows Credential Manager, instead of writing ~/.cargo/credentials.toml in plaintext. That change is a useful precedent for scope and rollout shape.

Problem

  • Any plaintext credential file is readable by any process running as the same user — no additional compromise is required beyond arbitrary local code execution (a malicious npm/pip/cargo package, a compromised editor extension, etc.).
  • Backup tools, dotfile-sync tools, and cloud drive sync (Dropbox, iCloud, OneDrive) commonly sweep up home-directory config files, meaning API keys can end up replicated to third-party storage without the user realizing it.
  • Shell history, crash dumps, and tar/cp -r of a home directory during support/debugging can leak the file contents inadvertently.
  • Anthropic API keys are billable credentials — leakage has direct financial impact, not just a security-hygiene concern.
  • Many developers already rely on OS secret stores for other CLI tools (gh, aws configure sso, docker login with credsStore, cargo login as above, npm via keytar-based wrappers) — a plaintext credential file is now the exception rather than the norm for developer tooling.

Proposed design

Backends

Add a pluggable credential-storage layer with OS-native backends:

| Platform | Backend |
|---|---|
| macOS | Keychain (Security.framework via security CLI or a native binding) |
| Linux | Secret Service API (libsecret — GNOME Keyring, KWallet, KeePassXC's Secret Service shim) |
| Windows | Credential Manager (wincred) |

A single credential is a namespaced entry, e.g. service = claude-code, account = api-key or oauth:<provider> or mcp:<server-name>, mirroring how the current config keys credentials.

Fallback

Not every environment has a secret store available (headless Linux boxes, containers, CI runners, WSL without a keyring daemon). In those cases:

  1. Detect the absence of a usable Secret Service / Keychain / Credential Manager at startup.
  2. Fall back to the existing plaintext file, but emit a one-time warning (not on every invocation) that credentials are stored unencrypted and suggest installing/enabling a keyring (e.g. gnome-keyring, kwalletd, or a headless option like keyctl/pass).
  3. Support an explicit escape hatch, e.g. CLAUDE_CODE_CREDENTIAL_STORE=file, for users who intentionally want file-based storage (containers with a mounted secrets volume, etc.) without the warning noise.

Migration

  • On first run after the change ships, if a plaintext credential file exists and a secret store is available, migrate the credential into the secret store and delete (not just leave) the plaintext copy, with a printed confirmation of what happened.
  • If migration fails for any reason (locked keyring, permission issue), leave the plaintext file untouched and surface a clear error rather than silently losing the credential.

Config surface

  • A config key (e.g. credentialStore: "auto" | "keychain" | "file") analogous to Cargo's global-credential-providers, defaulting to "auto" (try OS store, fall back to file with a warning).
  • Respect this setting per-machine, not per-project, since it reflects local OS capability rather than project policy.

Non-goals

  • This does not attempt to solve credential storage for remote/headless agent execution (e.g. Claude Code running inside a CI job or a cloud sandbox) — those environments legitimately need injectable, ephemeral credentials (env vars, short-lived tokens) rather than a local keyring, and should continue to support ANTHROPIC_API_KEY-style env var injection unchanged.
  • This does not change how MCP servers themselves store credentials internally — only how Claude Code stores the credentials it holds on the user's behalf to talk to those servers/APIs.

Suggested implementation library

@napi-rs/keyring — a Node.js binding (via napi-rs) around the Rust keyring-rs crate, the same crate that backs Cargo's own cargo:libsecret / cargo:macos-keychain / cargo:wincred credential providers referenced below. It covers all three target backends (macOS Keychain, Windows Credential Manager, Linux Secret Service) without a build-time libsecret dependency, which keeps it friendly to headless/WSL2 environments and lines up directly with the fallback behavior described above. It supersedes the now-archived keytar, off of which Microsoft's own @azure/identity-cache-persistence and MSAL libraries are actively migrating for the same reason.

Prior art

  • Cargo: cargo:libsecret / cargo:macos-keychain / cargo:wincred credential providers, configured via registry.global-credential-providers in config.toml.
  • Docker: credsStore / credHelpers in ~/.docker/config.json, backed by docker-credential-osxkeychain, docker-credential-secretservice, docker-credential-wincred.
  • GitHub CLI (gh): stores auth tokens in the OS keyring by default, with GH_TOKEN/plaintext fallback documented explicitly as the less-preferred path.

Ask

Requesting that the Claude Code team implement this directly.

View original on GitHub ↗

3 Comments

bendavis78 · 2 months ago

Related: #47955 covered the plugin-credential angle of this specifically. Plugin userConfig fields marked "sensitive": true get keychain storage on macOS and Windows, but fall back to a plaintext JSON file on Linux (~/.claude/.credentials.json). No libsecret or Secret Service integration exists. That issue was auto-closed as stale and then locked before it got real engagement.

This proposal is the more complete version of that ask, with an actual backend, fallback, and migration design.

This matters beyond hygiene for plugin users. Sentry, Cortex, Jira, and similar plugins each add a long-lived auth token. That's N secrets in cleartext per developer, not just one API key. Any local code execution, like a malicious npm package or a compromised editor extension, can read all of them without extra privilege.

👍 for consolidating tracking here instead of splitting across issues.

mkurz · 2 months ago

+1

Surprised this isn't supported yet. codex supports storing secrets in the OS keyring under Linux already with the Secret Service D-Bus API.

Showing cached comments. Read the full discussion on GitHub ↗