fix: use centralized credentials from ~/.claude-mem/.env to prevent API key hijacking (#733)

This fixes Issue #733 where claude-mem would incorrectly use ANTHROPIC_API_KEY from
random project .env files instead of the user's configured Claude Code CLI subscription.

Root cause: The SDK's `query()` function inherits from `process.env` when no `env`
option is passed. When users work in projects with their own .env files containing
API keys, the SDK would discover and use those keys, billing the wrong account.

Solution: Centralized credential management via ~/.claude-mem/.env

Changes:
- Add EnvManager.ts: Centralized credential storage and isolated env builder
- SDKAgent: Pass isolated env to SDK query() that only includes credentials from
  ~/.claude-mem/.env, not random keys from process.env inheritance
- GeminiAgent/OpenRouterAgent: Use getCredential() instead of process.env fallback
- SettingsDefaultsManager: Add CLAUDE_MEM_CLAUDE_AUTH_METHOD setting ('cli' | 'api')

How it works:
1. buildIsolatedEnv() creates a clean environment with only essential system vars
   (PATH, HOME, etc.) and credentials explicitly configured in ~/.claude-mem/.env
2. SDK subprocess runs with this isolated env, never seeing random API keys
3. If no ANTHROPIC_API_KEY is in ~/.claude-mem/.env, Claude Code CLI billing is used
4. Same pattern applied to Gemini/OpenRouter agents for consistency

This ensures claude-mem always uses the user's intended billing method, regardless
of what .env files exist in their working directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
bigphoot
2026-01-17 14:48:39 -08:00
committed by Alex Newman
parent aedee33ca9
commit 006ff40175
5 changed files with 301 additions and 8 deletions
+7 -3
View File
@@ -17,6 +17,7 @@ import { logger } from '../../utils/logger.js';
import { buildInitPrompt, buildObservationPrompt, buildSummaryPrompt, buildContinuationPrompt } from '../../sdk/prompts.js';
import { SettingsDefaultsManager } from '../../shared/SettingsDefaultsManager.js';
import { USER_SETTINGS_PATH } from '../../shared/paths.js';
import { getCredential } from '../../shared/EnvManager.js';
import type { ActiveSession, ConversationMessage } from '../worker-types.js';
import { ModeManager } from '../domain/ModeManager.js';
import {
@@ -409,13 +410,15 @@ export class OpenRouterAgent {
/**
* Get OpenRouter configuration from settings or environment
* Issue #733: Uses centralized ~/.claude-mem/.env for credentials, not random project .env files
*/
private getOpenRouterConfig(): { apiKey: string; model: string; siteUrl?: string; appName?: string } {
const settingsPath = USER_SETTINGS_PATH;
const settings = SettingsDefaultsManager.loadFromFile(settingsPath);
// API key: check settings first, then environment variable
const apiKey = settings.CLAUDE_MEM_OPENROUTER_API_KEY || process.env.OPENROUTER_API_KEY || '';
// API key: check settings first, then centralized claude-mem .env (NOT process.env)
// This prevents Issue #733 where random project .env files could interfere
const apiKey = settings.CLAUDE_MEM_OPENROUTER_API_KEY || getCredential('OPENROUTER_API_KEY') || '';
// Model: from settings or default
const model = settings.CLAUDE_MEM_OPENROUTER_MODEL || 'xiaomi/mimo-v2-flash:free';
@@ -430,11 +433,12 @@ export class OpenRouterAgent {
/**
* Check if OpenRouter is available (has API key configured)
* Issue #733: Uses centralized ~/.claude-mem/.env, not random project .env files
*/
export function isOpenRouterAvailable(): boolean {
const settingsPath = USER_SETTINGS_PATH;
const settings = SettingsDefaultsManager.loadFromFile(settingsPath);
return !!(settings.CLAUDE_MEM_OPENROUTER_API_KEY || process.env.OPENROUTER_API_KEY);
return !!(settings.CLAUDE_MEM_OPENROUTER_API_KEY || getCredential('OPENROUTER_API_KEY'));
}
/**