init
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
import { isRemoteManagedSettingsEligible } from '../services/remoteManagedSettings/syncCache.js'
|
||||
import { clearCACertsCache } from './caCerts.js'
|
||||
import { getGlobalConfig } from './config.js'
|
||||
import { isEnvTruthy } from './envUtils.js'
|
||||
import {
|
||||
isProviderManagedEnvVar,
|
||||
SAFE_ENV_VARS,
|
||||
} from './managedEnvConstants.js'
|
||||
import { clearMTLSCache } from './mtls.js'
|
||||
import { clearProxyCache, configureGlobalAgents } from './proxy.js'
|
||||
import { isSettingSourceEnabled } from './settings/constants.js'
|
||||
import {
|
||||
getSettings_DEPRECATED,
|
||||
getSettingsForSource,
|
||||
} from './settings/settings.js'
|
||||
|
||||
/**
|
||||
* `claude ssh` remote: ANTHROPIC_UNIX_SOCKET routes auth through a -R forwarded
|
||||
* socket to a local proxy, and the launcher sets a handful of placeholder auth
|
||||
* env vars that the remote's ~/.claude settings.env MUST NOT clobber (see
|
||||
* isAnthropicAuthEnabled). Strip them from any settings-sourced env object.
|
||||
*/
|
||||
function withoutSSHTunnelVars(
|
||||
env: Record<string, string> | undefined,
|
||||
): Record<string, string> {
|
||||
if (!env || !process.env.ANTHROPIC_UNIX_SOCKET) return env || {}
|
||||
const {
|
||||
ANTHROPIC_UNIX_SOCKET: _1,
|
||||
ANTHROPIC_BASE_URL: _2,
|
||||
ANTHROPIC_API_KEY: _3,
|
||||
ANTHROPIC_AUTH_TOKEN: _4,
|
||||
CLAUDE_CODE_OAUTH_TOKEN: _5,
|
||||
...rest
|
||||
} = env
|
||||
return rest
|
||||
}
|
||||
|
||||
/**
|
||||
* When the host owns inference routing (sets
|
||||
* CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST in spawn env), strip
|
||||
* provider-selection / model-default vars from settings-sourced env so a
|
||||
* user's ~/.claude/settings.json can't redirect requests away from the
|
||||
* host-configured provider.
|
||||
*/
|
||||
function withoutHostManagedProviderVars(
|
||||
env: Record<string, string> | undefined,
|
||||
): Record<string, string> {
|
||||
if (!env) return {}
|
||||
if (!isEnvTruthy(process.env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST)) {
|
||||
return env
|
||||
}
|
||||
const out: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
if (!isProviderManagedEnvVar(key)) {
|
||||
out[key] = value
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot of env keys present before any settings.env is applied — for CCD,
|
||||
* these are the keys the desktop host set to orchestrate the subprocess.
|
||||
* Settings must not override them (OTEL_LOGS_EXPORTER=console would corrupt
|
||||
* the stdio JSON-RPC transport). Keys added LATER by user/project settings
|
||||
* are not in this set, so mid-session settings.json changes still apply.
|
||||
* Lazy-captured on first applySafeConfigEnvironmentVariables() call.
|
||||
*/
|
||||
let ccdSpawnEnvKeys: Set<string> | null | undefined
|
||||
|
||||
function withoutCcdSpawnEnvKeys(
|
||||
env: Record<string, string> | undefined,
|
||||
): Record<string, string> {
|
||||
if (!env || !ccdSpawnEnvKeys) return env || {}
|
||||
const out: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
if (!ccdSpawnEnvKeys.has(key)) out[key] = value
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the strip filters applied to every settings-sourced env object.
|
||||
*/
|
||||
function filterSettingsEnv(
|
||||
env: Record<string, string> | undefined,
|
||||
): Record<string, string> {
|
||||
return withoutCcdSpawnEnvKeys(
|
||||
withoutHostManagedProviderVars(withoutSSHTunnelVars(env)),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Trusted setting sources whose env vars can be applied before the trust dialog.
|
||||
*
|
||||
* - userSettings (~/.claude/settings.json): controlled by the user, not project-specific
|
||||
* - flagSettings (--settings CLI flag or SDK inline settings): explicitly passed by the user
|
||||
* - policySettings (managed settings from enterprise API or local managed-settings.json):
|
||||
* controlled by IT/admin (highest priority, cannot be overridden)
|
||||
*
|
||||
* Project-scoped sources (projectSettings, localSettings) are excluded because they live
|
||||
* inside the project directory and could be committed by a malicious actor to redirect
|
||||
* traffic (e.g., ANTHROPIC_BASE_URL) to an attacker-controlled server.
|
||||
*/
|
||||
const TRUSTED_SETTING_SOURCES = [
|
||||
'userSettings',
|
||||
'flagSettings',
|
||||
'policySettings',
|
||||
] as const
|
||||
|
||||
/**
|
||||
* Apply environment variables from trusted sources to process.env.
|
||||
* Called before the trust dialog so that user/enterprise env vars like
|
||||
* ANTHROPIC_BASE_URL take effect during first-run/onboarding.
|
||||
*
|
||||
* For trusted sources (user settings, managed settings, CLI flags), ALL env vars
|
||||
* are applied — including ones like ANTHROPIC_BASE_URL that would be dangerous
|
||||
* from project-scoped settings.
|
||||
*
|
||||
* For project-scoped sources (projectSettings, localSettings), only safe env vars
|
||||
* from the SAFE_ENV_VARS allowlist are applied. These are applied after trust is
|
||||
* fully established via applyConfigEnvironmentVariables().
|
||||
*/
|
||||
export function applySafeConfigEnvironmentVariables(): void {
|
||||
// Capture CCD spawn-env keys before any settings.env is applied (once).
|
||||
if (ccdSpawnEnvKeys === undefined) {
|
||||
ccdSpawnEnvKeys =
|
||||
process.env.CLAUDE_CODE_ENTRYPOINT === 'claude-desktop'
|
||||
? new Set(Object.keys(process.env))
|
||||
: null
|
||||
}
|
||||
|
||||
// Global config (~/.claude.json) is user-controlled. In CCD mode,
|
||||
// filterSettingsEnv strips keys that were in the spawn env snapshot so
|
||||
// the desktop host's operational vars (OTEL, etc.) are not overridden.
|
||||
Object.assign(process.env, filterSettingsEnv(getGlobalConfig().env))
|
||||
|
||||
// Apply ALL env vars from trusted setting sources, policySettings last.
|
||||
// Gate on isSettingSourceEnabled so SDK settingSources: [] (isolation mode)
|
||||
// doesn't get clobbered by ~/.claude/settings.json env (gh#217). policy/flag
|
||||
// sources are always enabled, so this only ever filters userSettings.
|
||||
for (const source of TRUSTED_SETTING_SOURCES) {
|
||||
if (source === 'policySettings') continue
|
||||
if (!isSettingSourceEnabled(source)) continue
|
||||
Object.assign(
|
||||
process.env,
|
||||
filterSettingsEnv(getSettingsForSource(source)?.env),
|
||||
)
|
||||
}
|
||||
|
||||
// Compute remote-managed-settings eligibility now, with userSettings and
|
||||
// flagSettings env applied. Eligibility reads CLAUDE_CODE_USE_BEDROCK,
|
||||
// ANTHROPIC_BASE_URL — both settable via settings.env.
|
||||
// getSettingsForSource('policySettings') below consults the remote cache,
|
||||
// which guards on this. The two-phase structure makes the ordering
|
||||
// dependency visible: non-policy env → eligibility → policy env.
|
||||
isRemoteManagedSettingsEligible()
|
||||
|
||||
Object.assign(
|
||||
process.env,
|
||||
filterSettingsEnv(getSettingsForSource('policySettings')?.env),
|
||||
)
|
||||
|
||||
// Apply only safe env vars from the fully-merged settings (which includes
|
||||
// project-scoped sources). For safe vars that also exist in trusted sources,
|
||||
// the merged value (which may come from a higher-priority project source)
|
||||
// will overwrite the trusted value — this is acceptable since these vars are
|
||||
// in the safe allowlist. Only policySettings values are guaranteed to survive
|
||||
// unchanged (it has the highest merge priority in both loops) — except
|
||||
// provider-routing vars, which filterSettingsEnv strips from every source
|
||||
// when CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST is set.
|
||||
const settingsEnv = filterSettingsEnv(getSettings_DEPRECATED()?.env)
|
||||
for (const [key, value] of Object.entries(settingsEnv)) {
|
||||
if (SAFE_ENV_VARS.has(key.toUpperCase())) {
|
||||
process.env[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply environment variables from settings to process.env.
|
||||
* This applies ALL environment variables (except provider-routing vars when
|
||||
* CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST is set — see filterSettingsEnv) and
|
||||
* should only be called after trust is established. This applies potentially
|
||||
* dangerous environment variables such as LD_PRELOAD, PATH, etc.
|
||||
*/
|
||||
export function applyConfigEnvironmentVariables(): void {
|
||||
Object.assign(process.env, filterSettingsEnv(getGlobalConfig().env))
|
||||
|
||||
Object.assign(process.env, filterSettingsEnv(getSettings_DEPRECATED()?.env))
|
||||
|
||||
// Clear caches so agents are rebuilt with the new env vars
|
||||
clearCACertsCache()
|
||||
clearMTLSCache()
|
||||
clearProxyCache()
|
||||
|
||||
// Reconfigure proxy/mTLS agents to pick up any proxy env vars from settings
|
||||
configureGlobalAgents()
|
||||
}
|
||||
Reference in New Issue
Block a user