diff --git a/agent/credential_pool.py b/agent/credential_pool.py index f6c6375788..0ce187503c 100644 --- a/agent/credential_pool.py +++ b/agent/credential_pool.py @@ -1059,6 +1059,17 @@ def _seed_from_singletons(provider: str, entries: List[PooledCredential]) -> Tup auth_store = _load_auth_store() if provider == "anthropic": + # Only auto-discover external credentials (Claude Code, Hermes PKCE) + # when the user has explicitly configured anthropic as their provider. + # Without this gate, auxiliary client fallback chains silently read + # ~/.claude/.credentials.json without user consent. See PR #4210. + try: + from hermes_cli.auth import is_provider_explicitly_configured + if not is_provider_explicitly_configured("anthropic"): + return changed, active_sources + except ImportError: + pass + from agent.anthropic_adapter import read_claude_code_credentials, read_hermes_oauth_credentials for source_name, creds in ( diff --git a/tests/agent/test_credential_pool.py b/tests/agent/test_credential_pool.py index 797597dd70..de6ffba5c5 100644 --- a/tests/agent/test_credential_pool.py +++ b/tests/agent/test_credential_pool.py @@ -567,6 +567,7 @@ def test_singleton_seed_does_not_clobber_manual_oauth_entry(tmp_path, monkeypatc monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False) monkeypatch.delenv("ANTHROPIC_TOKEN", raising=False) monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False) + monkeypatch.setattr("hermes_cli.auth.is_provider_explicitly_configured", lambda pid: True) _write_auth_store( tmp_path, { @@ -1043,3 +1044,30 @@ def test_release_lease_decrements_counter(tmp_path, monkeypatch): pool.release_lease("cred-1") assert pool._active_leases.get("cred-1", 0) == 0 + + +def test_load_pool_does_not_seed_claude_code_when_anthropic_not_configured(tmp_path, monkeypatch): + """Claude Code credentials must not be auto-seeded when the user never selected anthropic.""" + monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes")) + _write_auth_store(tmp_path, {"version": 1, "credential_pool": {}}) + + # Claude Code credentials exist on disk + monkeypatch.setattr( + "agent.anthropic_adapter.read_claude_code_credentials", + lambda: {"accessToken": "sk-ant...oken", "refreshToken": "rt", "expiresAt": 9999999999999}, + ) + monkeypatch.setattr( + "agent.anthropic_adapter.read_hermes_oauth_credentials", + lambda: None, + ) + # User configured kimi-coding, NOT anthropic + monkeypatch.setattr( + "hermes_cli.auth.is_provider_explicitly_configured", + lambda pid: pid == "kimi-coding", + ) + + from agent.credential_pool import load_pool + pool = load_pool("anthropic") + + # Should NOT have seeded the claude_code entry + assert pool.entries() == []