Support configured credential providers in remote execution (#56049)

## Why

Remote credential masking previously fell back to unmasked execution whenever custom credential providers were configured.

## What changed

- Transport configured provider rules to the executor-local network proxy, where credential values and destination context are resolved.
- Advertise `credentialMaskingCustomProviders` and negotiate masking support. Keep the warning and unmasked fallback for executors that support only built-in providers.
- Discard provider rules when masking is disabled and omit their contents from debug output.

## Testing

Extend coverage for capability compatibility, provider configuration round trips, and fallback on older executors. Exercise configured-provider masking in remote request history and verify that masked placeholders are restored to real credentials in upstream request headers.

GitOrigin-RevId: b036f56900b343ddccc17b5293c82bc1f3426237
This commit is contained in:
viyatb-oai
2026-10-22 01:54:51 +00:00
committed by copyberry
parent 0769c6aeeb
commit 2a77e82f09
14 changed files with 479 additions and 81 deletions
+58 -15
View File
@@ -12,6 +12,7 @@ use crate::http_proxy;
use crate::network_policy::NetworkDecision;
use crate::network_policy::NetworkDecisionSource;
use crate::network_policy::NetworkPolicyDecider;
use crate::remote_config::RemoteCredentialMasking;
use crate::runtime::BlockedRequestObserver;
use crate::runtime::ConfigState;
use crate::runtime::HostAuthorization;
@@ -949,15 +950,17 @@ impl NetworkProxy {
pub async fn remote_launch_config(
&self,
local_binding_policy: crate::LocalBindingPolicy,
credential_masking: bool,
credential_masking: RemoteCredentialMasking,
) -> Result<crate::RemoteNetworkProxyLaunchConfig> {
let (mut config, brokerage_created_default_allowlist, _) =
self.state.current_cfg_with_brokerage_provenance().await?;
// Configured providers are not transported yet, so these commands run unmasked.
let credential_masking = credential_masking
&& config.enabled
let credential_masking = config.enabled
&& config.credential_broker
&& config.credential_providers.is_empty();
&& match credential_masking {
RemoteCredentialMasking::Disabled => false,
RemoteCredentialMasking::BuiltInOnly => config.credential_providers.is_empty(),
RemoteCredentialMasking::WithConfiguredProviders => true,
};
anyhow::ensure!(
!credential_masking
|| (!config.dangerously_allow_plaintext_credential_injection
@@ -1021,7 +1024,9 @@ impl NetworkProxy {
"MXC cannot enforce allow_local_binding=false: native host-loopback access is bidirectional; use true or a different sandbox backend"
);
let mut proxy = crate::RemoteNetworkProxyConfig::from_effective_config(&config)?;
proxy.credential_masking = credential_masking;
if credential_masking {
proxy.enable_credential_masking(config.credential_providers);
}
tracing::debug!(network_config = ?proxy, "resolved remote execution network configuration");
let (environment_id, execution_id) = self
.execution_scope
@@ -1970,7 +1975,7 @@ mod tests {
assert_eq!(proxy.current_cfg().await?, config);
let remote = proxy
.remote_launch_config(DefaultFalse, /*credential_masking*/ false)
.remote_launch_config(DefaultFalse, RemoteCredentialMasking::Disabled)
.await?;
assert_eq!(
(
@@ -2068,7 +2073,7 @@ mod tests {
owner_config.set_denied_domains(vec!["denied.example".to_string()]);
assert_eq!(
scoped
.remote_launch_config(DefaultFalse, /*credential_masking*/ false)
.remote_launch_config(DefaultFalse, RemoteCredentialMasking::Disabled)
.await?
.proxy
.domains,
@@ -2507,7 +2512,7 @@ mod tests {
/*fallback_policy_decider*/ None,
)?;
let launch = scoped
.remote_launch_config(RequireTrue, /*credential_masking*/ false)
.remote_launch_config(RequireTrue, RemoteCredentialMasking::Disabled)
.await?;
let prepared = scoped.prepare_for_optional_environment(
HashMap::from([(
@@ -2522,7 +2527,7 @@ mod tests {
assert!(!launch.proxy.enabled);
assert!(
scoped
.remote_launch_config(DefaultFalse, /*credential_masking*/ true)
.remote_launch_config(DefaultFalse, RemoteCredentialMasking::BuiltInOnly)
.await
.is_err()
);
@@ -2538,16 +2543,51 @@ mod tests {
},
);
let custom_provider_proxy = NetworkProxy::builder()
.state(Arc::new(network_proxy_state_for_policy(config)))
.state(Arc::new(network_proxy_state_for_policy(config.clone())))
.build()
.await?;
assert_eq!(
custom_provider_proxy
.remote_launch_config(DefaultFalse, /*credential_masking*/ true)
.remote_launch_config(DefaultFalse, RemoteCredentialMasking::BuiltInOnly)
.await?
.proxy,
launch.proxy
);
assert!(
custom_provider_proxy
.remote_launch_config(
DefaultFalse,
RemoteCredentialMasking::WithConfiguredProviders
)
.await
.is_err()
);
config.dangerously_allow_plaintext_credential_injection = false;
let custom_provider_proxy = NetworkProxy::builder()
.state(Arc::new(network_proxy_state_for_policy(config.clone())))
.build()
.await?;
assert_eq!(
custom_provider_proxy
.remote_launch_config(DefaultFalse, RemoteCredentialMasking::Disabled)
.await?
.proxy,
launch.proxy
);
let custom_launch = custom_provider_proxy
.remote_launch_config(
DefaultFalse,
RemoteCredentialMasking::WithConfiguredProviders,
)
.await?;
assert!(custom_launch.proxy.credential_masking_enabled());
assert_eq!(
custom_launch
.proxy
.into_network_proxy_config()
.credential_providers,
config.credential_providers,
);
let mut owner_config = NetworkProxyConfig::default();
owner_config.set_allowed_domains(vec!["owner.example".to_string()]);
@@ -2563,7 +2603,7 @@ mod tests {
)?;
assert!(
owner_scoped
.remote_launch_config(DefaultFalse, /*credential_masking*/ false)
.remote_launch_config(DefaultFalse, RemoteCredentialMasking::Disabled)
.await
.is_err()
);
@@ -2595,13 +2635,16 @@ mod tests {
.state(Arc::new(network_proxy_state_for_policy(config)))
.build()
.await?;
for credential_masking in [false, true] {
for (credential_masking, expected_masking) in [
(RemoteCredentialMasking::Disabled, false),
(RemoteCredentialMasking::BuiltInOnly, true),
] {
let launch = proxy
.remote_launch_config(DefaultFalse, credential_masking)
.await?;
assert!(launch.proxy.enabled);
assert_eq!(launch.proxy.mode, mode);
assert_eq!(launch.proxy.credential_masking, credential_masking);
assert_eq!(launch.proxy.credential_masking_enabled(), expected_masking);
}
}
assert_eq!(