From 5edf1557c4e21e7231602a19874df51494d9d172 Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Mon, 4 May 2026 20:59:11 -0700 Subject: [PATCH] fix: address Greptile P1 security + CodeRabbit follow-ups on PR #2302 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EnvManager: add ANTHROPIC_AUTH_TOKEN to BLOCKED_ENV_VARS so a token inherited from the parent shell can no longer short-circuit the OAuth lookup at SDK spawn time. Mirrors the ANTHROPIC_API_KEY treatment added in issue #733. Explicit gateway tokens in ~/.claude-mem/.env are still re-injected by buildIsolatedEnv(). - install.ts: extract resolveClaudeAuthMethod() that returns a stored CLAUDE_MEM_CLAUDE_AUTH_METHOD when present and otherwise infers the mode from ~/.claude-mem/.env (ANTHROPIC_BASE_URL → gateway, ANTHROPIC_API_KEY → api-key, else subscription). persistClaudeProvider, the interactive Claude auth flow, and promptClaudeModel now use it, so older installs that pre-date the setting are no longer misclassified as 'subscription' (which would clear working credentials and disable custom gateway models). - configureDirectApiKey: when an Anthropic API key already exists, prompt to keep or rotate it instead of silently re-saving — restores the ability to update a revoked or rotated key from the installer without losing the cancel-safe behaviour added in 7f3686fd. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/npx-cli/commands/install.ts | 64 +++++++++++++++++++++------------ src/shared/EnvManager.ts | 4 +++ 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/npx-cli/commands/install.ts b/src/npx-cli/commands/install.ts index bef4b4df..09ff0f19 100644 --- a/src/npx-cli/commands/install.ts +++ b/src/npx-cli/commands/install.ts @@ -588,16 +588,26 @@ type ProviderId = 'claude' | 'gemini' | 'openrouter'; type ClaudeAccessMode = 'subscription' | 'api-key'; type ClaudeApiMode = 'direct' | 'gateway'; +function resolveClaudeAuthMethod(): 'subscription' | 'api-key' | 'gateway' { + const stored = getSetting('CLAUDE_MEM_CLAUDE_AUTH_METHOD') as + | 'subscription' + | 'api-key' + | 'gateway' + | undefined; + if (stored === 'subscription' || stored === 'api-key' || stored === 'gateway') { + return stored; + } + const env = loadClaudeMemEnv(); + if (env.ANTHROPIC_BASE_URL?.trim()) return 'gateway'; + if (env.ANTHROPIC_API_KEY?.trim()) return 'api-key'; + return 'subscription'; +} + async function promptProvider(options: InstallOptions): Promise { const initialProvider = (getSetting('CLAUDE_MEM_PROVIDER') as ProviderId) || 'claude'; const persistClaudeProvider = (authMethod?: 'subscription' | 'api-key' | 'gateway') => { - const existingAuthMethod = getSetting('CLAUDE_MEM_CLAUDE_AUTH_METHOD') as - | 'subscription' - | 'api-key' - | 'gateway' - | undefined; - const resolvedAuthMethod = authMethod ?? existingAuthMethod ?? 'subscription'; + const resolvedAuthMethod = authMethod ?? resolveClaudeAuthMethod(); const wrote = mergeSettings({ CLAUDE_MEM_PROVIDER: 'claude', CLAUDE_MEM_CLAUDE_AUTH_METHOD: resolvedAuthMethod, @@ -618,13 +628,27 @@ async function promptProvider(options: InstallOptions): Promise { const configureDirectApiKey = async (): Promise => { const existing = loadClaudeMemEnv().ANTHROPIC_API_KEY || ''; if (existing.trim().length > 0) { - saveClaudeMemEnv({ - ANTHROPIC_API_KEY: existing.trim(), - ANTHROPIC_BASE_URL: '', - ANTHROPIC_AUTH_TOKEN: '', + const choice = await p.select<'keep' | 'replace'>({ + message: 'An Anthropic API key is already configured. Keep it or enter a new one?', + options: [ + { value: 'keep', label: 'Keep existing key' }, + { value: 'replace', label: 'Enter a new key (rotate)' }, + ], + initialValue: 'keep', }); - persistClaudeProvider('api-key'); - return; + if (p.isCancel(choice)) { + log.warn('API key prompt cancelled — leaving existing configuration untouched.'); + return; + } + if (choice === 'keep') { + saveClaudeMemEnv({ + ANTHROPIC_API_KEY: existing.trim(), + ANTHROPIC_BASE_URL: '', + ANTHROPIC_AUTH_TOKEN: '', + }); + persistClaudeProvider('api-key'); + return; + } } const apiKeyResult = await p.password({ @@ -708,14 +732,9 @@ async function promptProvider(options: InstallOptions): Promise { } const runClaudeAuthFlow = async (): Promise => { - const storedAuthMethod = getSetting('CLAUDE_MEM_CLAUDE_AUTH_METHOD') as - | 'subscription' - | 'api-key' - | 'gateway' - | undefined; - const initialAccessMode: ClaudeAccessMode = storedAuthMethod === 'subscription' || !storedAuthMethod - ? 'subscription' - : 'api-key'; + const resolvedAuthMethod = resolveClaudeAuthMethod(); + const initialAccessMode: ClaudeAccessMode = + resolvedAuthMethod === 'subscription' ? 'subscription' : 'api-key'; const result = await p.select({ message: 'Do you use a subscription plan or an API key/gateway for the memory agent?', @@ -741,7 +760,7 @@ async function promptProvider(options: InstallOptions): Promise { { value: 'direct', label: 'Anthropic API key' }, { value: 'gateway', label: 'LiteLLM or custom gateway' }, ], - initialValue: storedAuthMethod === 'gateway' || loadClaudeMemEnv().ANTHROPIC_BASE_URL ? 'gateway' : 'direct', + initialValue: resolvedAuthMethod === 'gateway' || loadClaudeMemEnv().ANTHROPIC_BASE_URL ? 'gateway' : 'direct', }); if (p.isCancel(apiModeResult)) { @@ -822,8 +841,7 @@ async function promptClaudeModel(options: InstallOptions): Promise { 'claude-sonnet-4-6', 'claude-opus-4-7', ]); - const authMethod = getSetting('CLAUDE_MEM_CLAUDE_AUTH_METHOD'); - const allowCustomModel = authMethod === 'gateway'; + const allowCustomModel = resolveClaudeAuthMethod() === 'gateway'; if (options.model && !allowCustomModel) { if (!allowed.has(options.model)) { diff --git a/src/shared/EnvManager.ts b/src/shared/EnvManager.ts index 18fad507..af442670 100644 --- a/src/shared/EnvManager.ts +++ b/src/shared/EnvManager.ts @@ -13,6 +13,10 @@ export const ENV_FILE_PATH = paths.envFile(); const BLOCKED_ENV_VARS = [ 'ANTHROPIC_API_KEY', // Issue #733: Prevent auto-discovery from project .env files + 'ANTHROPIC_AUTH_TOKEN', // Same leak risk as ANTHROPIC_API_KEY; a token inherited from the + // shell would otherwise short-circuit OAuth lookup at spawn time. + // The fresh token from ~/.claude-mem/.env is re-injected below + // when explicit gateway credentials are configured. 'CLAUDECODE', // Prevent "cannot be launched inside another Claude Code session" error 'CLAUDE_CODE_OAUTH_TOKEN', // Issue #2215: prevent stale parent-process token from leaking into // isolated env. The fresh token is read from the keychain at spawn