diff --git a/packages/api/src/domains/cats/services/agents/providers/ClaudeAgentService.ts b/packages/api/src/domains/cats/services/agents/providers/ClaudeAgentService.ts index 5b9b3452b5..05957a7878 100644 --- a/packages/api/src/domains/cats/services/agents/providers/ClaudeAgentService.ts +++ b/packages/api/src/domains/cats/services/agents/providers/ClaudeAgentService.ts @@ -216,6 +216,12 @@ export function buildClaudeEnvOverrides(callbackEnv?: Record): R // Subscription mode must not inherit shell-level Anthropic credentials. // Claude CLI should read auth from ~/.claude/settings.json instead. env.ANTHROPIC_API_KEY = null; + // ANTHROPIC_AUTH_TOKEN is sent as `Authorization: Bearer` and overrides the + // CLI's native OAuth/Keychain login. If the daemon was launched with a + // proxy token (e.g. a DashScope key in ANTHROPIC_AUTH_TOKEN), leaking it + // here makes the subscription child send that token to api.anthropic.com → + // 401 Invalid Bearer Token. Strip it so native auth is used. + env.ANTHROPIC_AUTH_TOKEN = null; env.ANTHROPIC_BASE_URL = null; env.ANTHROPIC_MODEL = null; env.ANTHROPIC_DEFAULT_OPUS_MODEL = null; diff --git a/packages/api/test/claude-agent-service.test.js b/packages/api/test/claude-agent-service.test.js index bce9f9d8ae..0b1531bb5d 100644 --- a/packages/api/test/claude-agent-service.test.js +++ b/packages/api/test/claude-agent-service.test.js @@ -573,8 +573,15 @@ test('preserves inherited Anthropic credentials when no profile mode override is test('F062: subscription profile clears inherited ANTHROPIC env vars', async () => { const prevApiKey = process.env.ANTHROPIC_API_KEY; const prevBaseUrl = process.env.ANTHROPIC_BASE_URL; + const prevAuthToken = process.env.ANTHROPIC_AUTH_TOKEN; process.env.ANTHROPIC_API_KEY = 'sk-inherited'; process.env.ANTHROPIC_BASE_URL = 'https://inherited.example.com'; + // Regression: a daemon launched with a proxy token in ANTHROPIC_AUTH_TOKEN + // (e.g. a DashScope key + DashScope base URL) must NOT leak that token into + // a subscription-mode child. ANTHROPIC_AUTH_TOKEN is sent as `Authorization: + // Bearer`; leaking it makes the child send the proxy token to + // api.anthropic.com → 401 Invalid Bearer Token, overriding native OAuth. + process.env.ANTHROPIC_AUTH_TOKEN = 'sk-proxy-token'; const proc = createMockProcess(); const spawnFn = createMockSpawnFn(proc); @@ -597,11 +604,14 @@ test('F062: subscription profile clears inherited ANTHROPIC env vars', async () const spawnOpts = spawnFn.mock.calls[0].arguments[2]; assert.equal(spawnOpts.env.ANTHROPIC_API_KEY, undefined); assert.equal(spawnOpts.env.ANTHROPIC_BASE_URL, undefined); + assert.equal(spawnOpts.env.ANTHROPIC_AUTH_TOKEN, undefined); } finally { if (prevApiKey === undefined) delete process.env.ANTHROPIC_API_KEY; else process.env.ANTHROPIC_API_KEY = prevApiKey; if (prevBaseUrl === undefined) delete process.env.ANTHROPIC_BASE_URL; else process.env.ANTHROPIC_BASE_URL = prevBaseUrl; + if (prevAuthToken === undefined) delete process.env.ANTHROPIC_AUTH_TOKEN; + else process.env.ANTHROPIC_AUTH_TOKEN = prevAuthToken; } });