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
@@ -10,6 +10,7 @@ use codex_network_proxy::LocalBindingPolicy::RequireTrue;
use codex_network_proxy::NetworkDomainPermission;
use codex_network_proxy::NetworkUnixSocketPermission;
use codex_network_proxy::NetworkUnixSocketPermissions;
use codex_network_proxy::RemoteCredentialMasking;
use codex_protocol::models::ManagedFileSystemPermissions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
@@ -105,7 +106,7 @@ async fn attachment_socket_grants_respect_configured_restrictions_at_remote_laun
/*fallback_policy_decider*/ None,
)?;
let launch = scoped
.remote_launch_config(DefaultFalse, /*credential_masking*/ false)
.remote_launch_config(DefaultFalse, RemoteCredentialMasking::Disabled)
.await?;
assert_eq!(
launch.proxy.dangerously_allow_all_unix_sockets, expected,
@@ -122,7 +123,7 @@ async fn attachment_socket_grants_respect_configured_restrictions_at_remote_laun
// Inheritance must not grant sockets to ordinary controller-only commands.
assert!(
!proxy
.remote_launch_config(DefaultFalse, /*credential_masking*/ false)
.remote_launch_config(DefaultFalse, RemoteCredentialMasking::Disabled)
.await?
.proxy
.dangerously_allow_all_unix_sockets
@@ -130,7 +131,7 @@ async fn attachment_socket_grants_respect_configured_restrictions_at_remote_laun
state.add_allowed_domain("granted.example").await?;
assert!(
scoped
.remote_launch_config(DefaultFalse, /*credential_masking*/ false)
.remote_launch_config(DefaultFalse, RemoteCredentialMasking::Disabled)
.await?
.proxy
.dangerously_allow_all_unix_sockets
@@ -151,7 +152,7 @@ async fn attachment_socket_grants_respect_configured_restrictions_at_remote_laun
.await?;
assert!(
!scoped
.remote_launch_config(DefaultFalse, /*credential_masking*/ false)
.remote_launch_config(DefaultFalse, RemoteCredentialMasking::Disabled)
.await?
.proxy
.dangerously_allow_all_unix_sockets
@@ -893,7 +894,7 @@ async fn environment_local_binding_preserves_explicit_denials_and_inherits_omitt
Some(Arc::new(|_request| async { NetworkDecision::Allow })),
)?;
let launch = scoped
.remote_launch_config(local_binding_policy, /*credential_masking*/ false)
.remote_launch_config(local_binding_policy, RemoteCredentialMasking::Disabled)
.await;
assert_eq!(proxy.current_cfg().await?, carrier.config, "{name}");
if local_binding_policy == RequireTrue && !expected {
@@ -40,11 +40,11 @@ use crate::unified_exec::TerminalSandboxSource;
use crate::unified_exec::UnifiedExecProcess;
use crate::unified_exec::UnifiedExecProcessManager;
use codex_core_plugins::PluginMetricsSidecar;
use codex_exec_server::EnvironmentCapabilities;
use codex_features::Feature;
use codex_network_proxy::CREDENTIAL_BROKER_ACTIVE_ENV_KEY;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_network_proxy::NetworkProxy;
use codex_network_proxy::RemoteCredentialMasking;
use codex_network_proxy::RemoteNetworkProxyConfig;
use codex_protocol::error::CodexErr;
use codex_protocol::models::AdditionalPermissionProfile;
@@ -352,13 +352,18 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
"failed to query exec-server capabilities: {err}"
))))
})?;
let credential_masking = ctx
let network_proxy = environment_info.capabilities.network_proxy();
let credential_masking = if ctx
.step_context
.turn
.config
.features
.enabled(Feature::CredentialMasking)
&& environment_info.capabilities.credential_masking;
{
network_proxy.credential_masking
} else {
RemoteCredentialMasking::Disabled
};
let mut launch = network
.remote_launch_config(
crate::windows_sandbox::local_binding_policy_for_sandbox(
@@ -374,7 +379,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
warn_if_remote_credential_masking_unavailable(
ctx,
req,
&environment_info.capabilities,
network_proxy.credential_masking,
&launch.proxy,
)
.await;
@@ -402,7 +407,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
if !launch.proxy.enabled {
(env, None, None)
} else {
if !environment_info.capabilities.network_proxy_launch {
if !network_proxy.launch {
return Err(ToolError::Rejected(
"selected exec-server does not support executor-local network proxy launches"
.to_string(),
@@ -701,7 +706,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
async fn warn_if_remote_credential_masking_unavailable(
ctx: &ToolCtx,
req: &UnifiedExecRequest,
capabilities: &EnvironmentCapabilities,
credential_masking: RemoteCredentialMasking,
proxy_config: &RemoteNetworkProxyConfig,
) {
if !ctx
@@ -715,11 +720,11 @@ async fn warn_if_remote_credential_masking_unavailable(
}
let environment_id = &req.turn_environment.selection.environment_id;
let message = if !capabilities.credential_masking {
let message = if credential_masking == RemoteCredentialMasking::Disabled {
format!(
"Credential masking is unavailable on environment `{environment_id}` because its exec-server does not support it. This command will run with unmasked credentials."
)
} else if !proxy_config.credential_masking {
} else if !proxy_config.credential_masking_enabled() {
format!(
"Credential masking is unavailable for this network proxy configuration on environment `{environment_id}`. This command will run with unmasked credentials."
)
@@ -11,7 +11,7 @@ use pretty_assertions::assert_eq;
use pretty_assertions::assert_ne;
#[test_case::test_case(false, false, None; "builtin_providers")]
#[test_case::test_case(true, false, None; "configured_provider_fallback")]
#[test_case::test_case(true, false, None; "configured_provider")]
#[test_case::test_case(true, true, None; "configured_provider_login_fallback")]
#[test_case::test_case(true, false, Some("BASH_ENV"); "configured_provider_executor_startup_fallback")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -27,7 +27,8 @@ async fn remote_credential_masking_request_history(
eprintln!("skipping credential masking: this scenario requires a Bash or Zsh executor");
return Ok(());
}
let use_zsh_snapshot = shell.name() == "zsh" && !configured_provider;
let expect_masking = !use_login_shell && executor_startup_env.is_none();
let use_zsh_snapshot = shell.name() == "zsh" && expect_masking;
let home = Arc::new(TempDir::new()?);
if let Some(warning) =
codex_sandboxing::system_bwrap_warning(&PermissionProfile::workspace_write(), home.path())
@@ -35,11 +36,19 @@ async fn remote_credential_masking_request_history(
eprintln!("skipping sandbox test: {warning}");
return Ok(());
}
const API_KEY: &str = "sk-proj-executor-only-0000000000000000000000000000000000";
let (credential_env, credential, prefix) = if configured_provider {
("VENDOR_TOKEN", "vendor_abcdefgh", "vendor_")
} else {
(
"OPENAI_API_KEY",
"sk-proj-executor-only-0000000000000000000000000000000000",
"sk-proj-",
)
};
const CALL_ID: &str = "print-credential";
// Use our own native executor so only its environment contains the credential.
let mut executor_env = vec![
("OPENAI_API_KEY", API_KEY),
(credential_env, credential),
("OPENAI_BASE_URL", "https://api.openai.com/v1"),
];
executor_env.extend(executor_startup_env.map(|key| (key, "/dev/null")));
@@ -78,7 +87,7 @@ allow_local_binding = true
[shell_environment_policy]
ignore_default_excludes = true
include_only = ["HOME", "PATH", "OPENAI_API_KEY", "OPENAI_BASE_URL"{startup_env}]
include_only = ["HOME", "PATH", "{credential_env}", "OPENAI_BASE_URL"{startup_env}]
[features]
unified_exec = true
@@ -104,13 +113,14 @@ credential_masking = true
})
.build(&server)
.await?;
let mut command = if configured_provider {
let mut command = if !expect_masking {
// Keep the real credential out of the model-visible command text.
fs::write(test.workspace_path("expected-api-key"), API_KEY)?;
"test \"$OPENAI_API_KEY\" = \"$(<expected-api-key)\" && printf 'unmasked fallback'"
.to_string()
fs::write(test.workspace_path("expected-api-key"), credential)?;
format!(
"test \"${credential_env}\" = \"$(<expected-api-key)\" && printf 'unmasked fallback'"
)
} else {
"printf '%s' \"$OPENAI_API_KEY\"".to_string()
format!("printf '%s' \"${credential_env}\"")
};
if let Some(key) = executor_startup_env {
command = format!("test -n \"${key}\" && {command}");
@@ -143,11 +153,11 @@ credential_masking = true
.await;
test.codex
.start_or_steer_turn(TurnInputRequest::user_input(vec![text(
"Print OPENAI_API_KEY from the executor environment.",
)]))
.start_or_steer_turn(TurnInputRequest::user_input(vec![text(&format!(
"Print {credential_env} from the executor environment.",
))]))
.await?;
if configured_provider {
if use_login_shell || shell.name() == "zsh" && !use_zsh_snapshot {
let warning = wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::Warning(warning) if warning.message.starts_with("Credential masking is unavailable"))
})
@@ -155,7 +165,7 @@ credential_masking = true
let EventMsg::Warning(warning) = warning else {
unreachable!("waited for a credential masking warning")
};
insta::assert_snapshot!(warning.message, @"Credential masking is unavailable for this network proxy configuration on environment `remote`. This command will run with unmasked credentials.");
insta::assert_snapshot!(warning.message, @"Credential masking is unavailable for this shell startup on environment `remote`. This command will run with unmasked credentials.");
}
wait_for_event(&test.codex, |event| {
matches!(event, EventMsg::TurnComplete(_))
@@ -165,29 +175,31 @@ credential_masking = true
assert_eq!(requests.len(), 2);
// Check complete, raw requests before any snapshot rewriting or normalization.
for request in &requests {
assert!(!request.body_json().to_string().contains(API_KEY));
assert!(!request.body_json().to_string().contains(credential));
}
let output = requests[1]
.function_call_output_text(CALL_ID)
.context("model follow-up should include the executor output")?;
assert!(output.contains("Process exited with code 0"), "{output}");
if configured_provider {
if !expect_masking {
assert!(output.ends_with("Output:\nunmasked fallback"), "{output}");
return Ok(());
}
let (_, placeholder) = output
.split_once("Output:\n")
.context("executor output should contain a credential placeholder")?;
assert!(placeholder.starts_with("sk-proj-"));
assert_eq!(placeholder.len(), API_KEY.len());
assert_ne!(placeholder, API_KEY);
assert!(placeholder.starts_with(prefix));
assert_eq!(placeholder.len(), credential.len());
assert_ne!(placeholder, credential);
// Share the full-history snapshots after normalizing the provider-specific variable name.
let mut snapshot = context_snapshot::format_request_history_snapshot(
"A remote executor masks its credential before the command output reaches the model.",
&requests,
&ContextSnapshotOptions::default().rewrite_known_segments(),
)
.replace(placeholder, "<CREDENTIAL_PLACEHOLDER>");
.replace(placeholder, "<CREDENTIAL_PLACEHOLDER>")
.replace(credential_env, "<CREDENTIAL_ENV>");
for (pattern, replacement) in [
(r"Chunk ID: [a-z0-9]+", "Chunk ID: <CHUNK_ID>"),
(
@@ -13,9 +13,9 @@ Scenario: A remote executor masks its credential before the command output reach
02:message/user:
<ENVIRONMENT_CONTEXT>
03:message/user:
Print OPENAI_API_KEY from the executor environment.
Print <CREDENTIAL_ENV> from the executor environment.
-- request 2 (turn) --
04:function_call/exec_command:{"cmd":"printf '%s' \\"$OPENAI_API_KEY\\"","environment_id":"remote","login":false,"yield_time_ms":1000}
04:function_call/exec_command:{"cmd":"printf '%s' \\"$<CREDENTIAL_ENV>\\"","environment_id":"remote","login":false,"yield_time_ms":1000}
05:function_call_output:Chunk ID: <CHUNK_ID>
Wall time: <DURATION> seconds
Process exited with code 0
@@ -13,9 +13,9 @@ Scenario: A remote executor masks its credential before the command output reach
02:message/user:
<ENVIRONMENT_CONTEXT>
03:message/user:
Print OPENAI_API_KEY from the executor environment.
Print <CREDENTIAL_ENV> from the executor environment.
-- request 2 (turn) --
04:function_call/exec_command:{"cmd":"printf '%s' \\"$OPENAI_API_KEY\\"","environment_id":"remote","login":true,"yield_time_ms":1000}
04:function_call/exec_command:{"cmd":"printf '%s' \\"$<CREDENTIAL_ENV>\\"","environment_id":"remote","login":true,"yield_time_ms":1000}
05:function_call_output:Chunk ID: <CHUNK_ID>
Wall time: <DURATION> seconds
Process exited with code 0
@@ -76,6 +76,7 @@ const REPLAY_RETAINED_OUTPUT_SEQ: u64 = 800;
enum PushedExecScenario {
Complete,
CredentialMasking,
CredentialMaskingCustomProvidersUnsupported,
CredentialMaskingUnsupportedShell,
CredentialMaskingUnsupported,
OutputAfterExit,
@@ -201,7 +202,9 @@ async fn respond_environment_info(
json!({ "name": "sh", "path": "/bin/sh" })
} else if matches!(
scenario,
PushedExecScenario::CredentialMasking | PushedExecScenario::CredentialMaskingUnsupported
PushedExecScenario::CredentialMasking
| PushedExecScenario::CredentialMaskingCustomProvidersUnsupported
| PushedExecScenario::CredentialMaskingUnsupported
) {
json!({ "name": "bash", "path": "/bin/bash" })
} else {
@@ -215,6 +218,7 @@ async fn respond_environment_info(
| PushedExecScenario::DenyOldLinuxWithRoot(_)
| PushedExecScenario::DenyLinux
| PushedExecScenario::CredentialMasking
| PushedExecScenario::CredentialMaskingCustomProvidersUnsupported
| PushedExecScenario::CredentialMaskingUnsupportedShell
| PushedExecScenario::CredentialMaskingUnsupported => Some("linux"),
_ => None,
@@ -223,6 +227,7 @@ async fn respond_environment_info(
if matches!(
scenario,
PushedExecScenario::CredentialMasking
| PushedExecScenario::CredentialMaskingCustomProvidersUnsupported
| PushedExecScenario::CredentialMaskingUnsupportedShell
) {
capabilities["credentialMasking"] = json!(true);
@@ -566,6 +571,7 @@ async fn serve_exec_with_pushed_events(
}
PushedExecScenario::Complete
| PushedExecScenario::CredentialMasking
| PushedExecScenario::CredentialMaskingCustomProvidersUnsupported
| PushedExecScenario::CredentialMaskingUnsupportedShell
| PushedExecScenario::CredentialMaskingUnsupported
| PushedExecScenario::DenyWin
@@ -722,6 +728,7 @@ async fn serve_exec_with_pushed_events(
}
PushedExecScenario::Complete
| PushedExecScenario::CredentialMasking
| PushedExecScenario::CredentialMaskingCustomProvidersUnsupported
| PushedExecScenario::CredentialMaskingUnsupportedShell
| PushedExecScenario::CredentialMaskingUnsupported
| PushedExecScenario::CodexPath
@@ -859,6 +866,7 @@ async fn serve_exec_with_pushed_events(
#[test_case(PushedExecScenario::ReplayGap, ManagedNetworkScenario::None, false ; "truncated_event_replay")]
#[test_case(PushedExecScenario::Complete, ManagedNetworkScenario::Enabled { policy_callbacks: true }, false ; "managed_network_uses_executor_proxy_launch")]
#[test_case(PushedExecScenario::CredentialMasking, ManagedNetworkScenario::Enabled { policy_callbacks: true }, false ; "credential_masking_launches_on_supported_executor")]
#[test_case(PushedExecScenario::CredentialMaskingCustomProvidersUnsupported, ManagedNetworkScenario::Enabled { policy_callbacks: true }, false ; "configured_credential_provider_warns_and_runs_on_old_executor")]
#[test_case(PushedExecScenario::CredentialMaskingUnsupported, ManagedNetworkScenario::Enabled { policy_callbacks: true }, false ; "credential_masking_warns_and_runs_unsupported_executor")]
#[test_case(PushedExecScenario::CredentialMaskingUnsupportedShell, ManagedNetworkScenario::Enabled { policy_callbacks: true }, false ; "credential_masking_warns_and_runs_unsupported_shell")]
#[test_case(PushedExecScenario::Complete, ManagedNetworkScenario::Enabled { policy_callbacks: false }, false ; "strict_managed_allowlist_omits_policy_callbacks")]
@@ -904,6 +912,7 @@ async fn exec_command_consumes_pushed_remote_process_events(
let credential_masking = matches!(
scenario,
PushedExecScenario::CredentialMasking
| PushedExecScenario::CredentialMaskingCustomProvidersUnsupported
| PushedExecScenario::CredentialMaskingUnsupported
| PushedExecScenario::CredentialMaskingUnsupportedShell
);
@@ -1085,6 +1094,19 @@ enabled = false
builder = builder
.with_cloud_config_bundle(cloud_config_bundle)
.with_pre_build_hook(move |home| {
let provider_config = if matches!(
scenario,
PushedExecScenario::CredentialMaskingCustomProvidersUnsupported
) {
r#"[features.network_proxy.credentials.vendor]
env = ["VENDOR_TOKEN"]
patterns = ["vendor_[a-z]{8}"]
url_prefix_from_env = "VENDOR_BASE_URL"
auth = ["bearer"]
"#
} else {
""
};
fs::write(
home.join("config.toml"),
format!(
@@ -1101,6 +1123,7 @@ allow_local_binding = true
[features]
hooks = true
credential_masking = {credential_masking}
{provider_config}
[hooks]
@@ -1757,6 +1780,10 @@ timeout = 900
}
);
assert_eq!(params["networkProxy"]["proxy"]["mode"], "full");
assert_eq!(
params["networkProxy"]["proxy"]["credentialProviders"],
Value::Null
);
assert_eq!(
params["networkProxy"]["policyDecisionTimeoutMs"].as_u64(),
policy_callbacks.then_some(1_000_000)
@@ -1774,6 +1801,7 @@ timeout = 900
scenario,
PushedExecScenario::CredentialMaskingUnsupportedShell
| PushedExecScenario::CredentialMaskingUnsupported
| PushedExecScenario::CredentialMaskingCustomProvidersUnsupported
) {
let shell = if matches!(
scenario,
@@ -1796,6 +1824,11 @@ timeout = 900
PushedExecScenario::CredentialMaskingUnsupportedShell
) {
insta::assert_snapshot!(warning.message, @"Credential masking is unavailable for shell `sh` on environment `remote`. This command will run with unmasked credentials.");
} else if matches!(
scenario,
PushedExecScenario::CredentialMaskingCustomProvidersUnsupported
) {
insta::assert_snapshot!(warning.message, @"Credential masking is unavailable for this network proxy configuration on environment `remote`. This command will run with unmasked credentials.");
} else {
insta::assert_snapshot!(warning.message, @"Credential masking is unavailable on environment `remote` because its exec-server does not support it. This command will run with unmasked credentials.");
}
@@ -1884,6 +1917,7 @@ timeout = 900
}
PushedExecScenario::Complete
| PushedExecScenario::CredentialMasking
| PushedExecScenario::CredentialMaskingCustomProvidersUnsupported
| PushedExecScenario::CredentialMaskingUnsupportedShell
| PushedExecScenario::CredentialMaskingUnsupported
| PushedExecScenario::DenyWin
+117 -2
View File
@@ -8,6 +8,7 @@ pub use codex_file_system::WalkOutcome;
use codex_file_system::WireFileSystemSandboxContext;
use codex_install_context::InstallContext;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_network_proxy::RemoteCredentialMasking;
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
use codex_protocol::ThreadId;
use codex_protocol::capabilities::SelectedCapabilityRoot;
@@ -151,10 +152,13 @@ fn unknown_executor_version() -> String {
pub struct EnvironmentCapabilities {
/// Whether `exec` accepts instructions for launching an executor-local network proxy.
#[serde(default)]
pub network_proxy_launch: bool,
network_proxy_launch: bool,
/// Whether the selected shell can mask executor environment credentials.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub credential_masking: bool,
credential_masking: bool,
/// Whether credential masking also supports configured providers.
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
credential_masking_custom_providers: bool,
/// Whether capability discovery applies the filesystem sandbox sent with each root.
#[serde(default)]
pub capability_discovery_sandbox: bool,
@@ -187,6 +191,31 @@ pub struct EnvironmentCapabilities {
pub linux_approved_root_write_preserves_restrictions: bool,
}
impl EnvironmentCapabilities {
/// Network proxy features available through this executor.
pub fn network_proxy(&self) -> NetworkProxyCapabilities {
NetworkProxyCapabilities {
launch: self.network_proxy_launch,
credential_masking: if !self.credential_masking {
RemoteCredentialMasking::Disabled
} else if self.credential_masking_custom_providers {
RemoteCredentialMasking::WithConfiguredProviders
} else {
RemoteCredentialMasking::BuiltInOnly
},
}
}
}
/// Features supported by an executor-local network proxy.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct NetworkProxyCapabilities {
/// Whether `exec` accepts instructions for launching an executor-local network proxy.
pub launch: bool,
/// Credential masking support available through the selected shell.
pub credential_masking: RemoteCredentialMasking,
}
/// Status returned by an initialized exec-server connection.
///
/// The response is intentionally small today. New status details can be added
@@ -286,6 +315,7 @@ impl EnvironmentInfo {
capabilities: EnvironmentCapabilities {
network_proxy_launch: true,
credential_masking,
credential_masking_custom_providers: credential_masking,
capability_discovery_sandbox: true,
capability_discovery_v2: true,
environment_config_read: true,
@@ -1260,6 +1290,7 @@ mod tests {
use super::FsOpenParams;
use super::FsReadFileParams;
use super::HttpRequestParams;
use super::NetworkProxyCapabilities;
use super::ProcessId;
use super::ProcessSandboxType;
use super::ShellInfo;
@@ -1270,6 +1301,7 @@ mod tests {
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_network_proxy::NetworkProxyAuditMetadata;
use codex_network_proxy::NetworkProxyConfig;
use codex_network_proxy::RemoteCredentialMasking;
use codex_network_proxy::RemoteNetworkProxyConfig;
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
use codex_protocol::config_types::WindowsSandboxProxySettingsMode;
@@ -1508,6 +1540,7 @@ mod tests {
EnvironmentCapabilities {
network_proxy_launch: true,
credential_masking: false,
credential_masking_custom_providers: false,
capability_discovery_sandbox: true,
capability_discovery_v2: false,
environment_config_read: false,
@@ -1520,6 +1553,88 @@ mod tests {
linux_approved_root_write_preserves_restrictions: false,
}
);
for (input_masking, expected_masking, output_masking) in [
(
serde_json::json!({}),
RemoteCredentialMasking::Disabled,
serde_json::json!({}),
),
(
serde_json::json!({ "credentialMasking": true }),
RemoteCredentialMasking::BuiltInOnly,
serde_json::json!({ "credentialMasking": true }),
),
(
serde_json::json!({
"credentialMasking": true,
"credentialMaskingCustomProviders": false,
}),
RemoteCredentialMasking::BuiltInOnly,
serde_json::json!({ "credentialMasking": true }),
),
(
serde_json::json!({
"credentialMasking": true,
"credentialMaskingCustomProviders": true,
}),
RemoteCredentialMasking::WithConfiguredProviders,
serde_json::json!({
"credentialMasking": true,
"credentialMaskingCustomProviders": true,
}),
),
(
serde_json::json!({ "credentialMaskingCustomProviders": true }),
RemoteCredentialMasking::Disabled,
serde_json::json!({ "credentialMaskingCustomProviders": true }),
),
(
serde_json::json!({
"credentialMasking": false,
"credentialMaskingCustomProviders": true,
}),
RemoteCredentialMasking::Disabled,
serde_json::json!({ "credentialMaskingCustomProviders": true }),
),
] {
for launch in [None, Some(false), Some(true)] {
let mut input = serde_json::json!({
"capabilityDiscoverySandbox": true,
});
if let Some(launch) = launch {
input["networkProxyLaunch"] = launch.into();
}
input
.as_object_mut()
.unwrap()
.extend(input_masking.as_object().unwrap().clone());
let decoded: EnvironmentCapabilities = serde_json::from_value(input).unwrap();
assert_eq!(
decoded.network_proxy(),
NetworkProxyCapabilities {
launch: launch.unwrap_or_default(),
credential_masking: expected_masking,
}
);
let mut expected_wire = serde_json::json!({
"networkProxyLaunch": launch.unwrap_or_default(),
"capabilityDiscoverySandbox": true,
"capabilityDiscoveryV2": false,
"environmentConfigRead": false,
"httpHeaderEnvVars": false,
"sandboxedFileStreaming": false,
"fileWriteStreaming": false,
"shellSnapshotV2": false,
"windowsMxc": false,
});
expected_wire
.as_object_mut()
.unwrap()
.extend(output_masking.as_object().unwrap().clone());
assert_eq!(serde_json::to_value(decoded).unwrap(), expected_wire);
}
}
}
#[test]
+2 -3
View File
@@ -72,9 +72,8 @@ struct PreparedWindowsSandboxRequest {
impl PreparedExecRequest {
#[cfg(unix)]
pub(crate) fn credential_snapshot_proxy(&self, params: &ExecParams) -> Option<&NetworkProxy> {
if !params.network_proxy.as_ref()?.proxy.credential_masking
|| !supports_credential_snapshot(params)
{
let launch = params.network_proxy.as_ref()?;
if !launch.proxy.credential_masking_enabled() || !supports_credential_snapshot(params) {
return None;
}
self.network_proxy_handle.as_ref()?.proxy()
@@ -523,7 +523,9 @@ async fn native_request_handles_remote_proxy_config_for_platform(
config.set_allowed_domains(vec!["allowed.example".to_string()]);
let mut proxy_config = RemoteNetworkProxyConfig::from_effective_config(&config)
.expect("supported remote proxy config");
proxy_config.credential_masking = credential_masking;
if credential_masking {
proxy_config.enable_credential_masking(Default::default());
}
let params = ExecParams {
metadata: Default::default(),
process_id: ProcessId::from("process-remote-proxy"),
@@ -627,7 +629,7 @@ async fn disabled_remote_proxy_config_is_rejected_before_exporting_ports() {
let mut proxy_config =
RemoteNetworkProxyConfig::from_effective_config(&NetworkProxyConfig::default())
.expect("serializable disabled proxy config");
proxy_config.credential_masking = true;
proxy_config.enable_credential_masking(Default::default());
let params = ExecParams {
metadata: Default::default(),
process_id: ProcessId::from("process-disabled-remote-proxy"),
+53 -2
View File
@@ -8,6 +8,8 @@ mod tls_proxy;
#[path = "exec_process/windows_sandbox.rs"]
mod windows_sandbox;
#[cfg(unix)]
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::sync::Arc;
@@ -522,7 +524,9 @@ async fn shell_snapshot_v2_remote_managed_proxy_uses_prepared_execution_context(
const API_KEY: &str = "sk-proj-executor-only-0000000000000000000000000000000000";
const PROFILE_GH_TOKEN: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const REQUEST_GH_TOKEN: &str = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const VENDOR_TOKEN: &str = "vendor_abcdefgh";
const CUSTOM_HOST: &str = "chatgpt.com";
let configured_provider = shell_name == "bash" && shell_args.first() == Some(&"-c");
let shell_path = format!("/bin/{shell_name}");
if shell_name == "zsh" && !std::path::Path::new(&shell_path).exists() {
eprintln!("skipping zsh test: {shell_path} is unavailable");
@@ -553,6 +557,12 @@ async fn shell_snapshot_v2_remote_managed_proxy_uses_prepared_execution_context(
})
.transpose()?;
let mut executor_env = vec![("OPENAI_API_KEY", API_KEY.to_string())];
if configured_provider {
executor_env.extend([
("VENDOR_TOKEN", VENDOR_TOKEN.to_string()),
("VENDOR_BASE_URL", "https://api.openai.com/v1".to_string()),
]);
}
if let Some(upstream) = &upstream {
let ca_path = home.path().join("upstream-ca.pem");
std::fs::write(&ca_path, &upstream.ca_cert_pem)?;
@@ -681,6 +691,11 @@ async fn shell_snapshot_v2_remote_managed_proxy_uses_prepared_execution_context(
.include_only
.extend(["GH_TOKEN", "GH_ALIAS", "AUTH_SIZE_*"].map(str::to_string));
}
if configured_provider {
policy
.include_only
.extend(["VENDOR_TOKEN", "VENDOR_BASE_URL"].map(str::to_string));
}
let mut proxy_settings = NetworkProxyConfig {
enabled: true,
..NetworkProxyConfig::default()
@@ -691,7 +706,23 @@ async fn shell_snapshot_v2_remote_managed_proxy_uses_prepared_execution_context(
}
proxy_settings.set_allowed_domains(allowed_domains);
let mut proxy_config = RemoteNetworkProxyConfig::from_effective_config(&proxy_settings)?;
proxy_config.credential_masking = credential_masking;
let mut providers = BTreeMap::new();
if configured_provider {
providers.insert(
"vendor".to_string(),
codex_network_proxy::CredentialProviderConfig {
env: vec!["VENDOR_TOKEN".to_string()],
patterns: vec!["vendor_[a-z]{8}".to_string()],
url_prefix_from_env: Some("VENDOR_BASE_URL".to_string()),
auth: vec![codex_network_proxy::CredentialAuthMethod::Header],
header: Some("X-Vendor-Token".to_string()),
..Default::default()
},
);
}
if credential_masking {
proxy_config.enable_credential_masking(providers);
}
let mut saved_api_key = None;
for (attempt, &shell_args) in shell_args.iter().enumerate() {
@@ -741,8 +772,13 @@ async fn shell_snapshot_v2_remote_managed_proxy_uses_prepared_execution_context(
command
};
let command = if credential_masking {
let vendor_header = if configured_provider {
"--header \"X-Vendor-Token: $(<\"$HOME/vendor-token\")\""
} else {
""
};
format!(
"{command}; test \"$GH_ENTERPRISE_TOKEN\" = executor-unbound-enterprise-token || exit 45; test \"$AUTH_ALIAS\" = \"Bearer $OPENAI_API_KEY\" || exit 42; printf '|%s' \"$OPENAI_API_KEY\"; curl --silent --show-error --fail --max-time 5 --noproxy '' --config \"$HOME/curl.conf\" \"${{OPENAI_BASE_URL:-https://api.openai.com/v1}}/models\" >/dev/null"
"{command}; test \"$GH_ENTERPRISE_TOKEN\" = executor-unbound-enterprise-token || exit 45; test \"$AUTH_ALIAS\" = \"Bearer $OPENAI_API_KEY\" || exit 42; printf '|%s' \"$OPENAI_API_KEY\"; curl --silent --show-error --fail --max-time 5 --noproxy '' --config \"$HOME/curl.conf\" {vendor_header} \"${{OPENAI_BASE_URL:-https://api.openai.com/v1}}/models\" >/dev/null"
)
} else {
format!("{command}; test \"$OPENAI_API_KEY\" = '{API_KEY}' || exit 46")
@@ -761,6 +797,11 @@ async fn shell_snapshot_v2_remote_managed_proxy_uses_prepared_execution_context(
} else {
command
};
let command = if configured_provider && attempt == 0 {
format!("printf '%s' \"$VENDOR_TOKEN\" > \"$HOME/vendor-token\"; {command}")
} else {
command
};
let started = timeout(
Duration::from_secs(30),
backend.start(ExecParams {
@@ -833,6 +874,16 @@ async fn shell_snapshot_v2_remote_managed_proxy_uses_prepared_execution_context(
format!("header = \"Authorization: Bearer {saved_api_key}\"\n")
);
let request = upstream.next_request()?;
if configured_provider {
let placeholder = std::fs::read_to_string(home.path().join("vendor-token"))?;
assert!(placeholder.starts_with("vendor_"));
assert_eq!(placeholder.len(), VENDOR_TOKEN.len());
assert_ne!(placeholder, VENDOR_TOKEN);
assert_eq!(
request.headers.get("x-vendor-token"),
Some(&vec![VENDOR_TOKEN.to_string()])
);
}
assert_eq!(
(
request.connect_authority.as_str(),
+1
View File
@@ -107,6 +107,7 @@ pub use proxy::is_managed_proxy_env_var;
pub use proxy::proxy_url_env_value;
pub use proxy::strip_managed_proxy_env;
pub use remote_config::RemoteCredentialBrokerCache;
pub use remote_config::RemoteCredentialMasking;
pub use remote_config::RemoteNetworkProxyConfig;
pub use remote_config::RemoteNetworkProxyLaunchConfig;
pub use request_cancellation::NetworkRequestCancellation;
+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!(
+79 -6
View File
@@ -2,8 +2,11 @@ use anyhow::Result;
use anyhow::ensure;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
use std::fmt;
use std::sync::Mutex;
use crate::CredentialProviderConfig;
use crate::NetworkDomainPermissions;
use crate::NetworkMode;
use crate::NetworkProxyAuditMetadata;
@@ -69,6 +72,54 @@ impl RemoteCredentialBrokerCache {
}
}
/// Credential masking supported by an executor or requested for a remote launch.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum RemoteCredentialMasking {
#[default]
Disabled,
/// Mask only when the configuration contains no custom providers.
BuiltInOnly,
WithConfiguredProviders,
}
/// Masking fields shared with independently deployed executors.
#[derive(Clone, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default)]
struct CredentialMaskingConfig {
#[serde(
rename = "credentialMasking",
skip_serializing_if = "std::ops::Not::not"
)]
enabled: bool,
#[serde(
rename = "credentialProviders",
skip_serializing_if = "BTreeMap::is_empty"
)]
providers: BTreeMap<String, CredentialProviderConfig>,
}
impl CredentialMaskingConfig {
fn deserialize_normalized<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Self, D::Error> {
let mut config = Self::deserialize(deserializer)?;
// Discard provider rules sent with masking off, even if we forward this configuration.
if !config.enabled {
config.providers.clear();
}
Ok(config)
}
}
impl fmt::Debug for CredentialMaskingConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CredentialMaskingConfig")
.field("enabled", &self.enabled)
.field("credential_provider_count", &self.providers.len())
.finish()
}
}
/// Executor-local proxy launch inputs transported with one process start.
///
/// Unlike [`crate::ManagedNetworkSandboxContext`], this describes how the executor should create
@@ -115,15 +166,18 @@ impl RemoteNetworkProxyLaunchConfig {
/// Effective network proxy settings that are safe to send to a remote executor.
///
/// Listener addresses are deliberately omitted because the executor chooses its own loopback
/// ports. Masking carries only built-in broker activation; credentials, destination context, custom
/// providers, CA configuration, and MITM hooks stay on their execution host.
/// ports. Masking carries provider rules; credential values and destination context are resolved
/// on the executor. CA configuration and MITM hooks stay on their execution host.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct RemoteNetworkProxyConfig {
pub enabled: bool,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub credential_masking: bool,
#[serde(
flatten,
deserialize_with = "CredentialMaskingConfig::deserialize_normalized"
)]
credential_masking: CredentialMaskingConfig,
pub enable_socks5: bool,
pub enable_socks5_udp: bool,
pub allow_upstream_proxy: bool,
@@ -147,7 +201,7 @@ impl RemoteNetworkProxyConfig {
);
Ok(Self {
enabled: config.enabled,
credential_masking: false,
credential_masking: CredentialMaskingConfig::default(),
enable_socks5: config.enable_socks5,
enable_socks5_udp: config.enable_socks5_udp,
allow_upstream_proxy: config.allow_upstream_proxy,
@@ -161,6 +215,22 @@ impl RemoteNetworkProxyConfig {
})
}
/// Enable masking with the given provider rules, or just the built-ins if empty.
pub fn enable_credential_masking(
&mut self,
providers: BTreeMap<String, CredentialProviderConfig>,
) {
self.credential_masking = CredentialMaskingConfig {
enabled: true,
providers,
};
}
/// Whether the executor should mask credentials for this launch.
pub fn credential_masking_enabled(&self) -> bool {
self.credential_masking.enabled
}
pub(crate) fn into_network_proxy_config(self) -> NetworkProxyConfig {
let mut config = NetworkProxyConfig {
enabled: self.enabled,
@@ -174,7 +244,10 @@ impl RemoteNetworkProxyConfig {
allow_local_binding: Some(self.allow_local_binding),
..NetworkProxyConfig::default()
};
config.set_credential_broker_enabled(self.credential_masking);
if self.credential_masking.enabled {
config.set_credential_broker_enabled(/*enabled*/ true);
config.credential_providers = self.credential_masking.providers;
}
config
}
}
@@ -12,6 +12,7 @@ use crate::NetworkProxyAuditMetadata;
use crate::NetworkProxyConfig;
use crate::NetworkProxyState;
use crate::Platform;
use crate::RemoteCredentialMasking;
use std::sync::Arc;
#[test]
@@ -47,13 +48,14 @@ fn optional_socket_policy_preserves_input_and_resolves_at_remote_boundary() {
#[tokio::test]
async fn round_trip_preserves_supported_effective_settings() {
for (executor_os, socket, credential_masking) in [
(Platform::Linux, "/var/run/example.sock", false),
(Platform::Linux, "/var/run/example.sock", true),
(Platform::Macos, "/var/run/example.sock", false),
(Platform::Macos, "/var/run/example.sock", true),
(Platform::Windows, r"C:\example.sock", false),
(Platform::Unknown, r"C:\example.sock", false),
for (executor_os, socket, credential_masking, configured_provider) in [
(Platform::Linux, "/var/run/example.sock", false, false),
(Platform::Linux, "/var/run/example.sock", true, false),
(Platform::Linux, "/var/run/example.sock", true, true),
(Platform::Macos, "/var/run/example.sock", false, false),
(Platform::Macos, "/var/run/example.sock", true, true),
(Platform::Windows, r"C:\example.sock", false, false),
(Platform::Unknown, r"C:\example.sock", false, false),
] {
let mut config = NetworkProxyConfig {
enabled: true,
@@ -65,14 +67,67 @@ async fn round_trip_preserves_supported_effective_settings() {
allow_local_binding: Some(true),
..NetworkProxyConfig::default()
};
config.set_allowed_domains(vec!["example.com".into()]);
// Domain permissions serialize as a sorted map.
config.set_denied_domains(vec!["blocked.example.com".into()]);
config.set_allowed_domains(vec!["example.com".into()]);
config.set_allow_unix_sockets(vec![socket.into()]);
let mut remote = RemoteNetworkProxyConfig::from_effective_config(&config)
.expect("supported remote config");
remote.credential_masking = credential_masking;
config.set_credential_broker_enabled(credential_masking);
if configured_provider {
config.credential_providers.insert(
"vendor".to_string(),
crate::CredentialProviderConfig {
env: vec!["VENDOR_TOKEN".to_string()],
patterns: vec!["vendor_[a-z]{8}".to_string()],
url_prefix_from_env: Some("VENDOR_BASE_URL".to_string()),
auth: vec![crate::CredentialAuthMethod::Bearer],
..Default::default()
},
);
}
if credential_masking {
remote.enable_credential_masking(config.credential_providers.clone());
}
let wire = serde_json::to_value(&remote).expect("serialize remote config");
assert_eq!(
wire.get("credentialMasking"),
credential_masking.then_some(&serde_json::Value::Bool(true))
);
assert_eq!(
wire.get("credentialProviders"),
configured_provider
.then(|| serde_json::to_value(&config.credential_providers).unwrap())
.as_ref()
);
assert_eq!(
serde_json::from_value::<RemoteNetworkProxyConfig>(wire.clone())
.expect("deserialize remote config"),
remote
);
let mut disabled_wire = wire;
disabled_wire
.as_object_mut()
.unwrap()
.remove("credentialMasking");
let mut expected_wire = disabled_wire.clone();
expected_wire
.as_object_mut()
.unwrap()
.remove("credentialProviders");
// Ignore provider rules when the peer omits or explicitly disables masking.
let mut explicitly_disabled_wire = disabled_wire.clone();
explicitly_disabled_wire["credentialMasking"] = false.into();
for wire in [disabled_wire, explicitly_disabled_wire] {
let disabled: RemoteNetworkProxyConfig =
serde_json::from_value(wire).expect("disabled masking ignores provider rules");
assert!(!disabled.credential_masking_enabled());
assert_eq!(serde_json::to_value(disabled).unwrap(), expected_wire);
}
let diagnostic = format!("{remote:?}");
assert!(!diagnostic.contains("vendor_[a-z]{8}"));
assert!(!diagnostic.contains("VENDOR_BASE_URL"));
let round_trip = remote.clone().into_network_proxy_config();
assert_eq!(round_trip, config);
@@ -102,7 +157,14 @@ async fn round_trip_preserves_supported_effective_settings() {
);
assert_eq!(
proxy
.remote_launch_config(DefaultFalse, credential_masking)
.remote_launch_config(
DefaultFalse,
if credential_masking {
RemoteCredentialMasking::WithConfiguredProviders
} else {
RemoteCredentialMasking::Disabled
},
)
.await
.unwrap()
.proxy
@@ -242,7 +304,7 @@ async fn remote_credential_mappings_follow_the_environment_and_broker_config() {
..NetworkProxyConfig::default()
})
.unwrap();
proxy.credential_masking = true;
proxy.enable_credential_masking(Default::default());
NetworkProxyState::from_remote_launch_config(
RemoteNetworkProxyLaunchConfig::new(proxy)
.for_execution(environment_id.to_string(), "execution".to_string())
@@ -371,7 +433,7 @@ async fn local_binding_is_resolved_for_each_executor() -> anyhow::Result<()> {
.await?;
assert_eq!(
proxy
.remote_launch_config(policy, /*credential_masking*/ false)
.remote_launch_config(policy, RemoteCredentialMasking::Disabled)
.await
.ok()
.map(|launch| launch.proxy.allow_local_binding),