mod configured; mod destination; mod environment; mod matching; mod provider_config; mod providers; mod registry; mod replacement; use crate::config::NetworkProxyConfig; use crate::policy::normalize_host; use environment::update_brokered_credentials_marker; use rama_http::HeaderMap; use registry::ActiveCredentialSource; use registry::BrokeredCredentialProvider; use registry::active_credential_sources; use registry::is_builtin_shaped_credential; use registry::prioritized_credentials; use registry::registered_credential_source; use registry::select_credentials; use replacement::Replacements; use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::sync::Arc; use std::sync::RwLock; use url::Url; pub use environment::CredentialBrokerContext; pub use environment::CredentialBrokerEnvironment; pub use environment::brokered_credential_binding_env_keys; pub use environment::brokered_credential_dummy_env_keys; pub use environment::brokered_credential_env_keys; pub use environment::brokered_credential_marker_env_keys; pub use environment::brokered_credential_value_env_keys; pub use environment::credential_broker_provider_context_env_keys; pub use environment::credential_broker_provider_sources_allowed; pub use environment::is_credential_broker_provider_env_key; pub(crate) use environment::marked_credential_dummy_env_keys; pub use provider_config::CredentialAuthMethod; pub use provider_config::CredentialProviderConfig; pub const CREDENTIAL_BROKER_ACTIVE_ENV_KEY: &str = "CODEX_NETWORK_PROXY_CREDENTIAL_BROKER_ACTIVE"; pub(crate) const BROKERED_CREDENTIALS_ENV_KEY: &str = "CODEX_NETWORK_PROXY_BROKERED_CREDENTIALS"; const MIN_EMBEDDED_CREDENTIAL_LENGTH: usize = 16; const BROKERED_CREDENTIAL_ALIAS_MARKER_PREFIX: &str = "@alias:"; /// How to handle recognized credentials left raw in a prepared command environment. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CredentialMaskingFailureMode { /// Warn and keep the unmasked values. Warn, /// Remove variables containing unmasked values, including copied aliases. Deny, /// Reject the command environment. Error, } #[derive(Clone)] pub(crate) struct CredentialBroker { state: Arc>, } #[derive(Default)] struct CredentialBrokerState { config_revision: u64, enabled: bool, allow_local_binding: bool, openai_api_host: Option, context: CredentialBrokerContext, configured_provider_configs: BTreeMap, configured_providers: Vec>, credentials: Vec, credential_owners: Vec, credential_aliases: Vec, } struct CredentialOwner { env_var: String, real_value: String, } struct CredentialRecord { env_var: String, provider: BrokeredCredentialProvider, host_binding: providers::CredentialHostBinding, additional_host_bindings: Vec, fallback_host_bindings: Vec, environment_id: Option, real_value: String, dummy_value: String, // Canonical identities accompanying discovery, distinct from an alias's own credential. source_values: Vec, generated_aliases: Arc>>, } impl CredentialRecord { fn has_carried_identity( &self, env: &HashMap, credentials: &[CredentialRecord], sources: &[ActiveCredentialSource], ) -> bool { let mut source_present = false; let source_matches = sources .iter() .filter(|source| source_accepts_credential(source, self)) .flat_map(|source| &source.env_vars) .filter_map(|key| env_value(env, key)) .any(|value| { source_present = true; let value = match &self.provider { BrokeredCredentialProvider::Builtin(_) => value.trim(), BrokeredCredentialProvider::Configured(_) => value, }; let value = credentials .iter() .find(|source| source.dummy_value == value) .map_or(value, |source| source.real_value.as_str()); value == self.real_value || value == self.dummy_value || self.source_values.iter().any(|source| source == value) }); let source_changed = source_present && !source_matches; env_contains_credential_value(env, &self.dummy_value) || (source_changed || self.invalidates_host_binding(env)) && env.iter().any(|(key, value)| { !env_key_matches(key, CREDENTIAL_BROKER_ACTIVE_ENV_KEY) && !env_key_matches(key, BROKERED_CREDENTIALS_ENV_KEY) && !crate::is_managed_proxy_env_var(key, value) && (value == &self.real_value || self.contains_embedded_value(value, &self.real_value)) }) } fn invalidates_host_binding(&self, env: &HashMap) -> bool { match &self.provider { BrokeredCredentialProvider::Builtin(provider) => { provider.sources().iter().any(|source| { source .env_vars .iter() .any(|key| env_key_matches(key, &self.env_var)) && (source.invalidates_host_binding)(env) }) } BrokeredCredentialProvider::Configured(provider) => { provider .config .url_prefix_from_env .as_deref() .is_some_and(|key| env_value(env, key).is_some()) && provider.dynamic_destination(env).is_none() } } } fn observe_host_binding( &mut self, host_binding: providers::CredentialHostBinding, env: &HashMap, ) { if self.invalidates_host_binding(env) { self.additional_host_bindings.clear(); self.fallback_host_bindings.clear(); self.host_binding = host_binding; return; } if self.host_binding == host_binding { return; } // Track the latest source separately, but keep prior destinations usable by // concurrent commands sharing this dummy in the same environment. self.additional_host_bindings .retain(|binding| binding != &host_binding); self.additional_host_bindings .push(std::mem::replace(&mut self.host_binding, host_binding)); } fn host_bindings(&self) -> impl Iterator { std::iter::once(&self.host_binding).chain(&self.additional_host_bindings) } fn belongs_to_environment(&self, environment_id: Option<&str>) -> bool { self.environment_id.as_deref() == environment_id } } fn source_accepts_credential( source: &ActiveCredentialSource, credential: &CredentialRecord, ) -> bool { credential.provider.same_provider(&source.provider) && source .env_vars .iter() .any(|env_var| env_key_matches(env_var, &credential.env_var)) } fn source_tracks_credential( source: &ActiveCredentialSource, credential: &CredentialRecord, env: &HashMap, ) -> bool { if !source_accepts_credential(source, credential) { return false; } // Aliases may hold a different credential from the canonical source. if source.host_binding == credential.host_binding { return true; } let mut source_present = false; let contains_credential = source .env_vars .iter() .filter_map(|env_var| env_value(env, env_var)) .any(|value| { source_present = true; let value = match &source.provider { BrokeredCredentialProvider::Builtin(_) => value.trim(), BrokeredCredentialProvider::Configured(_) => value, }; value == credential.dummy_value || value == credential.real_value || credential .source_values .iter() .any(|source| source == value) }); !source_present || contains_credential } struct CredentialAlias { env_var: String, dummy_value: String, } enum CredentialEnvironment { Child, Snapshot, } fn env_key_matches(candidate: &str, expected: &str) -> bool { if cfg!(windows) { candidate.eq_ignore_ascii_case(expected) } else { candidate == expected } } fn env_entry<'a>(env: &'a HashMap, key: &str) -> Option<(&'a str, &'a str)> { env.iter() .find(|(candidate, _)| env_key_matches(candidate, key)) .map(|(key, value)| (key.as_str(), value.as_str())) } pub(super) fn env_value<'a>(env: &'a HashMap, key: &str) -> Option<&'a str> { env_entry(env, key).map(|(_, value)| value) } fn set_env_value(env: &mut HashMap, key: &str, value: String) { if cfg!(windows) { env.retain(|candidate, _| !env_key_matches(candidate, key)); } env.insert(key.to_string(), value); } fn remove_env_value(env: &mut HashMap, key: &str) { if cfg!(windows) { env.retain(|candidate, _| !env_key_matches(candidate, key)); } else { env.remove(key); } } fn env_contains_credential_value(env: &HashMap, credential: &str) -> bool { env.iter().any(|(key, value)| { !env_key_matches(key, CREDENTIAL_BROKER_ACTIVE_ENV_KEY) && !env_key_matches(key, BROKERED_CREDENTIALS_ENV_KEY) && value.contains(credential) && !crate::is_managed_proxy_env_var(key, value) }) } impl CredentialBroker { pub(crate) fn new(enabled: bool) -> Self { Self { state: Arc::new(RwLock::new(CredentialBrokerState { enabled, ..CredentialBrokerState::default() })), } } pub(crate) fn configure(&self, config: &NetworkProxyConfig) { let mut state = self.write_state(); let previous_context_sources = (state.context != config.credential_broker_context) .then(|| active_credential_sources(&state, &HashMap::new())); if state.enabled != config.credential_broker || state.openai_api_host != config.credential_broker_openai_host || state.allow_local_binding != config.allow_local_binding() || state.configured_provider_configs != config.credential_providers || previous_context_sources.is_some() { state.config_revision += 1; } state.context.clone_from(&config.credential_broker_context); state.allow_local_binding = config.allow_local_binding(); if state.enabled != config.credential_broker { state.enabled = config.credential_broker; state.credentials.clear(); state.credential_owners.clear(); state.credential_aliases.clear(); } if state.openai_api_host != config.credential_broker_openai_host { state .openai_api_host .clone_from(&config.credential_broker_openai_host); state.credentials.retain(|credential| { !matches!( &credential.provider, BrokeredCredentialProvider::Builtin(provider) if provider.reset_on_configuration_change ) }); } if state.configured_provider_configs != config.credential_providers { let mut configured_providers: Vec> = Vec::new(); let mut provider_configs = config.credential_providers.iter().collect::>(); provider_configs.sort_by_key(|(id, provider_config)| { std::cmp::Reverse( state.configured_provider_configs.get(id.as_str()) == Some(*provider_config) && state .configured_providers .iter() .any(|provider| provider.id == id.as_str()), ) }); for (id, provider_config) in provider_configs { if configured_providers.iter().any(|existing| { existing.config.env.iter().any(|existing_key| { provider_config .env .iter() .any(|key| env_key_matches(existing_key, key)) }) }) { tracing::warn!( provider = %id, "ignoring credential provider with an overlapping environment source" ); continue; } let existing = (state.configured_provider_configs.get(id) == Some(provider_config)) .then(|| { state .configured_providers .iter() .find(|provider| provider.id == *id) .cloned() }) .flatten(); match existing { Some(provider) => configured_providers.push(provider), None => { match configured::ConfiguredCredentialProvider::compile(id, provider_config) { Ok(provider) => configured_providers.push(Arc::new(provider)), Err(error) => { tracing::warn!(provider = %id, %error, "ignoring invalid credential provider"); } } } } } state .credentials .retain(|credential| match &credential.provider { BrokeredCredentialProvider::Builtin(_) => true, BrokeredCredentialProvider::Configured(provider) => configured_providers .iter() .any(|current| Arc::ptr_eq(current, provider)), }); state.configured_providers = configured_providers; state .configured_provider_configs .clone_from(&config.credential_providers); } if let Some(previous_sources) = previous_context_sources { // Update registrations that used the old fallback, leaving distinct captured bindings intact. let context = state.context.with_fallbacks(&HashMap::new()).into_owned(); let sources = active_credential_sources(&state, &context); state.credentials.retain_mut(|credential| { let Some(previous_source) = previous_sources.iter().find(|source| { source_accepts_credential(source, credential) && credential .host_bindings() .any(|binding| binding == &source.host_binding) }) else { return true; }; let source = sources .iter() .find(|source| source_accepts_credential(source, credential)); if source.is_some_and(|source| source.host_binding == previous_source.host_binding) { return true; } if !credential .fallback_host_bindings .contains(&previous_source.host_binding) { credential .fallback_host_bindings .push(previous_source.host_binding.clone()); } if source.is_none() || credential.invalidates_host_binding(&context) { // Clearing a fallback revokes its history, not independently captured hosts. credential .additional_host_bindings .retain(|binding| !credential.fallback_host_bindings.contains(binding)); if credential .fallback_host_bindings .contains(&credential.host_binding) { let replacement = source .map(|source| source.host_binding.clone()) .or_else(|| credential.additional_host_bindings.pop()); let Some(replacement) = replacement else { return false; }; credential.host_binding = replacement; } credential.fallback_host_bindings.clear(); } if credential.host_binding == previous_source.host_binding { let Some(source) = source else { return false; }; credential.observe_host_binding(source.host_binding.clone(), &context); } else { // A captured primary destination must stay primary when its older fallback changes. if let Some(source) = source && !credential .host_bindings() .any(|binding| binding == &source.host_binding) { credential .additional_host_bindings .push(source.host_binding.clone()); } } if let Some(source) = source && !credential .fallback_host_bindings .contains(&source.host_binding) { credential .fallback_host_bindings .push(source.host_binding.clone()); } true }); } } pub(crate) fn config_revision(&self) -> u64 { self.read_state().config_revision } #[cfg(test)] pub(crate) fn discover_parent_credentials( &self, parent_env: &HashMap, child_env: &HashMap, ) { self.discover_parent_credentials_for_environment( parent_env, child_env, /*environment_id*/ None, ); } pub(crate) fn discover_parent_credentials_for_environment( &self, parent_env: &HashMap, child_env: &HashMap, environment_id: Option<&str>, ) { let mut state = self.write_state(); if !state.enabled { return; } state.observe_credential_owners(parent_env); let active_sources = active_credential_sources(&state, child_env); for source in &active_sources { for env_var in &source.env_vars { let Some(real_value) = brokerable_credential_value(parent_env, &state, env_var, &source.provider) .map(str::to_string) else { continue; }; // Reconcile carried identities in virtualize_env, preserving their registered // destinations instead of binding the parent's value to a rotated child source. if state.credentials.iter().any(|credential| { env_key_matches(&credential.env_var, env_var) && credential.provider.same_provider(&source.provider) && credential.real_value == real_value && credential.has_carried_identity( child_env, &state.credentials, &active_sources, ) }) { continue; } if env_value(child_env, env_var) != Some(real_value.as_str()) && child_env.values().any(|value| { value == &real_value || is_builtin_shaped_credential(&real_value) && value.contains(&real_value) || source.provider.contains_embedded_value(value, &real_value) }) { let _ = state.register( env_var, source.provider.clone(), source.host_binding.clone(), environment_id, &real_value, parent_env, ); } } } } #[cfg(test)] pub(crate) fn virtualize_child_env(&self, env: &mut HashMap) { self.virtualize_child_env_for_environment(env, /*environment_id*/ None); } pub(crate) fn virtualize_child_env_for_environment( &self, env: &mut HashMap, environment_id: Option<&str>, ) { self.virtualize_env(env, environment_id, CredentialEnvironment::Child); } pub(crate) fn virtualize_snapshot_env( &self, env: &mut HashMap, environment_id: Option<&str>, ) { self.virtualize_env(env, environment_id, CredentialEnvironment::Snapshot); } fn virtualize_env( &self, env: &mut HashMap, environment_id: Option<&str>, destination: CredentialEnvironment, ) { let mut state = self.write_state(); if !state.enabled { remove_env_value(env, CREDENTIAL_BROKER_ACTIVE_ENV_KEY); remove_env_value(env, BROKERED_CREDENTIALS_ENV_KEY); return; } set_env_value(env, CREDENTIAL_BROKER_ACTIVE_ENV_KEY, "1".to_string()); state.observe_credential_owners(env); let active_sources = active_credential_sources(&state, env); let carried_dummies = state .credentials .iter() .filter(|credential| { credential.has_carried_identity(env, &state.credentials, &active_sources) }) .map(|credential| credential.dummy_value.clone()) .collect::>(); for credential in state.credentials.iter_mut().filter(|credential| { credential.belongs_to_environment(environment_id) && carried_dummies.contains(&credential.dummy_value) }) { if let Some(source) = registered_credential_source(credential, &active_sources, env) .filter(|source| { credential.invalidates_host_binding(env) || source_tracks_credential(source, credential, env) }) { credential.observe_host_binding(source.host_binding, env); } } let stale_credentials = state .credentials .iter() .filter(|credential| { credential.belongs_to_environment(environment_id) && carried_dummies.contains(&credential.dummy_value) && !registered_credential_source(credential, &active_sources, env) .is_some_and(|source| source_tracks_credential(&source, credential, env)) }) .map(|credential| { ( credential.dummy_value.clone(), credential.real_value.clone(), ) }) .collect::>(); state.replace_child_env_dummies(env, &stale_credentials); state.credentials.retain(|credential| { !credential.belongs_to_environment(environment_id) || !stale_credentials .iter() .any(|(dummy_value, _)| credential.dummy_value == *dummy_value) }); let mut owned_dummies = state .credentials .iter() .filter(|credential| { credential.belongs_to_environment(environment_id) && carried_dummies.contains(&credential.dummy_value) }) .map(|credential| credential.dummy_value.clone()) .collect::>(); let unbound_inherited_credentials = state .credentials .iter() .filter(|credential| { !credential.belongs_to_environment(environment_id) && !owned_dummies.contains(&credential.dummy_value) && env_contains_credential_value(env, &credential.dummy_value) && registered_credential_source(credential, &active_sources, env).is_none() }) .map(|credential| { ( credential.dummy_value.clone(), credential.real_value.clone(), ) }) .collect::>(); state.replace_child_env_dummies(env, &unbound_inherited_credentials); let inherited_credentials = state .credentials .iter() .filter(|credential| { !credential.belongs_to_environment(environment_id) && !owned_dummies.contains(&credential.dummy_value) && carried_dummies.contains(&credential.dummy_value) }) .filter_map(|credential| { registered_credential_source(credential, &active_sources, env).map(|source| { ( credential.env_var.clone(), source.provider, source.host_binding, credential.real_value.clone(), credential.dummy_value.clone(), credential.source_values.clone(), Arc::clone(&credential.generated_aliases), ) }) }) .collect::>(); let mut rebound_dummies = Vec::new(); for ( env_var, provider, host_binding, real_value, inherited_dummy, source_values, generated_aliases, ) in inherited_credentials { let current_dummy = if let Some(credential) = state.credentials.iter_mut().find(|credential| { credential.belongs_to_environment(environment_id) && credential.provider.same_provider(&provider) && env_key_matches(&credential.env_var, &env_var) && credential.real_value == real_value }) { credential.observe_host_binding(host_binding, env); credential.dummy_value.clone() } else { state.credentials.push(CredentialRecord { env_var, provider, host_binding, additional_host_bindings: Vec::new(), fallback_host_bindings: Vec::new(), environment_id: environment_id.map(str::to_string), real_value, dummy_value: inherited_dummy.clone(), source_values, generated_aliases, }); inherited_dummy.clone() }; if current_dummy != inherited_dummy { rebound_dummies.push((inherited_dummy, current_dummy.clone())); } owned_dummies.insert(current_dummy); } state.replace_child_env_dummies(env, &rebound_dummies); for credential in state .credentials .iter_mut() .filter(|credential| credential.belongs_to_environment(environment_id)) { for source in &mut credential.source_values { if let Some((_, rebound)) = rebound_dummies.iter().find(|(dummy, _)| dummy == source) { source.clone_from(rebound); } } } for source in &active_sources { for env_var in &source.env_vars { virtualize_env_var( env, &mut state, env_var, source.provider.clone(), source.host_binding.clone(), environment_id, ); } } let provider_context_keys = state.environment(env).provider_context_keys; let mut known_registrations = Vec::new(); let binding_env = state.context.with_fallbacks(env); let discoverable_values = env .iter() .filter(|(key, value)| { !key.eq_ignore_ascii_case("PATH") && !key.to_ascii_uppercase().ends_with("_PATH") && !crate::is_managed_proxy_env_var(key, value) && !provider_context_keys .iter() .any(|context_key| env_key_matches(key, context_key)) }) .map(|(_, value)| { let known = matching::known_credential_matches(&state, value, env); for matched in &known { if matched.value == matched.real_value && !state.credentials.iter().any(|credential| { owned_dummies.contains(&credential.dummy_value) && credential.real_value == matched.real_value && credential.provider.same_provider(&matched.provider) && env_key_matches(&credential.env_var, matched.env_var) }) && let Some(source) = active_sources.iter().find(|source| { source.provider.same_provider(&matched.provider) && source .env_vars .iter() .any(|key| env_key_matches(key, matched.env_var)) }) { known_registrations.push(( matched.env_var.to_string(), matched.provider.clone(), source.host_binding.clone(), matched.real_value.to_string(), )); } } matching::mask_known_credentials(value, &known) }) .collect::>(); // Keep normal destination reconciliation for exact identities before discovering new ones. for (env_var, provider, host_binding, real_value) in known_registrations { let _ = state.register( &env_var, provider, host_binding, environment_id, &real_value, env, ); } let configured_discoveries = state .configured_providers .iter() .flat_map(|provider| { discoverable_values.iter().flat_map(move |value| { provider .find_discoverable_credentials(value) .filter(|matched| !value[matched.range.clone()].contains('\0')) .map(move |matched| (Arc::clone(provider), value, matched)) }) }) .collect::>(); let mut builtin_spans = Vec::new(); for provider in providers::credential_providers() { let brokered_provider = BrokeredCredentialProvider::Builtin(provider); let Some((source, host_binding)) = provider.sources().iter().rev().find_map(|source| { (source.host_binding)(&binding_env, state.openai_api_host.as_deref()) .map(|binding| (source, binding)) }) else { continue; }; for value in &discoverable_values { for prefix in provider.credential_prefixes { for (start, _) in value.match_indices(prefix) { let credential = matching::builtin_credential_candidate(provider, value, start); let configured_start = configured_discoveries .iter() .filter(|(_, candidate_value, matched)| { *candidate_value == value && matched.has_distinctive_prefix && matched.range.start > start && matched.range.start < start + credential.len() }) .map(|(_, _, matched)| matched.range.start - start) .filter(|end| { credential[..*end].trim_end_matches(['_', '-']).len() >= provider.minimum_credential_len }) .min(); let credential = configured_start.map_or(credential, |end| { credential[..end].trim_end_matches(['_', '-']) }); if credential.len() < provider.minimum_credential_len || matching::is_operational_path_match( value, start, start + credential.len(), ) || provider.ignored_credential_prefixes.iter().any(|ignored| { credential.starts_with(ignored) && provider .credential_watermark .is_none_or(|watermark| !credential.contains(watermark)) }) || provider.request_header_value(credential).is_none() { continue; } // Retain ownership spans even when registration fails or is ambiguous. let span = start..start + credential.len(); builtin_spans.push((value, span.clone())); if configured_discoveries .iter() .any(|(_, candidate_value, matched)| { *candidate_value == value && matched.range.start == start && matched.range.end >= span.end }) || state.is_dummy_value(credential) || state.credentials.iter().any(|existing| { existing.belongs_to_environment(environment_id) && existing.provider.same_provider(&brokered_provider) && existing.host_binding == host_binding && existing.real_value == credential }) || state.credential_owners.iter().any(|existing| { existing.real_value == credential && (source.binding_env_vars.is_empty() && !source .env_vars .iter() .any(|key| env_key_matches(key, &existing.env_var)) || !provider.sources().iter().any(|candidate| { candidate .env_vars .iter() .any(|key| env_key_matches(key, &existing.env_var)) })) }) || provider.sources().iter().any(|candidate| { candidate .env_vars .iter() .any(|key| env_value(env, key) == Some(credential)) && (candidate.host_binding)( &binding_env, state.openai_api_host.as_deref(), ) .is_none() }) { continue; } let _ = state.register( source.env_vars[0], brokered_provider.clone(), host_binding.clone(), environment_id, credential, env, ); } } } } for (provider, value, matched) in &configured_discoveries { let Some(host_binding) = provider.host_binding(&binding_env) else { continue; }; let Some(env_var) = provider.config.env.first() else { continue; }; let brokered_provider = BrokeredCredentialProvider::Configured(provider.clone()); let range = &matched.range; let credential = &value[range.clone()]; if matching::is_operational_path_match(value, range.start, range.end) || builtin_spans.iter().any(|(candidate_value, span)| { candidate_value == value && span.start < range.end && range.start < span.end }) || configured_discoveries .iter() .any(|(other, candidate_value, matched)| { !Arc::ptr_eq(provider, other) && candidate_value == value && matched.range.start < range.end && range.start < matched.range.end }) || state.is_dummy_value(credential) || state.credentials.iter().any(|existing| { existing.belongs_to_environment(environment_id) && existing.provider.same_provider(&brokered_provider) && existing.host_binding == host_binding && existing.real_value == credential }) || credential_provider_definitions(&state).any(|other| { !other.same_provider(&brokered_provider) && other.recognizes_credential_in(credential) }) || brokered_provider.request_header_value(credential).is_none() { continue; } let _ = state.register( env_var, brokered_provider.clone(), host_binding.clone(), environment_id, credential, env, ); } let credentials = prioritized_credentials(&state, env) .into_iter() .filter(|credential| { credential.belongs_to_environment(environment_id) && (owned_dummies.contains(&credential.dummy_value) || active_sources.iter().any(|source| { credential.provider.same_provider(&source.provider) && credential.host_binding == source.host_binding })) }) .collect::>(); let mut credential_aliases = Vec::new(); for (key, value) in env.iter_mut() { if crate::is_managed_proxy_env_var(key, value) { continue; } let mut replacements = Replacements::default(); for credential in &credentials { for original in [&credential.real_value, &credential.dummy_value] { replacements.add( credential.value_match_ranges(value, original), &credential.dummy_value, Some(credential), ); } } if replacements.render(value) { credential_aliases.push(CredentialAlias { env_var: key.clone(), dummy_value: value.clone(), }); } } for alias in credential_aliases { if !state.credential_aliases.iter().any(|existing| { env_key_matches(&existing.env_var, &alias.env_var) && existing.dummy_value == alias.dummy_value }) { state.credential_aliases.push(alias); } } if state.allow_local_binding && matches!(destination, CredentialEnvironment::Child) { // Clients bypass the proxy for local destinations, so their credentials must stay real. state.restore_child_env(env, |credential| { credential.belongs_to_environment(environment_id) && credential .host_bindings() .any(providers::CredentialHostBinding::bypasses_proxy_with_local_binding) }); } update_brokered_credentials_marker(&state, env); } pub(crate) fn restore_child_env( &self, env: &mut HashMap, _command: &mut [String], ) { let state = self.read_state(); if !state.enabled || env_value(env, CREDENTIAL_BROKER_ACTIVE_ENV_KEY) != Some("1") { return; } state.restore_child_env(env, |_| true); } pub(crate) fn restore_and_disable_child_env( &self, env: &mut HashMap, command: &mut [String], ) { self.restore_child_env(env, command); remove_env_value(env, CREDENTIAL_BROKER_ACTIVE_ENV_KEY); remove_env_value(env, BROKERED_CREDENTIALS_ENV_KEY); } pub(crate) fn child_alias_matches( &self, key: &str, value: &str, snapshot_value: &str, environment_id: Option<&str>, ) -> bool { let state = self.read_state(); if !state.enabled { return false; } let mut expected = HashMap::from([(key.to_string(), snapshot_value.to_string())]); state.restore_child_env(&mut expected, |_| true); let referenced_credentials = |text: &str| { state .credentials .iter() .filter(|credential| { text == credential.dummy_value || credential.contains_embedded_value(text, &credential.dummy_value) }) .collect::>() }; let expected_credentials = referenced_credentials(snapshot_value); if expected_credentials.is_empty() { return false; } state.credential_aliases.iter().any(|alias| { if !env_key_matches(key, &alias.env_var) { return false; } let alias_credentials = referenced_credentials(&alias.dummy_value); let same_owner = |left: &&CredentialRecord, right: &&CredentialRecord| { left.provider.same_provider(&right.provider) && env_key_matches(&left.env_var, &right.env_var) && left.real_value == right.real_value }; if !expected_credentials.iter().all(|expected| { alias_credentials .iter() .any(|actual| same_owner(expected, actual)) }) || !alias_credentials.iter().all(|actual| { expected_credentials .iter() .any(|expected| same_owner(expected, actual)) }) { return false; } let mut candidate = HashMap::from([(key.to_string(), alias.dummy_value.clone())]); let mut identity = candidate.clone(); if state.allow_local_binding { state.restore_child_env(&mut candidate, |credential| { credential.belongs_to_environment(environment_id) && credential.host_bindings().any( providers::CredentialHostBinding::bypasses_proxy_with_local_binding, ) }); } if env_value(&candidate, key) != Some(value) { return false; } state.restore_child_env(&mut identity, |_| true); identity == expected }) } #[cfg(test)] pub(crate) fn host_requires_mitm(&self, host: &str, port: u16) -> bool { self.host_protocols_for_environment(host, port, /*environment_id*/ None) .tls } pub(crate) fn host_protocols_for_environment( &self, host: &str, port: u16, environment_id: Option<&str>, ) -> crate::brokered_tunnel::BrokeredProtocols { let normalized_host = normalize_host(host); let state = self.read_state(); let mut protocols = crate::brokered_tunnel::BrokeredProtocols::default(); if state.enabled { for credential in state .credentials .iter() .filter(|credential| credential.belongs_to_environment(environment_id)) { for binding in credential.host_bindings() { protocols.tls |= binding.requires_mitm(&normalized_host, port); if let providers::CredentialHostBinding::ConfiguredHosts(destinations) = binding { protocols.http |= destinations.iter().any(|destination| { destination.requires_http_interception(&normalized_host, port) }); } } } } protocols } pub(crate) fn virtualize_text(&self, text: &mut String, env: &HashMap) -> bool { let state = self.read_state(); matching::virtualize_text(&state, text, env) } pub(crate) fn handle_unmasked_credentials( &self, env: &mut HashMap, mode: CredentialMaskingFailureMode, ) -> anyhow::Result<()> { let state = self.read_state(); anyhow::ensure!( state.enabled, "credential masking requires an enabled credential broker" ); let mut unmasked_keys = Vec::new(); for (key, value) in env.iter() { let value = value.trim(); let known = matching::known_credential_matches(&state, value, env); let contains_real_credential = known .iter() .any(|matched| matched.value == matched.real_value); let remainder = matching::mask_known_credentials(value, &known); let mut masked = remainder.clone(); if contains_real_credential || !matching::virtualize_text(&state, &mut masked, env) || masked != remainder { unmasked_keys.push(key.clone()); } } drop(state); if unmasked_keys.is_empty() { return Ok(()); } match mode { CredentialMaskingFailureMode::Warn => { tracing::warn!( "Credential masking could not protect some environment credentials; continuing with those values unmasked." ); } CredentialMaskingFailureMode::Deny => { for key in unmasked_keys { env.remove(&key); } tracing::warn!( "Credential masking could not protect some environment credentials; removed those variables before execution." ); } CredentialMaskingFailureMode::Error => { anyhow::bail!( "credential masking left an unmasked credential in the command environment" ); } } Ok(()) } pub(crate) fn restore_text(&self, text: &mut String) -> bool { let state = self.read_state(); if !state.enabled { return false; } let mut credentials = state.credentials.iter().collect::>(); credentials .sort_unstable_by_key(|credential| std::cmp::Reverse(credential.dummy_value.len())); let mut replacements = Replacements::default(); for credential in credentials { replacements.add( credential.value_match_ranges(text, &credential.dummy_value), &credential.real_value, /*dummy*/ None, ); } replacements.render(text) } pub(crate) fn environment(&self, env: &HashMap) -> CredentialBrokerEnvironment { self.read_state().environment(env) } pub(crate) fn environment_for_text( &self, text: &str, env: &HashMap, ) -> CredentialBrokerEnvironment { let state = self.read_state(); let mut scoped_env = env.clone(); for credential in &state.credentials { remove_env_value(&mut scoped_env, &credential.env_var); } for credential in &state.credentials { if text == credential.dummy_value || credential.contains_embedded_value(text, &credential.dummy_value) { set_env_value( &mut scoped_env, &credential.env_var, credential.dummy_value.clone(), ); } } update_brokered_credentials_marker(&state, &mut scoped_env); state.environment(&scoped_env) } pub(crate) fn source_matches_text(&self, source: &str, source_value: &str, text: &str) -> bool { let state = self.read_state(); state.enabled && state.credentials.iter().any(|credential| { env_key_matches(&credential.env_var, source) && credential.real_value == source_value && (text == credential.real_value || credential.contains_embedded_value(text, &credential.real_value)) }) } pub(crate) fn provider_sources_allowed( &self, value: &str, virtualized: &str, source_env: &HashMap, is_allowed: impl Fn(&str) -> bool, ) -> bool { let state = self.read_state(); let known = matching::known_credential_matches(&state, value, source_env); if known.iter().any(|matched| { !known.iter().any(|equivalent| { equivalent.range == matched.range && equivalent.provider.same_provider(&matched.provider) && equivalent.real_value == matched.real_value && is_allowed(equivalent.env_var) }) }) { return false; } let uncovered = matching::mask_known_credentials(value, &known); let value = uncovered.as_str(); let builtin_recognized = providers::credential_providers().any(|provider| { provider.credential_prefixes.iter().any(|prefix| { value.match_indices(*prefix).any(|(start, _)| { matching::recognized_credential_match(provider, value, virtualized, start) .is_some() }) }) }); if builtin_recognized && !credential_broker_provider_sources_allowed( value, virtualized, source_env, &is_allowed, ) { return false; } let mut configured_recognized = false; for provider in &state.configured_providers { for source in &provider.config.env { if let Some(credential) = env_value(source_env, source) && provider.matches_value(credential) && !provider .credential_value_match_ranges(value, credential) .is_empty() && provider .credential_value_match_ranges(virtualized, credential) .is_empty() { configured_recognized = true; if !provider.config.env.iter().any(|equivalent| { env_value(source_env, equivalent) == Some(credential) && is_allowed(equivalent) }) { return false; } } } for credential in provider.find_credentials(value).filter(|credential| { !credential.contains('\0') && !virtualized.contains(credential) }) { configured_recognized = true; let sources = provider .config .env .iter() .filter(|source| env_value(source_env, source) == Some(credential)) .collect::>(); let allowed = if sources.is_empty() { provider.config.env.iter().all(|source| is_allowed(source)) } else { sources.iter().any(|source| is_allowed(source)) }; if !allowed { return false; } } } !known.is_empty() || builtin_recognized || configured_recognized } #[cfg(test)] pub(crate) fn inject_request_headers(&self, destination: &str, headers: &mut HeaderMap) { self.inject_request_headers_for_environment( destination, headers, /*environment_id*/ None, ); } pub(crate) fn inject_request_headers_for_environment( &self, destination: &str, headers: &mut HeaderMap, environment_id: Option<&str>, ) { let state = self.read_state(); if !state.enabled { return; } let credentials = select_credentials(headers, destination, &state.credentials, environment_id); for (credential, header_name, header_value) in credentials { credential .provider .insert_request_header(headers, header_name, header_value); } } pub(crate) fn request_matches_hooked_host_alias( &self, destination: &str, headers: &HeaderMap, hooked_hosts: &[String], environment_id: Option<&str>, ) -> bool { let request = if destination.contains("://") { Url::parse(destination).ok() } else { None }; let normalized_host = normalize_host( request .as_ref() .and_then(Url::host_str) .unwrap_or(destination), ); let state = self.read_state(); if !state.enabled { return false; } let selected_headers = select_credentials(headers, destination, &state.credentials, environment_id) .into_iter() .map(|(_, name, _)| name) .collect::>(); if selected_headers.is_empty() { return false; } let matching_credentials = state .credentials .iter() .filter(|credential| { credential.belongs_to_environment(environment_id) && credential .host_bindings() .any(|binding| binding.matches_request(&normalized_host, request.as_ref())) }) .collect::>(); // Ambiguity removes the whole header, so every translation for a surviving header // contributed through equivalent deduplication or Basic-field merging. matching_credentials.iter().any(|credential| { hooked_hosts.iter().any(|hooked_host| { hooked_host != &normalized_host && credential .host_bindings() .any(|binding| binding.matches_hostname(hooked_host)) }) && matching_credentials.iter().any(|candidate| { candidate.provider.same_provider(&credential.provider) && candidate.real_value == credential.real_value && credential .provider .translate_request_headers( headers, &candidate.dummy_value, &credential.real_value, ) .iter() .any(|(name, _)| selected_headers.contains(name)) }) }) } fn read_state(&self) -> std::sync::RwLockReadGuard<'_, CredentialBrokerState> { self.state .read() .unwrap_or_else(std::sync::PoisonError::into_inner) } fn write_state(&self) -> std::sync::RwLockWriteGuard<'_, CredentialBrokerState> { self.state .write() .unwrap_or_else(std::sync::PoisonError::into_inner) } } fn virtualize_env_var( env: &mut HashMap, state: &mut CredentialBrokerState, env_var: &str, provider: BrokeredCredentialProvider, host_binding: providers::CredentialHostBinding, environment_id: Option<&str>, ) { let previous_dummy = env_value(env, env_var) .filter(|value| state.is_dummy_value(value)) .map(str::to_string); let Some(real_value) = brokerable_credential_value(env, state, env_var, &provider) .map(str::to_string) .or_else(|| { let dummy = previous_dummy.as_deref()?; state .credentials .iter() .find(|credential| { credential.dummy_value == dummy && credential.provider.same_provider(&provider) && env_key_matches(&credential.env_var, env_var) }) .map(|credential| credential.real_value.clone()) }) else { return; }; if let Some(dummy_value) = state.register( env_var, provider, host_binding, environment_id, &real_value, env, ) { if let Some(previous_dummy) = previous_dummy && previous_dummy != dummy_value { state.replace_child_env_dummies(env, &[(previous_dummy, dummy_value.clone())]); } set_env_value(env, env_var, dummy_value); } } fn brokerable_credential_value<'a>( env: &'a HashMap, state: &CredentialBrokerState, env_var: &str, provider: &BrokeredCredentialProvider, ) -> Option<&'a str> { let real_value = env_value(env, env_var)?; let real_value = match provider { BrokeredCredentialProvider::Builtin(_) => real_value.trim(), BrokeredCredentialProvider::Configured(_) => real_value, }; (!real_value.is_empty() && !state.is_dummy_value(real_value) && provider.request_header_value(real_value).is_some()) .then_some(real_value) } impl CredentialBrokerState { fn restore_child_env( &self, env: &mut HashMap, should_restore: impl Fn(&CredentialRecord) -> bool, ) { let credentials = self .credentials .iter() .filter(|credential| should_restore(credential)) .filter(|credential| { env.iter().any(|(key, value)| { !env_key_matches(key, CREDENTIAL_BROKER_ACTIVE_ENV_KEY) && !env_key_matches(key, BROKERED_CREDENTIALS_ENV_KEY) && (value == &credential.dummy_value || credential.contains_embedded_value(value, &credential.dummy_value)) }) }) .collect::>(); for (key, value) in env.iter_mut() { if env_key_matches(key, CREDENTIAL_BROKER_ACTIVE_ENV_KEY) || env_key_matches(key, BROKERED_CREDENTIALS_ENV_KEY) { continue; } let canonical_credential = self .credentials .iter() .any(|credential| env_key_matches(key, &credential.env_var)); if !canonical_credential && !self.credential_aliases.iter().any(|alias| { env_key_matches(key, &alias.env_var) && value == &alias.dummy_value }) { continue; } let mut replacements = Replacements::default(); for credential in &credentials { if canonical_credential && !self.credentials.iter().any(|candidate| { env_key_matches(key, &candidate.env_var) && candidate.provider.same_provider(&credential.provider) && candidate.host_binding == credential.host_binding && candidate.real_value == credential.real_value }) { continue; } replacements.add( credential.value_match_ranges(value, &credential.dummy_value), &credential.real_value, /*dummy*/ None, ); } // Carry surviving dummy spans through partial local-destination restoration. for credential in &self.credentials { replacements.add( credential.value_match_ranges(value, &credential.dummy_value), &credential.dummy_value, Some(credential), ); } replacements.render(value); } } fn observe_credential_owners(&mut self, env: &HashMap) { // Ownership is known even when a destination is missing or invalid. let sources = providers::credential_providers() .flat_map(|provider| { provider.sources().iter().flat_map(move |source| { source .env_vars .iter() .map(move |key| (*key, BrokeredCredentialProvider::Builtin(provider))) }) }) .chain(self.configured_providers.iter().flat_map(|provider| { provider.config.env.iter().map(move |key| { ( key.as_str(), BrokeredCredentialProvider::Configured(Arc::clone(provider)), ) }) })); let owners = sources .filter_map(|(key, provider)| { brokerable_credential_value(env, self, key, &provider) .map(|value| (key.to_string(), value.to_string())) }) .collect::>(); for (key, value) in owners { self.remember_credential_owner(&key, &value); } } fn remember_credential_owner(&mut self, env_var: &str, real_value: &str) { if !self .credential_owners .iter() .any(|owner| env_key_matches(&owner.env_var, env_var) && owner.real_value == real_value) { self.credential_owners.push(CredentialOwner { env_var: env_var.to_string(), real_value: real_value.to_string(), }); } } fn register( &mut self, env_var: &str, provider: BrokeredCredentialProvider, host_binding: providers::CredentialHostBinding, environment_id: Option<&str>, real_value: &str, existing_env: &HashMap, ) -> Option { self.remember_credential_owner(env_var, real_value); if let Some(existing) = self.credentials.iter_mut().find(|credential| { env_key_matches(&credential.env_var, env_var) && credential.provider.same_provider(&provider) && credential.belongs_to_environment(environment_id) && credential.real_value == real_value }) { // Existing dummies were reconciled before fresh-value discovery. if !env_contains_credential_value(existing_env, &existing.dummy_value) { existing.observe_host_binding(host_binding, existing_env); } return Some(existing.dummy_value.clone()); } if self.credentials.iter().any(|credential| { is_builtin_shaped_credential(real_value) && credential.dummy_value.contains(real_value) || provider.contains_embedded_value(&credential.dummy_value, real_value) }) { tracing::warn!( env_var, "credential brokerage skipped: credential overlaps an existing dummy" ); return None; } let Some(dummy_value) = (0..64).find_map(|_| { let candidate = provider.dummy_value(real_value)?; (candidate != real_value // These records restore embedded dummies without configured regex boundaries. && (!is_builtin_shaped_credential(real_value) || candidate.len() >= MIN_EMBEDDED_CREDENTIAL_LENGTH) && !existing_env .values() .any(|value| value.contains(&candidate)) && !self.credentials.iter().any(|credential| { credential.dummy_value == candidate || credential.real_value == candidate || credential.contains_embedded_value(&candidate, &credential.dummy_value) || credential.contains_embedded_value(&candidate, &credential.real_value) }) && !credential_provider_definitions(self).any(|other| { !other.same_provider(&provider) && other.recognizes_strictly_embedded_dummy_collision_in(&candidate) })) .then_some(candidate) }) else { tracing::warn!( env_var, "credential brokerage skipped: unable to generate a unique dummy credential" ); return None; }; let source_values = env_value(existing_env, env_var) .map(|value| { let value = match &provider { BrokeredCredentialProvider::Builtin(_) => value.trim(), BrokeredCredentialProvider::Configured(_) => value, }; if value == real_value { vec![real_value.to_string(), dummy_value.clone()] } else if let Some(source) = self .credentials .iter() .find(|source| source.dummy_value == value) { vec![source.real_value.clone(), source.dummy_value.clone()] } else { vec![value.to_string()] } }) .unwrap_or_default(); self.credentials.push(CredentialRecord { env_var: env_var.to_string(), provider, host_binding, additional_host_bindings: Vec::new(), fallback_host_bindings: Vec::new(), environment_id: environment_id.map(str::to_string), real_value: real_value.to_string(), dummy_value: dummy_value.clone(), source_values, generated_aliases: Arc::default(), }); Some(dummy_value) } fn is_dummy_value(&self, value: &str) -> bool { self.credentials .iter() .any(|credential| credential.dummy_value == value) } } fn credential_provider_definitions( state: &CredentialBrokerState, ) -> impl Iterator + '_ { providers::credential_providers() .map(BrokeredCredentialProvider::Builtin) .chain( state .configured_providers .iter() .cloned() .map(BrokeredCredentialProvider::Configured), ) } #[cfg(test)] #[path = "credential_broker_tests.rs"] mod tests; #[cfg(test)] #[path = "credential_broker/configured_tests.rs"] mod configured_tests;