feat(auth): machine-wide Anthropic OAuth shared credential pool - #70088
feat(auth): machine-wide Anthropic OAuth shared credential pool#70088danpetkovic wants to merge 1 commit into
Conversation
Add an opt-in shared OAuth-only Anthropic pool under the Hermes root so every profile resolves the same three grants with one canonical refresh-token chain per account. Shared scope is controlled by a non-secret marker, uses strict root-auth locking for single-use refresh, closes env/Claude-Code/explicit-key bypasses for official api.anthropic.com targets, and guards CLI/dashboard/ backup mutation paths. Includes focused tests, secret scanner, break-glass helper, and docs. Refs: NousResearch#29530, NousResearch#8040
teknium1
left a comment
There was a problem hiding this comment.
Thanks for tackling the real multi-profile OAuth refresh race. Current main still serializes only Codex/xAI refreshes in agent/credential_pool.py:1132-1163, so the underlying Anthropic gap remains.
Problems
hermes_cli/auth.py:516-525catches an unexpected shared-resolver error, then swallows its own re-raise and falls back toANTHROPIC_*credentials at:527-533. Active shared scope must fail closed.agent/auxiliary_client.py:3763-3790catches all shared refresh/load failures at:3780, then can refresh a Claude Code credential and return success at:3790. This is another shared-scope bypass.agent/anthropic_shared_pool.py:1668-1672reads and archives the marker before validating it is not a symlink, contradicting the module's no-follow safety contract.
Suggested changes
- Route every active-shared-scope path through one fail-closed authoritative resolver, including auxiliary refresh.
- Validate marker file type before reading it, then add regression coverage for resolver failures with valid env/Claude Code credentials.
This is an automated hermes-sweeper review.
| return ctx.access_token | ||
| except AuthError: | ||
| raise | ||
| except Exception: |
There was a problem hiding this comment.
This handler fails open. If resolve_shared_anthropic_credential() raises a non-AuthError while the marker is active, the bare re-raise inside the nested try is swallowed by its except Exception, and the function returns an ANTHROPIC_* credential below. Preserve the original error whenever shared scope is active.
| return False | ||
| _evict_cached_clients(normalized) | ||
| return True | ||
| except Exception: |
There was a problem hiding this comment.
Do not swallow shared-pool failures here. The legacy branch immediately refreshes Claude Code credentials and can return success while shared scope is active, bypassing the asserted authoritative shared credential gate.
| ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") | ||
| backup = recovery_dir() / f"{ts}-anthropic-scope.json" | ||
| # Read raw bytes without following if possible | ||
| raw = path.read_bytes() if path.exists() else b"" |
There was a problem hiding this comment.
Validate this path before reading it. Path.read_bytes() follows a symlink, so this archives its target before _path_is_safe_regular_file() rejects the marker at line 1672. Use the existing no-follow validation/read pattern first.
Summary
Implements an opt-in, machine-wide Anthropic OAuth-only credential pool shared by every Hermes profile under one root. Default behavior is unchanged until the operator enables shared scope.
Closes the multi-profile re-materialization / single-use refresh races for Anthropic Max OAuth that were fixed earlier for Codex/xAI.
Design
<root>/shared/anthropic_pool_scope.json(absent= legacy profile mode)auth.json→shared_credential_pools.anthropic(never profilecredential_pool)manual:hermes_pkce), strategy fixed tofill_firsttoken_generation/refresh_attemptfor single-use refresh safetyapi.anthropic.com, ignore env keys, Claude Code files, explicitapi_key, and profile rowshermes auth scope|backup|restore,--sharedon add/remove/reset/logoutPrior art
Test plan
Fork merge: danpetkovic#1 (
32d2482baf2170d73e7da05a4028973086fbf0ff)