anthropics / anthropics/claude-code

[BUG] Credential storage: refresh token written in plaintext, and Keychain items accumulate without bound

オープン
#91,158 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る
area:auth area:security bug has repro platform:macos
主要言語
Python
スター
145k
フォーク
23.1k
PR マージ指標
PR 指標を取得中

説明

## What's Wrong?

Two related credential-storage problems on macOS, plus a scope/auth-mode issue.

1. PLAINTEXT REFRESH TOKEN. `~/.claude/.credentials.json` (mode 0600) contains a live
access token and a refresh token valid for 27 days. A Keychain item requires
per-application ACL consent; a 0600 file requires only executing as the user. The
Keychain is in use simultaneously (see 2), so this is not a fallback for a missing
platform store.

2. UNBOUNDED KEYCHAIN ACCUMULATION. The login keychain holds 1,306 items with service
prefix `Claude Code-credentials` out of 1,440 total services — 90.6% of the keychain.
Suffixes are uniformly 8 lowercase hex chars with no other length present, indicating
one generating code path. Observed growing live during a single session:
1,295 -> 1,298 -> 1,300 -> 1,306. Not correlated with sessions
(68 session-env dirs, 2 sessions dirs).

3. SCOPE DOWNGRADE ON RE-AUTH. The authorize URL requests `scope=user%3Ainference`
(one scope) while the stored credential carries five, including
`user:sessions:claude_code`. Completing that flow yields fewer capabilities than the
credential it replaces, and drops the scope `claude --teleport` requires — so the
offered login cannot fix the failure that prompted it. No warning is shown.

4. AUTH MODE MISREPORTED. `claude --teleport ` returns "API key authentication is
not sufficient" while a valid Max OAuth credential with the required scope is present
and no API key is configured anywhere.

## What Should Happen?

1. Refresh tokens stored in the Keychain, not plaintext, when the Keychain is available.
2. Superseded credential items removed; item count bounded.
3. Re-auth requests at least the scopes the existing credential holds, or warns explicitly.
4. Auth mode reported accurately based on actual credential state.

## Error Messages / Logs

$ claude --teleport session_01EWeDKeB3S5mWCn92jWJar4
Claude Code web sessions require authentication with a Claude.ai account. API
key authentication is not sufficient. Please run /login to authenticate, or
check your authentication status with /status.

Machine state at that same moment:
accessToken valid (expires 2026-09-01T11:31:05Z)
refreshToken valid (expires 2026-09-28T20:09:46Z)
subscription max
scopes user:file_upload user:inference user:mcp_servers user:profile
user:sessions:claude_code
ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN / CLAUDE_CODE_OAUTH_TOKEN : all unset
(verified: env -i HOME=$HOME /bin/zsh -lic, plus .zshrc .zshenv .zprofile
.profile .bashrc, plus settings.json and settings.local.json)

## Steps to Reproduce

1. Authenticate normally with a Claude.ai (Max) account on macOS.
2. Observe the plaintext store exists alongside the Keychain:
ls -l ~/.claude/.credentials.json
3. Count credential items:
security dump-keychain | grep -c '"svce"="Claude Code-credentials'
4. Use Claude Code normally for a period; re-run step 3. The count increases and
superseded items are never removed.
5. Run `claude --teleport `. Observe the API-key message despite a
valid OAuth credential.
6. Start `/login` and inspect the authorize URL: `scope=user%3Ainference` only.

## Additional Information

MEASURED, NEGATIVE — Keychain performance is NOT degraded at this item count, so this
is a hygiene and blast-radius issue, not a performance one. `SecItemCopyMatching`
(attributes only, 25 iterations): exact-service lookup median 0.258 ms against a
0.135 ms nonexistent-service control floor (control returns errSecItemNotFound/-25300,
confirming the query was valid and searching); full enumeration of 1,430 items 14.3 ms.

RELATED: #81281 describes a stale `~/.claude/.credentials.json` overriding a valid
`CLAUDE_CODE_OAUTH_TOKEN`. This report is distinct — no env var is set here (verified
above) and the error string differs — but both indicate the client misreading its own
auth state, and #81281 independently confirms the plaintext file is authoritative.

---

### Environment

| | |
|---|---|
| Claude Code | 2.1.212 (Claude Code) |
| Platform | Claude API (Max subscription) |
| OS | macOS 27.0 (build 26A5425a, developer beta) |
| Hardware | Apple M2 Pro, arm64 |
| Terminal / Shell | Ghostty 1.3.1 / zsh (also reproduced in Terminal.app) |
| Model | Opus |
| Regression | No — not tested against a prior version |

**Preflight:** searched existing issues; single bug report; on latest version.

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

調査の方向性

Start by tracing the macOS credential handling used by `/login`, `--teleport`, and the `~/.claude/.credentials.json` store, then compare it with the Keychain items described in the report. Reproduce the item count, scope mismatch, and authentication message using the listed commands and environment checks. Done means refresh tokens are protected, obsolete items are bounded, re-auth preserves or warns about scopes, and `--teleport` reports the actual credential mode.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
macos, python
領域
authentication, authorization, cli, security
issue の種類
バグ
難易度
5/5
見積もり時間
1週間以上
活発さ
活発
明瞭さ
おおむね明確
初心者へのやさしさ
32/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。