fix(llm-auth): honor configured provider -- drop inherited CLAUDE_CODE_OAUTH_TOKEN on non-Anthropic workspaces (drain fix) #81
Reference in New Issue
Block a user
Delete Branch "fix/runtime-honors-provider-drop-inherited-oauth"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Root cause (2026-05-28 Anthropic drain)
The claude-code runtime auto-prefers
CLAUDE_CODE_OAUTH_TOKEN(llm-auth: detected oauth) and routes to Anthropic even when the workspace config explicitly setsprovider=minimax. The platform injects the tenant's shared global secrets into every workspace, so a non-Anthropic agent inherits the tenant's Claude OAuth token and silently bills Anthropic. Confirmed on DevB (Dev Engineer B / MiniMax,i-07400c3230cf45337):provider=minimax, model=MiniMax-M2.7,MINIMAX_API_KEY=unset,CLAUDE_CODE_OAUTH_TOKEN=SET-> silent Anthropic fallback = the drain.core#2000's strip removedANTHROPIC_*but leftCLAUDE_CODE_OAUTH_TOKEN, so it did not close the drain.Fix -- runtime honors provider (CTO decision 2026-05-28)
Provider is SSOT (internal#718).
normalise_llm_env(env, provider=...)now drops an inheritedCLAUDE_CODE_OAUTH_TOKENwhen the resolved provider is not an Anthropic-OAuth provider (anthropic/anthropic-oauth/claude/claude-code), before the OAuth short-circuit can hijack auth. Downstream auth then follows the configured provider; if that provider's own key is missing, preflight fails loudly -- no silent fallback, no drain.llm_auth.py: newproviderparam +_ANTHROPIC_OAUTH_PROVIDERSguard (case-insensitive). Empty/Noneprovider preserves legacy behaviour (backward compatible with all existing call sites).main.py: defers thenormalise_llm_envcall to just afterload_configsoconfig.provider(SSOT) is available -- still runs before preflight.tests/test_llm_auth.py: +7 tests (minimax drops oauth; anthropic/claude-code keep it; minimax keeps proxy token after dropping oauth; empty/None legacy; case-insensitive; openai drops).Test
pytest tests/test_llm_auth.py-> 24 passed. (The 7 collection errors in the full suite on the op-host are pre-existing missing-dep imports, unrelated to this change.)Not in scope (CTO-held follow-ups, filed separately)
openai-alias gap.Generated with Claude Code
Provider is SSOT (internal#718). The platform injects the tenant's shared global secrets into every workspace, so a non-Anthropic workspace (provider=minimax/openai/moonshot) inherits a stray CLAUDE_CODE_OAUTH_TOKEN belonging to the tenant's Claude agents. claude-code auto-prefers that OAuth token ("llm-auth: detected oauth") and silently bills Anthropic instead of the configured provider — the 2026-05-28 drain (DevB MiniMax). normalise_llm_env now takes the resolved provider and drops an inherited CLAUDE_CODE_OAUTH_TOKEN when the provider is not an Anthropic-OAuth provider, BEFORE the oauth short-circuit can hijack auth. If the provider's own key is absent, preflight fails loudly — no silent fallback, no drain. main.py defers the call to after load_config so config.provider (SSOT) is available; still runs before preflight. Empty/None provider preserves legacy behaviour. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Five-Axis review — APPROVED.
Correctness: guard fires only when provider is set AND not in the Anthropic-OAuth set; case-insensitive; drops the inherited oauth BEFORE the existing short-circuit so it cannot hijack a non-Anthropic workspace. Backward-compat: empty/None provider preserves legacy behaviour (all existing call sites unaffected). Tests: +7 targeted cases, 24/24 pass. Security: removes a foreign credential from the env — reduces blast radius, no secret logged. Failure mode: missing provider key now fails loud at preflight instead of silent Anthropic drain. Matches CTO 2026-05-28 decision (runtime honors provider).
APPROVED — runtime/credential axis. Confirmed normalise_llm_env runs at main 0.1b after load_config so config.provider is populated, and still before run_preflight; the guard mutates env in place ahead of adapter/executor construction, so the claude-code SDK never sees a foreign CLAUDE_CODE_OAUTH_TOKEN on a minimax/openai/moonshot workspace. No new imports, pure function, fail-safe on empty provider. Matches the live drain evidence on DevB (oauth present, minimax wiring absent).