Skip to content

Claude Code

Claude Code stores MCP OAuth tokens in plaintext on Linux

4 min read

Written by an AI model from the linked sources and checked against them by a second model. How we work

All about: Claude Code versions and changes →
Image: secretspec.dev

Claude Code’s MCP documentation says authentication tokens are “stored securely”. On Linux, that rests on a single file permission. A September 2 post on the SecretSpec blog looked at Claude Code 2.1.257 after it had authenticated to several remote MCP servers. It found their OAuth access tokens stored as plain JSON in ~/.claude/.credentials.json, a file set to mode 0600.

The tokens sit under a top-level mcpOAuth object, with one entry per server. The post’s redacted Cloudflare example carries accessToken, clientId, discoveryState, redirectUri, serverUrl and a serverName of “cloudflare-observability”. None of this contradicts Anthropic’s own credential-management documentation, which the post quotes. On macOS, Claude Code uses the encrypted Keychain and falls back to the same file when the Keychain is unavailable. On Linux, it uses the file with mode 0600. On Windows, it keeps the file under %USERPROFILE%\.claude\, where it inherits the access controls of the user’s profile directory. “That is a much narrower claim than most people hear when a product says a credential is ‘stored securely,’” the author writes.

Mode 0600 stops other user accounts on the same machine from reading the file, BuilderWithin explains. It does nothing against code that already runs as you, such as a malicious dependency or a compromised editor extension. That kind of attacker already exists around these tools: in June, the Miasma malware went after Claude Code, Gemini CLI, Cursor and VS Code configurations and stole cloud credentials. MCP tokens may not be the only thing in the file, either. A GitHub gist on Claude Code’s storage says the same .credentials.json also holds the main account login on Linux, WSL and native Windows. It sits under claudeAiOauth, with an access token, a refresh token, an expiry and scopes. On macOS, according to the gist, Claude Code deletes the file and moves those credentials into the Keychain.

The post argues that the browser flow makes the secret easy to miss. You run claude mcp login, approve access in the browser and come back to a connected server. Nobody copied a token from a dashboard or pasted it into a config file. Claude Code still received a credential, though, and it has to persist it for the connection to survive a restart. OAuth handles the rest of the job: discovering the authorization server, requesting scopes (which Anthropic’s docs let users pin), exchanging the authorization code, refreshing and revoking. “What OAuth does not specify is a secure local vault,” the author writes. In the post’s comparison table, both an OAuth credential and a scoped API token have to be stored by the client, and both can be replayed if stolen.

Codex, OpenAI’s coding agent, makes this configurable. Its configuration reference documents auto, file and keyring backends for MCP OAuth, and mcp_oauth_credentials_store = "keyring" selects the system keyring. The author says this is not a general secret-provider interface. It does mean a plaintext file is not the only choice on Linux, which in Claude Code it still is.

The fix the post proposes uses SecretSpec, the project whose blog published it. Claude Code would put a replaceable credential-store interface between its OAuth client and the disk. It would serialize one credential per server and call the SecretSpec TypeScript SDK to load, save or delete it. The SDK embeds SecretSpec’s Rust resolver, and version 0.20 has 33 provider integrations, from local keyrings and password managers to encrypted files and cloud secret managers. A user could make the system keyring the default with secretspec config global init --provider keyring --profile default. A team could require OpenBao, and a headless workstation could use an age-encrypted store. The OAuth flow itself would not change. SecretSpec says it would add the small get/set/delete surface this needs, and that version 0.21 will let applications reach providers over a local protocol without embedding an SDK.

Until then, BuilderWithin suggests running /mcp inside Claude Code to see which servers are authenticated, then choosing “Clear authentication” or running claude mcp logout <name> for any you don’t use. Later, on October 13, the outlet added a correction. On macOS, Claude Code falls back to the file when the Keychain rejects a write, for example when it is locked during an SSH session, although SSH does not always force that fallback. And clearing a local copy does not revoke a token that has already been stolen.

SecretSpec has already shipped Git and Docker credential helpers and proposed a secret resolver interface for Nix. It cannot do the same for Claude Code: the public repository leaves out the core CLI implementation, and the license is all rights reserved. Only Anthropic can change how MCP OAuth tokens are stored. The post ends on the case for open source: “when a secret crosses the wrong boundary, users do not have to wait for the vendor to decide that the boundary matters.”

No comments yet. Be the first.

Join the conversation

Sign in to leave a comment. It appears after moderation.