anthropics / anthropics/claude-code

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

Abierto
#91,158 1 comentario 0 reacciones 0 asignados Ver en GitHub
area:auth area:security bug has repro platform:macos
Lenguaje dominante
Python
Estrellas
145k
Forks
23.1k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

## 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.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Línea de trabajo

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.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
macos, python
Área
authentication, authorization, cli, security
Tipo de issue
Error
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Activo
Claridad
Bastante claro
Aptitud para principiantes
32/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.