| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- 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()
- }
|