()
| 122 | * fully established via applyConfigEnvironmentVariables(). |
| 123 | */ |
| 124 | export function applySafeConfigEnvironmentVariables(): void { |
| 125 | // Capture CCD spawn-env keys before any settings.env is applied (once). |
| 126 | if (ccdSpawnEnvKeys === undefined) { |
| 127 | ccdSpawnEnvKeys = |
| 128 | process.env.CLAUDE_CODE_ENTRYPOINT === 'claude-desktop' |
| 129 | ? new Set(Object.keys(process.env)) |
| 130 | : null |
| 131 | } |
| 132 | |
| 133 | // Global config (~/.claude.json) is user-controlled. In CCD mode, |
| 134 | // filterSettingsEnv strips keys that were in the spawn env snapshot so |
| 135 | // the desktop host's operational vars (OTEL, etc.) are not overridden. |
| 136 | Object.assign(process.env, filterSettingsEnv(getGlobalConfig().env)) |
| 137 | |
| 138 | // Apply ALL env vars from trusted setting sources, policySettings last. |
| 139 | // Gate on isSettingSourceEnabled so SDK settingSources: [] (isolation mode) |
| 140 | // doesn't get clobbered by ~/.claude/settings.json env (gh#217). policy/flag |
| 141 | // sources are always enabled, so this only ever filters userSettings. |
| 142 | for (const source of TRUSTED_SETTING_SOURCES) { |
| 143 | if (source === 'policySettings') continue |
| 144 | if (!isSettingSourceEnabled(source)) continue |
| 145 | Object.assign( |
| 146 | process.env, |
| 147 | filterSettingsEnv(getSettingsForSource(source)?.env), |
| 148 | ) |
| 149 | } |
| 150 | |
| 151 | // Compute remote-managed-settings eligibility now, with userSettings and |
| 152 | // flagSettings env applied. Eligibility reads CLAUDE_CODE_USE_BEDROCK, |
| 153 | // ANTHROPIC_BASE_URL — both settable via settings.env. |
| 154 | // getSettingsForSource('policySettings') below consults the remote cache, |
| 155 | // which guards on this. The two-phase structure makes the ordering |
| 156 | // dependency visible: non-policy env → eligibility → policy env. |
| 157 | isRemoteManagedSettingsEligible() |
| 158 | |
| 159 | Object.assign( |
| 160 | process.env, |
| 161 | filterSettingsEnv(getSettingsForSource('policySettings')?.env), |
| 162 | ) |
| 163 | |
| 164 | // Apply only safe env vars from the fully-merged settings (which includes |
| 165 | // project-scoped sources). For safe vars that also exist in trusted sources, |
| 166 | // the merged value (which may come from a higher-priority project source) |
| 167 | // will overwrite the trusted value — this is acceptable since these vars are |
| 168 | // in the safe allowlist. Only policySettings values are guaranteed to survive |
| 169 | // unchanged (it has the highest merge priority in both loops) — except |
| 170 | // provider-routing vars, which filterSettingsEnv strips from every source |
| 171 | // when CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST is set. |
| 172 | const settingsEnv = filterSettingsEnv(getSettings_DEPRECATED()?.env) |
| 173 | for (const [key, value] of Object.entries(settingsEnv)) { |
| 174 | if (SAFE_ENV_VARS.has(key.toUpperCase())) { |
| 175 | process.env[key] = value |
| 176 | } |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Apply environment variables from settings to process.env. |
no test coverage detected