claude-code

Claude Code stores MCP OAuth tokens in plaintext on Linux

Claude News

claude-code

Claude Code 2.1.257 keeps MCP OAuth access tokens in plaintext on Linux, inside ~/.claude/.credentials.json with file mode 0600 as the only protection. The finding comes from a September 2 write-up by Secretspec, which authenticated to several remote MCP servers and then inspected the file.

At a glance

  • The file carries a top-level mcpOAuth object with one entry per connected server; the Cloudflare observability entry holds accessToken, clientId, discoveryState, redirectUri, serverName and serverUrl, with every credential value redacted in the published sample.
  • Claude Code's MCP documentation describes authentication tokens as "stored securely", which on Linux means file permissions rather than encryption; macOS instead uses the encrypted Keychain when it is available.
  • Codex documents auto, file and keyring backends for MCP OAuth storage, so setting mcp_oauth_credentials_store = "keyring" selects the system keyring; Claude Code offers no equivalent switch on Linux.

Any process running as the user can read a mode 0600 file, so a single compromised dependency or a stray backup carries live bearer tokens for every MCP server a developer has connected. The gap appears to be a definitional one: "stored securely" in the documentation describes file permissions, not encryption, and the browser-based login flow hides the fact that a long-lived secret was written to disk at all.

Anthropic's documentation gives each platform a different store

Anthropic's credential-management documentation splits behaviour by platform. macOS uses the encrypted macOS Keychain and falls back to ~/.claude/.credentials.json when the Keychain is unavailable. Linux uses that file with mode 0600, and Windows uses %USERPROFILE%\.claude\.credentials.json, inheriting the access controls of the user's profile directory.

The check reported by Secretspec was run on Claude Code 2.1.257 after authenticating to several remote MCP servers. The file had mode 0600 as expected, and it also held a top-level mcpOAuth object keyed per server, with the published Cloudflare observability sample showing every credential value redacted.

OAuth solved delegation and renewal, not local secret storage

The browser flow makes the secret easy to miss. A user runs claude mcp login, approves access in the browser and returns to a connected MCP server, without creating a token, copying it from a dashboard or pasting it into a configuration file. Claude Code still receives a credential and must persist it for the connection to survive a restart.

Through OAuth, Claude Code can discover the authorization server, request specific scopes, complete the authorization-code exchange, refresh an access token and revoke the grant, and Anthropic's MCP documentation lets users pin the scopes it requests. What the protocol does not specify is a secure local vault: like a scoped API token, the resulting bearer token must be stored by the client, can expire and can be replayed if stolen.

SecretSpec 0.20 carries 33 provider integrations behind one interface

Secretspec proposes making Claude Code's persistence layer replaceable: the OAuth client would call a credential-store interface, that interface would call the SecretSpec Node.js and TypeScript SDK, and the SDK would resolve to whichever provider the user or organization selected. The SDK embeds the Rust resolver, so the TypeScript side needs no bespoke code per backend.

SecretSpec 0.20 has 33 provider integrations covering local keyrings, password managers, encrypted files, cloud secret managers and deployment destinations, and providers declare capabilities so a credential store can demand readable and writable storage. A local default is set with secretspec config global init --provider keyring --profile default; a team could require OpenBao or a cloud secret manager, and a headless workstation an age-encrypted store.

Why only Anthropic can patch this

Claude Code's public repository does not include the core CLI implementation and its license is all rights reserved, so an outside credential-store patch is not possible; Secretspec says it can only document the problem and propose the TypeScript integration. Its own roadmap puts versioned resolver and provider IPC in SecretSpec 0.21, which would let applications use providers over a local protocol without embedding an SDK. No timeline for either change has been stated.

Comments

No comments yet. Be the first.

Join the conversation

Sign in with Google to leave a comment. Your name and avatar come from your Google profile, and the comment appears after moderation.

We only use your name and avatar from Google. We never store your email address.