From 24e87f305140d4d9d51efe44253b3fc062f43296 Mon Sep 17 00:00:00 2001 From: luvs01 Date: Sat, 8 Aug 2026 19:27:33 +0900 Subject: [PATCH] fix(claude): contain proxy admission token --- src/cli/claude.ts | 12 ++++++++++-- tests/claude-cli.test.ts | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/cli/claude.ts b/src/cli/claude.ts index 4284ea6ce7..759812cce8 100644 --- a/src/cli/claude.ts +++ b/src/cli/claude.ts @@ -92,6 +92,7 @@ export function buildClaudeEnv( }; setDefault("ANTHROPIC_BASE_URL", `http://127.0.0.1:${port}`); const existingBaseUrl = env.ANTHROPIC_BASE_URL; + let targetsLocalProxy = false; if (existingBaseUrl) { try { const parsed = new URL(existingBaseUrl); @@ -101,6 +102,10 @@ export function buildClaudeEnv( console.error(`⚠ Replacing stale opencodex ANTHROPIC_BASE_URL ${existingBaseUrl} with ${replacement}.`); env.ANTHROPIC_BASE_URL = replacement; } + const effective = new URL(env.ANTHROPIC_BASE_URL!); + targetsLocalProxy = effective.protocol === "http:" + && (effective.hostname === "localhost" || effective.hostname === "127.0.0.1") + && Number(effective.port) === port; } catch { // Preserve user-provided values that are not parseable URLs. } @@ -110,7 +115,10 @@ export function buildClaudeEnv( // the user's Claude login. Only inject a token when the proxy actually requires an // admission key; otherwise Claude Code keeps its own OAuth and sends it to us — // native claude models then pass through verbatim (see server/claude-messages.ts). - if ((config.apiKeys?.length ?? 0) > 0) { + // Never pair a proxy admission secret with a user-selected destination. Also let an + // explicitly exported Anthropic API key remain the sole credential so Claude Code + // does not report conflicting authentication variables. + if (targetsLocalProxy && !env.ANTHROPIC_API_KEY && (config.apiKeys?.length ?? 0) > 0) { setDefault("ANTHROPIC_AUTH_TOKEN", config.apiKeys![0].key); } // Detection reads the SANITIZED launch env — the exact object spawned below — so the @@ -128,7 +136,7 @@ export function buildClaudeEnv( env: () => env as NodeJS.ProcessEnv, ownTokens: ownAdmissionTokens(config), })); - if (!env.ANTHROPIC_AUTH_TOKEN && resolved.markerMode === "proxy") { + if (targetsLocalProxy && !env.ANTHROPIC_AUTH_TOKEN && !env.ANTHROPIC_API_KEY && resolved.markerMode === "proxy") { env.ANTHROPIC_AUTH_TOKEN = PROXY_MARKER; } if (resolved.origin === "auto-unknown") { diff --git a/tests/claude-cli.test.ts b/tests/claude-cli.test.ts index b46911505b..3cf74d5950 100644 --- a/tests/claude-cli.test.ts +++ b/tests/claude-cli.test.ts @@ -52,6 +52,26 @@ describe("ocx claude env assembly", () => { expect(env.ANTHROPIC_AUTH_TOKEN).toBe("sk-ocx-123"); }); + test("does not send the proxy admission key to a user-selected gateway", () => { + const env = buildClaudeEnv(cfg({ + apiKeys: [{ id: "1", name: "main", key: "sk-ocx-123", createdAt: "2026-01-01" }], + }), 10100, { + ANTHROPIC_BASE_URL: "https://gateway.example.test", + }, {}, { preBunAnthropicSlots: ["ANTHROPIC_BASE_URL"] }); + expect(env.ANTHROPIC_BASE_URL).toBe("https://gateway.example.test"); + expect(env.ANTHROPIC_AUTH_TOKEN).toBeUndefined(); + }); + + test("does not add an auth token when the user exported an Anthropic API key", () => { + const env = buildClaudeEnv(cfg({ + apiKeys: [{ id: "1", name: "main", key: "sk-ocx-123", createdAt: "2026-01-01" }], + }), 10100, { + ANTHROPIC_API_KEY: "sk-ant-user", + }, {}, { preBunAnthropicSlots: ["ANTHROPIC_API_KEY"] }); + expect(env.ANTHROPIC_API_KEY).toBe("sk-ant-user"); + expect(env.ANTHROPIC_AUTH_TOKEN).toBeUndefined(); + }); + // Host-managed routing guard (devlog 260720_claude_authmode_persist/020): // defends the spawn env against leftover cc-switch/CCR settings.json env hijack. test("subscription mode leaves the host-managed auth assertion unset", () => {