()
| 160 | * fully established via applyConfigEnvironmentVariables(). |
| 161 | */ |
| 162 | export function applySafeConfigEnvironmentVariables(): void { |
| 163 | // Capture CCD spawn-env keys before any settings.env is applied (once). |
| 164 | if (ccdSpawnEnvKeys === undefined) { |
| 165 | ccdSpawnEnvKeys = |
| 166 | process.env.CLAUDE_CODE_ENTRYPOINT === 'claude-desktop' |
| 167 | ? new Set(Object.keys(process.env)) |
| 168 | : null |
| 169 | } |
| 170 | |
| 171 | // Global config (~/.ncode.json) is user-controlled. In CCD mode, |
| 172 | // filterSettingsEnv strips keys that were in the spawn env snapshot so |
| 173 | // the desktop host's operational vars (OTEL, etc.) are not overridden. |
| 174 | Object.assign(process.env, filterSettingsEnv(getGlobalConfig().env)) |
| 175 | |
| 176 | // Apply ALL env vars from trusted setting sources, policySettings last. |
| 177 | // Gate on isSettingSourceEnabled so SDK settingSources: [] (isolation mode) |
| 178 | // doesn't get clobbered by ~/.claude/settings.json env (gh#217). policy/flag |
| 179 | // sources are always enabled, so this only ever filters userSettings. |
| 180 | for (const source of TRUSTED_SETTING_SOURCES) { |
| 181 | if (source === 'policySettings') continue |
| 182 | if (!isSettingSourceEnabled(source)) continue |
| 183 | Object.assign( |
| 184 | process.env, |
| 185 | filterSettingsEnv(getSettingsForSource(source)?.env, source), |
| 186 | ) |
| 187 | } |
| 188 | |
| 189 | // Compute remote-managed-settings eligibility now, with userSettings and |
| 190 | // flagSettings env applied. Eligibility reads CLAUDE_CODE_USE_BEDROCK, |
| 191 | // ANTHROPIC_BASE_URL — both settable via settings.env. |
| 192 | // getSettingsForSource('policySettings') below consults the remote cache, |
| 193 | // which guards on this. The two-phase structure makes the ordering |
| 194 | // dependency visible: non-policy env → eligibility → policy env. |
| 195 | isRemoteManagedSettingsEligible() |
| 196 | |
| 197 | Object.assign( |
| 198 | process.env, |
| 199 | filterSettingsEnv( |
| 200 | getSettingsForSource('policySettings')?.env, |
| 201 | 'policySettings', |
| 202 | ), |
| 203 | ) |
| 204 | |
| 205 | // Apply only safe env vars from the fully-merged settings (which includes |
| 206 | // project-scoped sources). For safe vars that also exist in trusted sources, |
| 207 | // the merged value (which may come from a higher-priority project source) |
| 208 | // will overwrite the trusted value — this is acceptable since these vars are |
| 209 | // in the safe allowlist. Only policySettings values are guaranteed to survive |
| 210 | // unchanged (it has the highest merge priority in both loops) — except |
| 211 | // provider-routing vars, which filterSettingsEnv strips from every source |
| 212 | // when CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST is set. |
| 213 | const settingsEnv = filterSettingsEnv(getSettings_DEPRECATED()?.env) |
| 214 | for (const [key, value] of Object.entries(settingsEnv)) { |
| 215 | if (SAFE_ENV_VARS.has(key.toUpperCase())) { |
| 216 | process.env[key] = value |
| 217 | } |
| 218 | } |
| 219 | } |
no test coverage detected