Skip to content

fix(auth): prevent unauthorized Claude Code credential usage in auxiliary fallback chain#7009

Closed
wanpengxie wants to merge 4 commits into
NousResearch:mainfrom
wanpengxie:fix/unauthorized-claude-code-credential-usage
Closed

fix(auth): prevent unauthorized Claude Code credential usage in auxiliary fallback chain#7009
wanpengxie wants to merge 4 commits into
NousResearch:mainfrom
wanpengxie:fix/unauthorized-claude-code-credential-usage

Conversation

@wanpengxie

Copy link
Copy Markdown

Summary

  • Gate _seed_from_singletons("anthropic") behind is_provider_explicitly_configured() — Claude Code credentials are no longer auto-discovered when the user never selected anthropic
  • Add suppressed_sources mechanism to auth.jsonhermes auth remove now prevents re-seeding of removed claude_code entries
  • Gate _resolve_api_key_provider() — auxiliary fallback chain skips anthropic when not explicitly configured

Problem

When a user configures a non-anthropic provider (e.g. kimi-coding) with an invalid API key, the auxiliary client fallback chain silently discovers and uses ~/.claude/.credentials.json without consent. This:

  1. Reads Claude Code OAuth tokens without user authorization
  2. Sends API requests impersonating Claude Code (user-agent: claude-cli, anthropic-beta: claude-code-20250219)
  3. Refreshes tokens and writes back to ~/.claude/.credentials.json, potentially disrupting Claude Code's own auth (scopes field loss causes Claude Code to report "not logged in" — see PR fix(anthropic): write scopes field to Claude Code credentials on token refresh #4126)
  4. Consumes Claude Max subscription quota through a different usage gate (ref: Anthropic Claude subscription auth returns 'You're out of extra usage' in Hermes even after restart/re-login #6475)

hermes auth remove was also ineffective for claude_code source — removed entries were immediately re-seeded on the next load_pool() call.

Reproduction scenario

  1. Install Hermes on a machine with Claude Code authenticated (Claude Max subscription)
  2. Run hermes setup, select kimi-coding, enter a malformed API key
  3. Run hermes chat — main agent fails on kimi, auxiliary client falls through to _try_anthropic() → reads ~/.claude/.credentials.json → uses Claude Max OAuth token for compression/summarization
  4. Token expires → Hermes refreshes → writes back to ~/.claude/.credentials.json
  5. Next day: Claude Code finds modified credentials → may report "not logged in" or hit different usage gate

Context

PR #4210 fixed the same class of bug for the setup wizard gate: "Users got silently routed to someone else's inference without being asked." This PR extends that fix to the remaining unprotected paths:

  • _seed_from_singletons() in agent/credential_pool.py — credential pool auto-discovery
  • _resolve_api_key_provider() in agent/auxiliary_client.py — auxiliary fallback chain
  • auth_remove_command() in hermes_cli/auth_commands.py — remove re-seed prevention

Changes

File Change
hermes_cli/auth.py Add is_provider_explicitly_configured(), suppress_credential_source(), is_source_suppressed()
agent/credential_pool.py Gate _seed_from_singletons("anthropic") behind explicit config check + suppress check
agent/auxiliary_client.py Gate _resolve_api_key_provider() anthropic branch
hermes_cli/auth_commands.py Write suppress flag on claude_code source removal
tests/hermes_cli/test_auth_provider_gate.py 6 tests for gate function
tests/agent/test_credential_pool.py 1 new test + 1 existing test fix
tests/hermes_cli/test_auth_commands.py 1 new test for suppress flag
tests/agent/test_auxiliary_client.py 1 new test for auxiliary gate

Test plan

  • pytest tests/hermes_cli/test_auth_provider_gate.py — 6/6 pass
  • pytest tests/agent/test_credential_pool.py — 28/28 pass
  • pytest tests/hermes_cli/test_auth_commands.py — 16/16 pass
  • pytest tests/agent/test_auxiliary_client.py — 97/98 pass (1 pre-existing failure unrelated to this PR)

🤖 Generated with Claude Code

xwp and others added 4 commits April 10, 2026 15:30
Gate function for checking whether a user has explicitly selected a
provider via hermes model/setup, auth.json active_provider, or env
vars.  Used in subsequent commits to prevent unauthorized credential
auto-discovery.  Follows the pattern from PR NousResearch#4210.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…er config

_seed_from_singletons('anthropic') now checks
is_provider_explicitly_configured('anthropic') before reading
~/.claude/.credentials.json.  Without this, the auxiliary client
fallback chain silently discovers and uses Claude Code tokens when
the user's primary provider key is invalid — consuming their Claude
Max subscription quota without consent.

Follows the same gating pattern as PR NousResearch#4210 (setup wizard gate)
but applied to the credential pool seeding path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, removing a claude_code credential from the anthropic pool
only printed a note — the next load_pool() re-seeded it from
~/.claude/.credentials.json.  Now writes a 'suppressed_sources' flag
to auth.json that _seed_from_singletons checks before seeding.

Follows the pattern of env: source removal (clears .env var) and
device_code removal (clears auth store state).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…configured

_resolve_api_key_provider() now checks is_provider_explicitly_configured
before calling _try_anthropic().  Previously, any auxiliary fallback
(e.g. when kimi-coding key was invalid) would silently discover and use
Claude Code OAuth tokens — consuming the user's Claude Max subscription
without their knowledge.

This is the auxiliary-client counterpart of the setup-wizard gate in
PR NousResearch#4210.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@teknium1

Copy link
Copy Markdown
Contributor

Merged via #7156. Your defense-in-depth credential gate was cherry-picked with authorship preserved (4 commits). The CLAUDE_CODE_OAUTH_TOKEN exclusion was a particularly nice touch. Thanks, @wanpengxie!

@teknium1 teknium1 closed this Apr 10, 2026
@CybercodeXx

Copy link
Copy Markdown

pero una pregunta en la terminal si funciona pero por medio de telegram manda el error de api como se soluciona via hermes gateway

CybercodeXx pushed a commit to CybercodeXx/hermes-agent that referenced this pull request Apr 17, 2026
Scenario: user configures kimi-coding with a malformed API key.
When the main agent fails and auxiliary fallback runs, the fix must
prevent _try_anthropic() from silently reading ~/.claude/.credentials.json.

9 tests covering all three vulnerable paths from PRs NousResearch#7009/NousResearch#7156:
  A. _seed_from_singletons() — credential pool gate
  B. _resolve_api_key_provider() — auxiliary client fallback chain
  C. suppress_credential_source() — re-seed prevention after auth remove

Also verifies CLAUDE_CODE_OAUTH_TOKEN (implicit) vs ANTHROPIC_API_KEY
(explicit) are distinguished correctly by is_provider_explicitly_configured().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants