fix: address Greptile P1 security + CodeRabbit follow-ups on PR #2302
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -588,16 +588,26 @@ type ProviderId = 'claude' | 'gemini' | 'openrouter';
|
|||||||
type ClaudeAccessMode = 'subscription' | 'api-key';
|
type ClaudeAccessMode = 'subscription' | 'api-key';
|
||||||
type ClaudeApiMode = 'direct' | 'gateway';
|
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<ProviderId> {
|
async function promptProvider(options: InstallOptions): Promise<ProviderId> {
|
||||||
const initialProvider = (getSetting('CLAUDE_MEM_PROVIDER') as ProviderId) || 'claude';
|
const initialProvider = (getSetting('CLAUDE_MEM_PROVIDER') as ProviderId) || 'claude';
|
||||||
|
|
||||||
const persistClaudeProvider = (authMethod?: 'subscription' | 'api-key' | 'gateway') => {
|
const persistClaudeProvider = (authMethod?: 'subscription' | 'api-key' | 'gateway') => {
|
||||||
const existingAuthMethod = getSetting('CLAUDE_MEM_CLAUDE_AUTH_METHOD') as
|
const resolvedAuthMethod = authMethod ?? resolveClaudeAuthMethod();
|
||||||
| 'subscription'
|
|
||||||
| 'api-key'
|
|
||||||
| 'gateway'
|
|
||||||
| undefined;
|
|
||||||
const resolvedAuthMethod = authMethod ?? existingAuthMethod ?? 'subscription';
|
|
||||||
const wrote = mergeSettings({
|
const wrote = mergeSettings({
|
||||||
CLAUDE_MEM_PROVIDER: 'claude',
|
CLAUDE_MEM_PROVIDER: 'claude',
|
||||||
CLAUDE_MEM_CLAUDE_AUTH_METHOD: resolvedAuthMethod,
|
CLAUDE_MEM_CLAUDE_AUTH_METHOD: resolvedAuthMethod,
|
||||||
@@ -618,13 +628,27 @@ async function promptProvider(options: InstallOptions): Promise<ProviderId> {
|
|||||||
const configureDirectApiKey = async (): Promise<void> => {
|
const configureDirectApiKey = async (): Promise<void> => {
|
||||||
const existing = loadClaudeMemEnv().ANTHROPIC_API_KEY || '';
|
const existing = loadClaudeMemEnv().ANTHROPIC_API_KEY || '';
|
||||||
if (existing.trim().length > 0) {
|
if (existing.trim().length > 0) {
|
||||||
saveClaudeMemEnv({
|
const choice = await p.select<'keep' | 'replace'>({
|
||||||
ANTHROPIC_API_KEY: existing.trim(),
|
message: 'An Anthropic API key is already configured. Keep it or enter a new one?',
|
||||||
ANTHROPIC_BASE_URL: '',
|
options: [
|
||||||
ANTHROPIC_AUTH_TOKEN: '',
|
{ value: 'keep', label: 'Keep existing key' },
|
||||||
|
{ value: 'replace', label: 'Enter a new key (rotate)' },
|
||||||
|
],
|
||||||
|
initialValue: 'keep',
|
||||||
});
|
});
|
||||||
persistClaudeProvider('api-key');
|
if (p.isCancel(choice)) {
|
||||||
return;
|
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({
|
const apiKeyResult = await p.password({
|
||||||
@@ -708,14 +732,9 @@ async function promptProvider(options: InstallOptions): Promise<ProviderId> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runClaudeAuthFlow = async (): Promise<void> => {
|
const runClaudeAuthFlow = async (): Promise<void> => {
|
||||||
const storedAuthMethod = getSetting('CLAUDE_MEM_CLAUDE_AUTH_METHOD') as
|
const resolvedAuthMethod = resolveClaudeAuthMethod();
|
||||||
| 'subscription'
|
const initialAccessMode: ClaudeAccessMode =
|
||||||
| 'api-key'
|
resolvedAuthMethod === 'subscription' ? 'subscription' : 'api-key';
|
||||||
| 'gateway'
|
|
||||||
| undefined;
|
|
||||||
const initialAccessMode: ClaudeAccessMode = storedAuthMethod === 'subscription' || !storedAuthMethod
|
|
||||||
? 'subscription'
|
|
||||||
: 'api-key';
|
|
||||||
|
|
||||||
const result = await p.select<ClaudeAccessMode>({
|
const result = await p.select<ClaudeAccessMode>({
|
||||||
message: 'Do you use a subscription plan or an API key/gateway for the memory agent?',
|
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<ProviderId> {
|
|||||||
{ value: 'direct', label: 'Anthropic API key' },
|
{ value: 'direct', label: 'Anthropic API key' },
|
||||||
{ value: 'gateway', label: 'LiteLLM or custom gateway' },
|
{ 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)) {
|
if (p.isCancel(apiModeResult)) {
|
||||||
@@ -822,8 +841,7 @@ async function promptClaudeModel(options: InstallOptions): Promise<void> {
|
|||||||
'claude-sonnet-4-6',
|
'claude-sonnet-4-6',
|
||||||
'claude-opus-4-7',
|
'claude-opus-4-7',
|
||||||
]);
|
]);
|
||||||
const authMethod = getSetting('CLAUDE_MEM_CLAUDE_AUTH_METHOD');
|
const allowCustomModel = resolveClaudeAuthMethod() === 'gateway';
|
||||||
const allowCustomModel = authMethod === 'gateway';
|
|
||||||
|
|
||||||
if (options.model && !allowCustomModel) {
|
if (options.model && !allowCustomModel) {
|
||||||
if (!allowed.has(options.model)) {
|
if (!allowed.has(options.model)) {
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ export const ENV_FILE_PATH = paths.envFile();
|
|||||||
|
|
||||||
const BLOCKED_ENV_VARS = [
|
const BLOCKED_ENV_VARS = [
|
||||||
'ANTHROPIC_API_KEY', // Issue #733: Prevent auto-discovery from project .env files
|
'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
|
'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
|
'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
|
// isolated env. The fresh token is read from the keychain at spawn
|
||||||
|
|||||||
Reference in New Issue
Block a user