Prepare managed network sandbox context (#29456)

## Why

Managed network configures commands to use local HTTP and SOCKS proxies.
For commands delegated to the exec server, the proxy environment and the
sandbox policy were prepared separately. On macOS, that meant a command
could receive `HTTPS_PROXY=http://127.0.0.1:43123` while Seatbelt still
denied access to port `43123`.

## What changed

`NetworkProxy` now prepares the command environment and sandbox context
together from the same runtime snapshot:

```text
Prepared managed network
├── command environment: HTTPS_PROXY=http://127.0.0.1:43123
└── sandbox context: allow outbound to 127.0.0.1:43123
```

That context travels with remote exec requests. The exec server
preserves the managed proxy and CA environment, and macOS Seatbelt
allows only the prepared loopback proxy ports without enabling broad
network access or local binding.

The protocol field is optional and the existing enforcement flag remains
in place, preserving compatibility with callers that do not send the new
context.
This commit is contained in:
jif
2026-06-23 20:07:09 +01:00
committed by GitHub
Unverified
parent 8d80b0176a
commit e476fc16ce
30 changed files with 472 additions and 78 deletions
+1
View File
@@ -2886,6 +2886,7 @@ dependencies = [
"codex-app-server-protocol",
"codex-client",
"codex-file-system",
"codex-network-proxy",
"codex-protocol",
"codex-sandboxing",
"codex-shell-command",
+1
View File
@@ -383,6 +383,7 @@ async fn run_command_under_sandbox(
network_sandbox_policy,
sandbox_policy_cwd: sandbox_policy_cwd.as_path(),
enforce_managed_network,
managed_network: None,
environment_id: None,
network: network.as_ref(),
extra_allow_unix_sockets: allow_unix_sockets,
+2
View File
@@ -377,6 +377,7 @@ pub fn build_exec_request(
args: args.to_vec(),
cwd,
env,
managed_network: None,
additional_permissions: None,
};
let options = ExecOptions {
@@ -459,6 +460,7 @@ pub(crate) async fn execute_exec_request(
arg0,
exec_server_sandbox: _,
exec_server_enforce_managed_network: _,
exec_server_managed_network: _,
} = exec_request;
// TODO(anp): Keep PathUri through the local process launch boundary.
+4
View File
@@ -15,6 +15,7 @@ use crate::exec::execute_exec_request;
use crate::spawn::CODEX_SANDBOX_ENV_VAR;
use crate::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
use codex_file_system::FileSystemSandboxContext;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_network_proxy::NetworkProxy;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::exec_output::ExecToolCallOutput;
@@ -63,6 +64,7 @@ pub struct ExecRequest {
pub arg0: Option<String>,
pub(crate) exec_server_sandbox: Option<FileSystemSandboxContext>,
pub(crate) exec_server_enforce_managed_network: bool,
pub(crate) exec_server_managed_network: Option<ManagedNetworkSandboxContext>,
}
impl ExecRequest {
@@ -107,6 +109,7 @@ impl ExecRequest {
arg0,
exec_server_sandbox: None,
exec_server_enforce_managed_network: false,
exec_server_managed_network: None,
}
}
@@ -165,6 +168,7 @@ impl ExecRequest {
arg0,
exec_server_sandbox: None,
exec_server_enforce_managed_network: false,
exec_server_managed_network: None,
}
}
}
+1
View File
@@ -226,6 +226,7 @@ pub(crate) async fn execute_user_shell_command(
arg0: None,
exec_server_sandbox: None,
exec_server_enforce_managed_network: false,
exec_server_managed_network: None,
};
let stdout_stream = Some(StdoutStream {
+16 -15
View File
@@ -50,6 +50,7 @@ pub(crate) fn build_sandbox_command(
args: args.to_vec(),
cwd,
env: env.clone(),
managed_network: None,
additional_permissions,
})
}
@@ -67,26 +68,26 @@ pub(crate) fn exec_env_for_sandbox_permissions(
env
}
pub(crate) fn strip_managed_proxy_env(env: &mut HashMap<String, String>) {
for key in PROXY_ENV_KEYS {
env.remove(*key);
pub(crate) fn is_managed_proxy_env_var(key: &str, value: &str) -> bool {
if PROXY_ENV_KEYS.contains(&key) {
return true;
}
for key in CUSTOM_CA_ENV_KEYS {
if env
.get(key)
.is_some_and(|value| is_managed_mitm_ca_trust_bundle_path(value))
{
env.remove(key);
}
if CUSTOM_CA_ENV_KEYS.contains(&key) {
return is_managed_mitm_ca_trust_bundle_path(value);
}
// Only macOS injects a Codex-owned SSH wrapper for the managed SOCKS proxy.
#[cfg(target_os = "macos")]
if env
.get(PROXY_GIT_SSH_COMMAND_ENV_KEY)
.is_some_and(|command| command.starts_with(CODEX_PROXY_GIT_SSH_COMMAND_MARKER))
{
env.remove(PROXY_GIT_SSH_COMMAND_ENV_KEY);
key == PROXY_GIT_SSH_COMMAND_ENV_KEY
&& value.starts_with(CODEX_PROXY_GIT_SSH_COMMAND_MARKER)
}
#[cfg(not(target_os = "macos"))]
{
false
}
}
pub(crate) fn strip_managed_proxy_env(env: &mut HashMap<String, String>) {
env.retain(|key, value| !is_managed_proxy_env_var(key, value));
}
/// Prepends `path_entry` to `PATH`, removing duplicate and empty existing
@@ -168,6 +168,7 @@ pub(super) async fn try_run_zsh_fork(
arg0,
exec_server_sandbox: _,
exec_server_enforce_managed_network: _,
exec_server_managed_network: _,
} = sandbox_exec_request;
let ParsedShellCommand { script, login, .. } = extract_shell_script(&command)?;
let effective_timeout = Duration::from_millis(
@@ -902,6 +903,7 @@ impl CoreShellCommandExecutor {
arg0: self.arg0.clone(),
exec_server_sandbox: None,
exec_server_enforce_managed_network: false,
exec_server_managed_network: None,
},
/*stdout_stream*/ None,
after_spawn,
@@ -1010,6 +1012,7 @@ impl CoreShellCommandExecutor {
args: args.to_vec(),
cwd,
env,
managed_network: None,
additional_permissions,
};
let options = ExecOptions {
@@ -41,6 +41,7 @@ use crate::unified_exec::NoopSpawnLifecycle;
use crate::unified_exec::UnifiedExecError;
use crate::unified_exec::UnifiedExecProcess;
use crate::unified_exec::UnifiedExecProcessManager;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_network_proxy::NetworkProxy;
use codex_protocol::error::CodexErr;
use codex_protocol::error::SandboxErr;
@@ -117,6 +118,7 @@ fn build_unified_exec_sandbox_command(
command: &[String],
cwd: &PathUri,
env: &HashMap<String, String>,
managed_network: Option<ManagedNetworkSandboxContext>,
additional_permissions: Option<AdditionalPermissionProfile>,
) -> Result<SandboxCommand, ToolError> {
let (program, args) = command
@@ -127,6 +129,7 @@ fn build_unified_exec_sandbox_command(
args: args.to_vec(),
cwd: cwd.clone(),
env: env.clone(),
managed_network,
additional_permissions,
})
}
@@ -321,22 +324,28 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
req.network.as_ref(),
launch_sandbox_permissions,
);
let mut env = exec_env_for_sandbox_permissions(&req.env, launch_sandbox_permissions);
if let Some(network) = managed_network {
network
.apply_to_env_for_optional_environment(
&mut env,
Some(&req.turn_environment.environment_id),
)
.map_err(|err| {
ToolError::Codex(CodexErr::Io(io::Error::other(format!(
"failed to prepare network proxy for environment `{}`: {err}",
req.turn_environment.environment_id
))))
})?;
}
let env = exec_env_for_sandbox_permissions(&req.env, launch_sandbox_permissions);
let (env, managed_network_context) = match managed_network {
Some(network) => {
let prepared = network
.prepare_for_optional_environment(
env,
Some(&req.turn_environment.environment_id),
)
.map_err(|err| {
ToolError::Codex(CodexErr::Io(io::Error::other(format!(
"failed to prepare network proxy for environment `{}`: {err}",
req.turn_environment.environment_id
))))
})?;
(prepared.env, Some(prepared.sandbox_context))
}
None => (env, None),
};
let explicit_env_overrides = req.explicit_env_overrides.clone();
#[cfg(unix)]
let mut env = env;
#[cfg(unix)]
let runtime_path_prepends = {
let mut runtime_path_prepends = RuntimePathPrepends::default();
if !environment_is_remote {
@@ -385,6 +394,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
&command,
&req.cwd,
&env,
managed_network_context.clone(),
req.additional_permissions.clone(),
)
.map_err(|error| match error {
@@ -450,6 +460,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
&command,
&req.cwd,
&env,
managed_network_context,
req.additional_permissions.clone(),
)
.map_err(|error| match error {
+2
View File
@@ -466,6 +466,7 @@ impl<'a> SandboxAttempt<'a> {
network: Option<&NetworkProxy>,
environment_id: Option<&str>,
) -> Result<crate::sandboxing::ExecRequest, CodexErr> {
let managed_network = command.managed_network.clone();
let exec_server_permissions = effective_permission_profile(
self.exec_server_permissions,
command.additional_permissions.as_ref(),
@@ -492,6 +493,7 @@ impl<'a> SandboxAttempt<'a> {
options,
self.workspace_roots.to_vec(),
);
exec_request.exec_server_managed_network = managed_network;
if self.sandbox_requested {
exec_request.exec_server_sandbox = Some(FileSystemSandboxContext {
permissions: exec_server_permissions.into(),
+25 -7
View File
@@ -1,6 +1,7 @@
use super::*;
use crate::sandboxing::SandboxPermissions;
use crate::tools::hook_names::HookToolName;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
@@ -212,7 +213,7 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
.clone()
.materialize_project_roots_with_workspace_roots(std::slice::from_ref(&cwd));
let manager = SandboxManager::new();
let attempt = SandboxAttempt {
let mut attempt = SandboxAttempt {
sandbox: SandboxType::None,
sandbox_requested: true,
permissions: &permissions,
@@ -227,20 +228,24 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
windows_sandbox_private_desktop: false,
network_denial_cancellation_token: None,
};
let command = SandboxCommand {
let managed_network = ManagedNetworkSandboxContext {
loopback_ports: vec![43123],
allow_local_binding: false,
};
let command = || SandboxCommand {
program: "/bin/bash".into(),
args: vec!["-lc".to_string(), "pwd".to_string()],
cwd: cwd_uri.clone(),
env: HashMap::new(),
managed_network: Some(managed_network.clone()),
additional_permissions: None,
};
let options = crate::sandboxing::ExecOptions {
let options = || crate::sandboxing::ExecOptions {
expiration: crate::exec::ExecExpiration::DefaultTimeout,
capture_policy: crate::exec::ExecCapturePolicy::ShellTool,
};
let request = attempt
.env_for_exec_server(command, options, /*network*/ None, Some("remote"))
.env_for_exec_server(command(), options(), /*network*/ None, Some("remote"))
.expect("prepare remote exec request");
assert_eq!(
@@ -256,8 +261,8 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
assert_eq!(
request.exec_server_sandbox,
Some(codex_exec_server::FileSystemSandboxContext {
permissions: exec_server_permissions.into(),
cwd: Some(cwd_uri),
permissions: exec_server_permissions.clone().into(),
cwd: Some(cwd_uri.clone()),
workspace_roots: Vec::new(),
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
@@ -265,4 +270,17 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
})
);
assert!(request.exec_server_enforce_managed_network);
assert_eq!(
request.exec_server_managed_network,
Some(managed_network.clone())
);
attempt.sandbox_requested = false;
let request = attempt
.env_for_exec_server(command(), options(), /*network*/ None, Some("remote"))
.expect("prepare unsandboxed remote exec request");
assert_eq!(request.exec_server_sandbox, None);
assert!(!request.exec_server_enforce_managed_network);
assert_eq!(request.exec_server_managed_network, Some(managed_network));
}
@@ -26,6 +26,7 @@ use crate::tools::events::ToolEventStage;
use crate::tools::network_approval::DeferredNetworkApproval;
use crate::tools::network_approval::finish_deferred_network_approval;
use crate::tools::orchestrator::ToolOrchestrator;
use crate::tools::runtimes::is_managed_proxy_env_var;
use crate::tools::runtimes::unified_exec::UnifiedExecRequest as UnifiedExecToolRequest;
use crate::tools::runtimes::unified_exec::UnifiedExecRuntime;
use crate::tools::sandboxing::SandboxAttempt;
@@ -143,10 +144,16 @@ fn exec_server_env_for_request(
HashMap<String, String>,
) {
if let Some(exec_server_env_config) = &request.exec_server_env_config {
(
Some(exec_server_env_config.policy.clone()),
env_overlay_for_exec_server(&request.env, &exec_server_env_config.local_policy_env),
)
let mut env =
env_overlay_for_exec_server(&request.env, &exec_server_env_config.local_policy_env);
if request.exec_server_managed_network.is_some() {
for (key, value) in &request.env {
if is_managed_proxy_env_var(key, value) {
env.insert(key.clone(), value.clone());
}
}
}
(Some(exec_server_env_config.policy.clone()), env)
} else {
(None, request.env.clone())
}
@@ -175,6 +182,7 @@ fn exec_server_params_for_request(
arg0: request.arg0.clone(),
sandbox: request.exec_server_sandbox.clone(),
enforce_managed_network: request.exec_server_enforce_managed_network,
managed_network: request.exec_server_managed_network.clone(),
}
}
@@ -1,5 +1,6 @@
use super::*;
use crate::unified_exec::clamp_yield_time;
use codex_network_proxy::ManagedNetworkSandboxContext;
use pretty_assertions::assert_eq;
use tokio::time::Duration;
use tokio::time::Instant;
@@ -76,6 +77,10 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() {
codex_protocol::permissions::FileSystemSandboxPolicy::unrestricted();
let network_sandbox_policy = codex_protocol::permissions::NetworkSandboxPolicy::Restricted;
let permission_profile = codex_protocol::models::PermissionProfile::Disabled;
let managed_network = ManagedNetworkSandboxContext {
loopback_ports: vec![43123],
allow_local_binding: false,
};
let mut request = ExecRequest {
command: vec!["bash".to_string(), "-lc".to_string(), "true".to_string()],
cwd: cwd.clone().into(),
@@ -83,6 +88,15 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() {
("HOME".to_string(), "/client-home".to_string()),
("PATH".to_string(), "/sandbox-path".to_string()),
("CODEX_THREAD_ID".to_string(), "thread-1".to_string()),
(
"HTTP_PROXY".to_string(),
"http://127.0.0.1:43123".to_string(),
),
("CODEX_NETWORK_PROXY_ACTIVE".to_string(), "1".to_string()),
(
"SSL_CERT_FILE".to_string(),
"/client/custom-ca.pem".to_string(),
),
]),
exec_server_env_config: Some(ExecServerEnvConfig {
policy: codex_exec_server::ExecEnvPolicy {
@@ -95,6 +109,15 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() {
local_policy_env: HashMap::from([
("HOME".to_string(), "/client-home".to_string()),
("PATH".to_string(), "/client-path".to_string()),
(
"HTTP_PROXY".to_string(),
"http://127.0.0.1:43123".to_string(),
),
("CODEX_NETWORK_PROXY_ACTIVE".to_string(), "1".to_string()),
(
"SSL_CERT_FILE".to_string(),
"/client/custom-ca.pem".to_string(),
),
]),
}),
network: None,
@@ -112,7 +135,8 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() {
windows_sandbox_filesystem_overrides: None,
arg0: None,
exec_server_sandbox: None,
exec_server_enforce_managed_network: false,
exec_server_enforce_managed_network: true,
exec_server_managed_network: Some(managed_network.clone()),
};
let params =
@@ -120,12 +144,19 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() {
assert_eq!(params.process_id.as_str(), "123");
assert_eq!(params.cwd, request.cwd);
assert!(params.enforce_managed_network);
assert_eq!(params.managed_network, Some(managed_network));
assert!(params.env_policy.is_some());
assert_eq!(
params.env,
HashMap::from([
("PATH".to_string(), "/sandbox-path".to_string()),
("CODEX_THREAD_ID".to_string(), "thread-1".to_string()),
(
"HTTP_PROXY".to_string(),
"http://127.0.0.1:43123".to_string(),
),
("CODEX_NETWORK_PROXY_ACTIVE".to_string(), "1".to_string(),),
])
);
request.exec_server_sandbox = Some(
+1
View File
@@ -20,6 +20,7 @@ codex-app-server-protocol = { workspace = true }
codex-api = { workspace = true }
codex-client = { workspace = true }
codex-file-system = { workspace = true }
codex-network-proxy = { workspace = true }
codex-protocol = { workspace = true }
codex-sandboxing = { workspace = true }
codex-shell-command = { workspace = true }
+2
View File
@@ -1162,6 +1162,7 @@ mod tests {
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await
.expect("start process");
@@ -1201,6 +1202,7 @@ mod tests {
arg0: None,
sandbox: Some(sandbox),
enforce_managed_network: false,
managed_network: None,
})
.await;
let Err(err) = result else {
+1
View File
@@ -115,6 +115,7 @@ impl FileSystemSandboxRunner {
args: vec![CODEX_FS_HELPER_ARG1.to_string()],
cwd: cwd.uri.clone(),
env: self.helper_env.clone(),
managed_network: None,
additional_permissions: None,
};
let native_workspace_roots = sandbox_context
@@ -965,6 +965,7 @@ mod tests {
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
}
}
@@ -1,6 +1,8 @@
use std::collections::HashMap;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_network_proxy::CUSTOM_CA_ENV_KEYS;
use codex_network_proxy::is_managed_mitm_ca_trust_bundle_path;
use codex_protocol::models::PermissionProfile;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxDirectSpawnTransformRequest;
@@ -8,6 +10,7 @@ use codex_sandboxing::SandboxManager;
use codex_sandboxing::SandboxTransformRequest;
use codex_sandboxing::SandboxType;
use codex_sandboxing::SandboxablePreference;
use codex_sandboxing::with_managed_mitm_ca_readable_root;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
@@ -59,6 +62,20 @@ pub(crate) fn prepare_exec_request(
native_workspace_roots.as_slice()
};
let permissions = permissions.materialize_project_roots_with_workspace_roots(workspace_roots);
let managed_mitm_ca_trust_bundle_path = params.managed_network.as_ref().and_then(|_| {
CUSTOM_CA_ENV_KEYS.iter().find_map(|key| {
let path = env.get(*key)?;
if !is_managed_mitm_ca_trust_bundle_path(path) {
return None;
}
AbsolutePathBuf::from_absolute_path(path).ok()
})
});
let permissions = with_managed_mitm_ca_readable_root(
permissions,
managed_mitm_ca_trust_bundle_path.as_ref(),
native_sandbox_policy_cwd.as_path(),
);
let (file_system_policy, network_policy) = permissions.to_runtime_permissions();
let sandbox_manager = SandboxManager::new();
let sandbox = sandbox_manager.select_initial(
@@ -98,6 +115,7 @@ pub(crate) fn prepare_exec_request(
args: args.to_vec(),
cwd: params.cwd.clone(),
env,
managed_network: params.managed_network.clone(),
additional_permissions: None,
},
permissions: &permissions,
@@ -1,5 +1,7 @@
use std::collections::HashMap;
#[cfg(target_os = "macos")]
use codex_network_proxy::ManagedNetworkSandboxContext;
#[cfg(unix)]
use codex_protocol::models::PermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -44,6 +46,7 @@ fn sandbox_request_wraps_native_argv_on_executor() {
arg0: None,
sandbox: Some(sandbox),
enforce_managed_network: false,
managed_network: None,
};
let prepared = prepare_exec_request(&params, HashMap::new(), Some(&runtime_paths))
@@ -78,6 +81,49 @@ fn sandbox_request_wraps_native_argv_on_executor() {
);
}
#[cfg(target_os = "macos")]
#[test]
fn sandbox_request_allows_prepared_managed_proxy_port() {
let cwd: AbsolutePathBuf = std::env::current_dir()
.expect("current directory")
.try_into()
.expect("absolute cwd");
let cwd_uri = PathUri::from_abs_path(&cwd);
let self_exe = std::env::current_exe().expect("current executable");
let runtime_paths =
ExecServerRuntimePaths::new(self_exe.clone(), Some(self_exe)).expect("runtime paths");
let sandbox = FileSystemSandboxContext::from_permission_profile_with_cwd(
PermissionProfile::workspace_write(),
cwd_uri.clone(),
);
let params = ExecParams {
process_id: ProcessId::from("process-managed-network"),
argv: vec!["/usr/bin/true".to_string()],
cwd: cwd_uri,
env_policy: None,
env: HashMap::new(),
tty: false,
pipe_stdin: false,
arg0: None,
sandbox: Some(sandbox),
enforce_managed_network: true,
managed_network: Some(ManagedNetworkSandboxContext {
loopback_ports: vec![43123],
allow_local_binding: false,
}),
};
let prepared = prepare_exec_request(&params, HashMap::new(), Some(&runtime_paths))
.expect("prepare managed-network sandbox request");
let policy = prepared
.command
.windows(2)
.find_map(|args| (args[0] == "-p").then_some(args[1].as_str()))
.expect("Seatbelt policy argument");
assert!(policy.contains("(allow network-outbound (remote ip \"localhost:43123\"))"));
}
#[test]
fn native_request_preserves_native_launch_fields() {
let cwd: AbsolutePathBuf = std::env::current_dir()
@@ -97,6 +143,7 @@ fn native_request_preserves_native_launch_fields() {
arg0: Some("custom-arg0".to_string()),
sandbox: None,
enforce_managed_network: false,
managed_network: None,
};
let prepared = prepare_exec_request(&params, env.clone(), /*runtime_paths*/ None)
+55
View File
@@ -2,6 +2,7 @@ use std::collections::HashMap;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use codex_file_system::FileSystemSandboxContext;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
use codex_utils_path_uri::PathUri;
use serde::Deserialize;
@@ -109,6 +110,12 @@ pub struct ExecParams {
/// Whether the eventual executor-side sandbox must enforce managed networking.
#[serde(default)]
pub enforce_managed_network: bool,
/// Optional details for enforcing managed networking without a live proxy object.
///
/// When `enforce_managed_network` is true and these details are absent, the executor must
/// continue to fail closed. This preserves compatibility with older clients.
#[serde(default)]
pub managed_network: Option<ManagedNetworkSandboxContext>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -504,12 +511,60 @@ mod base64_bytes {
#[cfg(test)]
mod tests {
use super::ExecParams;
use super::FsReadFileParams;
use super::HttpRequestParams;
use super::ProcessId;
use codex_file_system::FileSystemSandboxContext;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_protocol::models::PermissionProfile;
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
#[test]
fn exec_params_managed_network_context_round_trips_and_defaults_for_legacy_peers() {
let cwd =
PathUri::from_host_native_path(std::env::current_dir().expect("current directory"))
.expect("cwd URI");
let params = ExecParams {
process_id: ProcessId::from("managed-network"),
argv: vec!["true".to_string()],
cwd,
env_policy: None,
env: HashMap::new(),
tty: false,
pipe_stdin: false,
arg0: None,
sandbox: None,
enforce_managed_network: true,
managed_network: Some(ManagedNetworkSandboxContext {
loopback_ports: vec![43123, 48081],
allow_local_binding: false,
}),
};
let mut serialized = serde_json::to_value(&params).expect("serialize exec params");
assert_eq!(
serialized["managedNetwork"],
serde_json::json!({
"loopbackPorts": [43123, 48081],
"allowLocalBinding": false,
})
);
let round_trip: ExecParams =
serde_json::from_value(serialized.clone()).expect("deserialize exec params");
assert_eq!(round_trip, params);
serialized
.as_object_mut()
.expect("exec params object")
.remove("managedNetwork");
let legacy: ExecParams =
serde_json::from_value(serialized).expect("deserialize legacy exec params");
assert!(legacy.enforce_managed_network);
assert_eq!(legacy.managed_network, None);
}
#[test]
fn filesystem_protocol_accepts_legacy_absolute_paths_and_serializes_path_uris() {
@@ -36,6 +36,7 @@ fn exec_params_with_argv(process_id: &str, argv: Vec<String>) -> ExecParams {
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
}
}
@@ -441,6 +441,7 @@ mod tests {
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
}
}
@@ -83,6 +83,7 @@ async fn assert_exec_process_starts_and_exits(use_remote: bool) -> Result<()> {
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
assert_eq!(session.process.process_id().as_str(), "proc-1");
@@ -226,6 +227,7 @@ async fn assert_exec_process_streams_output(use_remote: bool) -> Result<()> {
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
assert_eq!(session.process.process_id().as_str(), process_id);
@@ -259,6 +261,7 @@ async fn assert_exec_process_pushes_events(use_remote: bool) -> Result<()> {
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
assert_eq!(session.process.process_id().as_str(), process_id);
@@ -308,6 +311,7 @@ async fn assert_exec_process_replays_events_after_close(use_remote: bool) -> Res
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
assert_eq!(session.process.process_id().as_str(), process_id);
@@ -358,6 +362,7 @@ async fn assert_exec_process_retains_output_after_exit_until_streams_close(
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
assert_eq!(session.process.process_id().as_str(), process_id);
@@ -433,6 +438,7 @@ async fn assert_exec_process_write_then_read(use_remote: bool) -> Result<()> {
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
assert_eq!(session.process.process_id().as_str(), process_id);
@@ -472,6 +478,7 @@ async fn assert_exec_process_write_then_read_without_tty(use_remote: bool) -> Re
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
assert_eq!(session.process.process_id().as_str(), process_id);
@@ -507,6 +514,7 @@ async fn assert_exec_process_rejects_write_without_pipe_stdin(use_remote: bool)
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
assert_eq!(session.process.process_id().as_str(), process_id);
@@ -543,6 +551,7 @@ async fn assert_exec_process_signal_interrupts_process(use_remote: bool) -> Resu
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
assert_eq!(session.process.process_id().as_str(), process_id);
@@ -598,6 +607,7 @@ async fn assert_exec_process_signal_reports_unsupported_on_windows(use_remote: b
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
@@ -640,6 +650,7 @@ async fn assert_exec_process_preserves_queued_events_before_subscribe(
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
@@ -700,6 +711,7 @@ async fn remote_exec_process_recovers_after_transport_disconnect() -> Result<()>
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
+1
View File
@@ -152,6 +152,7 @@ async fn remote_environment_routes_encrypted_exec_server_rpc() -> Result<()> {
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await?;
assert_eq!(
+2
View File
@@ -47,6 +47,7 @@ pub use proxy::Args;
#[cfg(target_os = "macos")]
pub use proxy::CODEX_PROXY_GIT_SSH_COMMAND_MARKER;
pub use proxy::DEFAULT_NO_PROXY_VALUE;
pub use proxy::ManagedNetworkSandboxContext;
pub use proxy::NO_PROXY_ENV_KEYS;
pub use proxy::NetworkProxy;
pub use proxy::NetworkProxyBuilder;
@@ -56,6 +57,7 @@ pub use proxy::PROXY_ENV_KEYS;
#[cfg(target_os = "macos")]
pub use proxy::PROXY_GIT_SSH_COMMAND_ENV_KEY;
pub use proxy::PROXY_URL_ENV_KEYS;
pub use proxy::PreparedManagedNetwork;
pub use proxy::has_proxy_url_env_vars;
pub use proxy::proxy_url_env_value;
pub use runtime::BlockedRequest;
+111 -12
View File
@@ -10,6 +10,8 @@ use anyhow::Context;
use anyhow::Result;
use clap::Parser;
use codex_utils_absolute_path::AbsolutePathBuf;
use serde::Deserialize;
use serde::Serialize;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::net::TcpListener as StdTcpListener;
@@ -328,6 +330,27 @@ struct EnvironmentProxyAddrs {
socks_addr: SocketAddr,
}
/// Portable managed-network facts needed by an operating-system sandbox.
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManagedNetworkSandboxContext {
/// Loopback proxy ports that sandboxed commands may connect to.
#[serde(default)]
pub loopback_ports: Vec<u16>,
/// Whether the command may bind local sockets and exchange loopback traffic.
#[serde(default)]
pub allow_local_binding: bool,
}
/// Environment-specific managed-network settings prepared for one command launch.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PreparedManagedNetwork {
/// Complete command environment with managed proxy variables applied.
pub env: HashMap<String, String>,
/// Matching portable sandbox inputs for the command environment.
pub sandbox_context: ManagedNetworkSandboxContext,
}
struct EnvironmentProxy {
addrs: EnvironmentProxyAddrs,
http_task: JoinHandle<Result<()>>,
@@ -653,22 +676,49 @@ impl NetworkProxy {
})
}
fn apply_to_env_for_addrs(
fn prepare_for_addrs(
&self,
env: &mut HashMap<String, String>,
mut env: HashMap<String, String>,
addrs: EnvironmentProxyAddrs,
) {
) -> PreparedManagedNetwork {
let runtime_settings = self.runtime_settings();
// Enforce proxying for child processes. Proxy endpoint values are always rewritten;
// managed MITM CA vars preserve child-scoped overrides after proxy startup.
apply_proxy_env_overrides(
env,
&mut env,
addrs.http_addr,
addrs.socks_addr,
self.socks_enabled,
runtime_settings.allow_local_binding,
runtime_settings.mitm_ca_trust_bundle.as_ref(),
);
let mut loopback_ports = [
Some(addrs.http_addr),
self.socks_enabled.then_some(addrs.socks_addr),
]
.into_iter()
.flatten()
.filter(|addr| addr.ip().is_loopback())
.map(|addr| addr.port())
.collect::<Vec<_>>();
loopback_ports.sort_unstable();
loopback_ports.dedup();
PreparedManagedNetwork {
env,
sandbox_context: ManagedNetworkSandboxContext {
loopback_ports,
allow_local_binding: runtime_settings.allow_local_binding,
},
}
}
fn apply_to_env_for_addrs(
&self,
env: &mut HashMap<String, String>,
addrs: EnvironmentProxyAddrs,
) {
let prepared = self.prepare_for_addrs(std::mem::take(env), addrs);
*env = prepared.env;
}
pub fn apply_to_env(&self, env: &mut HashMap<String, String>) {
@@ -705,6 +755,23 @@ impl NetworkProxy {
}
}
/// Applies the environment-specific proxy settings and returns the matching portable sandbox
/// projection from the same runtime configuration snapshot.
pub fn prepare_for_optional_environment(
&self,
env: HashMap<String, String>,
environment_id: Option<&str>,
) -> Result<PreparedManagedNetwork> {
let addrs = match environment_id {
Some(environment_id) => self.environment_proxy_addrs(environment_id)?,
None => EnvironmentProxyAddrs {
http_addr: self.http_addr,
socks_addr: self.socks_addr,
},
};
Ok(self.prepare_for_addrs(env, addrs))
}
fn environment_proxy_addrs(&self, environment_id: &str) -> Result<EnvironmentProxyAddrs> {
let mut proxies = self
.environment_proxies
@@ -1071,27 +1138,59 @@ mod tests {
}
#[tokio::test]
async fn apply_to_env_for_environment_uses_distinct_proxy_ports() -> Result<()> {
async fn prepare_for_environment_keeps_env_and_sandbox_ports_in_sync() -> Result<()> {
let state = Arc::new(network_proxy_state_for_policy(
NetworkProxySettings::default(),
));
let proxy = NetworkProxy::builder().state(state).build().await?;
let handle = proxy.run().await?;
let mut local_env = HashMap::new();
proxy.apply_to_env_for_environment(&mut local_env, "local")?;
let mut remote_env = HashMap::new();
proxy.apply_to_env_for_environment(&mut remote_env, "remote")?;
let base_env = HashMap::from([("PRESERVED".to_string(), "value".to_string())]);
let local = proxy.prepare_for_optional_environment(base_env.clone(), Some("local"))?;
let remote = proxy.prepare_for_optional_environment(HashMap::new(), Some("remote"))?;
assert_ne!(local_env.get("HTTP_PROXY"), remote_env.get("HTTP_PROXY"));
assert_eq!(
local.env.get("PRESERVED").map(String::as_str),
Some("value")
);
assert_ne!(local.env.get("HTTP_PROXY"), remote.env.get("HTTP_PROXY"));
assert_ne!(
local_env.get("HTTP_PROXY"),
local.env.get("HTTP_PROXY"),
Some(&format!("http://{}", proxy.http_addr()))
);
assert_ne!(
remote_env.get("HTTP_PROXY"),
remote.env.get("HTTP_PROXY"),
Some(&format!("http://{}", proxy.http_addr()))
);
for prepared in [&local, &remote] {
let http_port = prepared
.env
.get("HTTP_PROXY")
.and_then(|value| value.strip_prefix("http://"))
.and_then(|value| value.parse::<SocketAddr>().ok())
.map(|addr| addr.port())
.expect("managed HTTP proxy address");
let socks_port = prepared
.env
.get("ALL_PROXY")
.and_then(|value| value.strip_prefix("socks5h://"))
.and_then(|value| value.parse::<SocketAddr>().ok())
.map(|addr| addr.port())
.expect("managed SOCKS proxy address");
let mut expected_ports = vec![http_port, socks_port];
expected_ports.sort_unstable();
expected_ports.dedup();
assert_eq!(
prepared.sandbox_context,
ManagedNetworkSandboxContext {
loopback_ports: expected_ports,
allow_local_binding: false,
}
);
}
let mut legacy_env = base_env;
proxy.apply_to_env_for_environment(&mut legacy_env, "local")?;
assert_eq!(legacy_env, local.env);
handle.shutdown().await?;
Ok(())
@@ -509,6 +509,7 @@ impl ExecutorStdioServerLauncher {
arg0: None,
sandbox: None,
enforce_managed_network: false,
managed_network: None,
})
.await
.map_err(io::Error::other)?;
+5
View File
@@ -13,6 +13,7 @@ use crate::resolve_windows_elevated_filesystem_overrides;
use crate::resolve_windows_restricted_token_filesystem_overrides;
#[cfg(target_os = "windows")]
use crate::windows_sandbox_uses_elevated_backend;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_network_proxy::NetworkProxy;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::AdditionalPermissionProfile;
@@ -99,6 +100,7 @@ pub struct SandboxCommand {
pub args: Vec<String>,
pub cwd: PathUri,
pub env: HashMap<String, String>,
pub managed_network: Option<ManagedNetworkSandboxContext>,
pub additional_permissions: Option<AdditionalPermissionProfile>,
}
@@ -332,6 +334,8 @@ impl SandboxManager {
windows_sandbox_level,
windows_sandbox_private_desktop,
} = request;
#[cfg(target_os = "macos")]
let managed_network = command.managed_network.as_ref();
let additional_permissions = command.additional_permissions.take();
let managed_mitm_ca_trust_bundle_path =
network.and_then(NetworkProxy::managed_mitm_ca_trust_bundle_path);
@@ -364,6 +368,7 @@ impl SandboxManager {
network_sandbox_policy: pending.effective_network_policy,
sandbox_policy_cwd: pending.native_sandbox_policy_cwd.as_path(),
enforce_managed_network,
managed_network,
environment_id,
network,
extra_allow_unix_sockets: &[],
+5
View File
@@ -92,6 +92,7 @@ fn unsandboxed_transform_preserves_foreign_cwd_and_unrestricted_file_system_poli
args: Vec::new(),
cwd: cwd_uri.clone(),
env: HashMap::new(),
managed_network: None,
additional_permissions: None,
},
permissions: &permissions,
@@ -139,6 +140,7 @@ fn transform_additional_permissions_enable_network_for_external_sandbox() {
args: Vec::new(),
cwd: cwd_uri.clone(),
env: HashMap::new(),
managed_network: None,
additional_permissions: Some(AdditionalPermissionProfile {
network: Some(NetworkPermissions {
enabled: Some(true),
@@ -211,6 +213,7 @@ fn transform_additional_permissions_preserves_denied_entries() {
args: Vec::new(),
cwd: cwd_uri.clone(),
env: HashMap::new(),
managed_network: None,
additional_permissions: Some(AdditionalPermissionProfile {
file_system: Some(FileSystemPermissions::from_read_write_roots(
/*read*/ None,
@@ -314,6 +317,7 @@ fn transform_linux_seccomp_request(
args: Vec::new(),
cwd: cwd_uri.clone(),
env: HashMap::new(),
managed_network: None,
additional_permissions: None,
},
permissions: &permissions,
@@ -504,6 +508,7 @@ fn transform_for_direct_spawn_windows_materializes_inner_helper() {
"Path".to_string(),
r"C:\Windows\System32".to_string(),
)]),
managed_network: None,
additional_permissions: None,
},
permissions: &permissions,
+47 -25
View File
@@ -1,3 +1,4 @@
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_network_proxy::NetworkProxy;
use codex_network_proxy::PROXY_URL_ENV_KEYS;
use codex_network_proxy::has_proxy_url_env_vars;
@@ -103,6 +104,7 @@ struct UnixSocketPathParam {
}
fn proxy_policy_inputs(
managed_network: Option<&ManagedNetworkSandboxContext>,
network: Option<&NetworkProxy>,
environment_id: Option<&str>,
extra_allow_unix_sockets: &[AbsolutePathBuf],
@@ -112,33 +114,47 @@ fn proxy_policy_inputs(
.filter_map(|socket_path| normalize_path_for_sandbox(socket_path.as_path()))
.collect::<Vec<_>>();
let unix_domain_socket_policy = match network {
Some(network) if network.dangerously_allow_all_unix_sockets() => {
UnixDomainSocketPolicy::AllowAll
}
Some(network) => {
let mut allowed = network
.allow_unix_sockets()
.iter()
.filter_map(|socket_path| {
match normalize_path_for_sandbox(Path::new(socket_path)) {
Some(path) => Some(path),
None => {
warn!(
"ignoring network.allow_unix_sockets entry because it could not be normalized: {socket_path}"
);
None
}
}
})
.collect::<Vec<_>>();
allowed.extend(extra_allowed);
UnixDomainSocketPolicy::Restricted { allowed }
}
None => UnixDomainSocketPolicy::Restricted {
allowed: extra_allowed,
},
};
if let Some(managed_network) = managed_network {
return Ok(ProxyPolicyInputs {
ports: managed_network.loopback_ports.clone(),
has_proxy_config: true,
allow_local_binding: managed_network.allow_local_binding,
unix_domain_socket_policy,
});
}
match network {
Some(network) => {
let mut env = HashMap::new();
network
.apply_to_env_for_optional_environment(&mut env, environment_id)
.map_err(|err| err.to_string())?;
let unix_domain_socket_policy = if network.dangerously_allow_all_unix_sockets() {
UnixDomainSocketPolicy::AllowAll
} else {
let mut allowed = network
.allow_unix_sockets()
.iter()
.filter_map(|socket_path| {
match normalize_path_for_sandbox(Path::new(socket_path)) {
Some(path) => Some(path),
None => {
warn!(
"ignoring network.allow_unix_sockets entry because it could not be normalized: {socket_path}"
);
None
}
}
})
.collect::<Vec<_>>();
allowed.extend(extra_allowed);
UnixDomainSocketPolicy::Restricted { allowed }
};
Ok(ProxyPolicyInputs {
ports: proxy_loopback_ports_from_env(&env),
has_proxy_config: has_proxy_url_env_vars(&env),
@@ -147,9 +163,7 @@ fn proxy_policy_inputs(
})
}
None => Ok(ProxyPolicyInputs {
unix_domain_socket_policy: UnixDomainSocketPolicy::Restricted {
allowed: extra_allowed,
},
unix_domain_socket_policy,
..Default::default()
}),
}
@@ -586,6 +600,7 @@ fn create_seatbelt_command_args_for_legacy_policy(
network_sandbox_policy: NetworkSandboxPolicy::from(sandbox_policy),
sandbox_policy_cwd,
enforce_managed_network,
managed_network: None,
environment_id: None,
network,
extra_allow_unix_sockets: &[],
@@ -599,6 +614,7 @@ pub struct CreateSeatbeltCommandArgsParams<'a> {
pub network_sandbox_policy: NetworkSandboxPolicy,
pub sandbox_policy_cwd: &'a Path,
pub enforce_managed_network: bool,
pub managed_network: Option<&'a ManagedNetworkSandboxContext>,
pub environment_id: Option<&'a str>,
pub network: Option<&'a NetworkProxy>,
pub extra_allow_unix_sockets: &'a [AbsolutePathBuf],
@@ -613,6 +629,7 @@ pub fn create_seatbelt_command_args(
network_sandbox_policy,
sandbox_policy_cwd,
enforce_managed_network,
managed_network,
environment_id,
network,
extra_allow_unix_sockets,
@@ -709,7 +726,12 @@ pub fn create_seatbelt_command_args(
}
};
let proxy = proxy_policy_inputs(network, environment_id, extra_allow_unix_sockets)?;
let proxy = proxy_policy_inputs(
managed_network,
network,
environment_id,
extra_allow_unix_sockets,
)?;
let network_policy =
dynamic_network_policy_for_network(network_sandbox_policy, enforce_managed_network, &proxy);
+37
View File
@@ -14,6 +14,7 @@ use super::unix_socket_policy;
use codex_network_proxy::ConfigReloader;
use codex_network_proxy::ConfigReloaderFuture;
use codex_network_proxy::ConfigState;
use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_network_proxy::NetworkMode;
use codex_network_proxy::NetworkProxy;
use codex_network_proxy::NetworkProxyConfig;
@@ -205,6 +206,7 @@ fn explicit_unreadable_paths_are_excluded_from_full_disk_read_and_write_access()
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: Path::new("/"),
enforce_managed_network: false,
managed_network: None,
environment_id: None,
network: None,
extra_allow_unix_sockets: &[],
@@ -258,6 +260,37 @@ fn explicit_unreadable_paths_are_excluded_from_full_disk_read_and_write_access()
);
}
#[test]
fn prepared_managed_network_context_allows_only_its_proxy_ports() {
let file_system_policy = FileSystemSandboxPolicy::from_legacy_sandbox_policy_for_cwd(
&SandboxPolicy::new_read_only_policy(),
Path::new("/"),
);
let managed_network = ManagedNetworkSandboxContext {
loopback_ports: vec![43123, 48081],
allow_local_binding: false,
};
let args = create_seatbelt_command_args(CreateSeatbeltCommandArgsParams {
command: vec!["/bin/true".to_string()],
file_system_sandbox_policy: &file_system_policy,
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: Path::new("/"),
enforce_managed_network: true,
managed_network: Some(&managed_network),
environment_id: None,
network: None,
extra_allow_unix_sockets: &[],
})
.unwrap();
let policy = seatbelt_policy_arg(&args);
assert!(policy.contains("(allow network-outbound (remote ip \"localhost:43123\"))"));
assert!(policy.contains("(allow network-outbound (remote ip \"localhost:48081\"))"));
assert!(!policy.contains("(allow network-outbound (remote ip \"localhost:9999\"))"));
assert!(!policy.contains("(allow network-bind (local ip \"*:*\"))"));
assert!(!policy.contains("(allow network-outbound)\n"));
}
#[test]
fn explicit_unreadable_paths_are_excluded_from_readable_roots() {
let root = absolute_path("/tmp/codex-readable");
@@ -279,6 +312,7 @@ fn explicit_unreadable_paths_are_excluded_from_readable_roots() {
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: Path::new("/"),
enforce_managed_network: false,
managed_network: None,
environment_id: None,
network: None,
extra_allow_unix_sockets: &[],
@@ -585,6 +619,7 @@ fn create_seatbelt_args_allowlists_explicit_unix_socket_paths_without_proxy() {
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: cwd.path(),
enforce_managed_network: false,
managed_network: None,
environment_id: None,
network: None,
extra_allow_unix_sockets: &extra_allow_unix_sockets,
@@ -645,6 +680,7 @@ async fn create_seatbelt_args_merges_proxy_and_explicit_unix_socket_paths() -> a
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
sandbox_policy_cwd: cwd.path(),
enforce_managed_network: false,
managed_network: None,
environment_id: None,
network: Some(&network_proxy),
extra_allow_unix_sockets: &extra_allow_unix_sockets,
@@ -688,6 +724,7 @@ fn create_seatbelt_args_preserves_full_network_with_explicit_unix_socket_paths()
network_sandbox_policy: NetworkSandboxPolicy::Enabled,
sandbox_policy_cwd: cwd.path(),
enforce_managed_network: false,
managed_network: None,
environment_id: None,
network: None,
extra_allow_unix_sockets: &extra_allow_unix_sockets,