Allow exec-server to proxy permitted private IPs upstream (#48568)

## Why

Private IP destinations always bypassed inherited upstream proxies, preventing their use for private networks reachable through an upstream VPN proxy.

## What changed

- Add `codex exec-server --proxy-private-ips-via-upstream`, also configurable with `CODEX_EXEC_SERVER_PROXY_PRIVATE_IPS_VIA_UPSTREAM=true`. The setting defaults to disabled.
- Allow permitted RFC 1918, carrier-grade NAT, and IPv6 unique-local destinations to use an applicable upstream proxy. Loopback and link-local destinations retain direct routing, and destination access policy still applies.
- Keep connections direct when no valid upstream proxy applies or `allow_upstream_proxy=false`. Errors after selecting an upstream proxy do not trigger a direct retry.
- Rename `ExecServerRuntimePaths` to `ExecServerRuntimeOptions` and carry the routing setting from executor startup into the managed network proxy.

## Testing

Add routing coverage for private address ranges, special-use addresses, and public targets with the option enabled and disabled. Verify that HTTP and CONNECT requests still enforce destination allowlists and denylists, and update the CLI help snapshot.

GitOrigin-RevId: b7c9cc7da0e0f545694a6521b74c9b36b7b92769
This commit is contained in:
open-matt
2026-09-26 23:17:41 +00:00
committed by copyberry
parent 228ae3da8d
commit b8d5e3f12e
65 changed files with 359 additions and 213 deletions
+2 -2
View File
@@ -52,7 +52,7 @@ use codex_config::NoopThreadConfigLoader;
use codex_core::config::Config;
pub use codex_core::otel_init::build_provider as build_otel_provider;
pub use codex_exec_server::EnvironmentManager;
pub use codex_exec_server::ExecServerRuntimePaths;
pub use codex_exec_server::ExecServerRuntimeOptions;
use codex_feedback::CodexFeedback;
use codex_protocol::protocol::SessionSource;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -2081,7 +2081,7 @@ mod tests {
EnvironmentManager::create_for_tests(
Some("ws://127.0.0.1:8765".to_string()),
Some(
ExecServerRuntimePaths::new(
ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
+2 -2
View File
@@ -5,7 +5,7 @@
//! also handles the helper argv modes because exec-server re-execs
//! `codex_self_exe` for sandboxed filesystem and process requests.
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use std::ffi::OsStr;
@@ -29,7 +29,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let current_exe = std::env::current_exe()?;
let codex_linux_sandbox_exe =
std::env::var_os(CODEX_LINUX_SANDBOX_EXE_ENV_VAR).map(std::path::PathBuf::from);
let runtime_paths = ExecServerRuntimePaths::new(current_exe, codex_linux_sandbox_exe)?;
let runtime_paths = ExecServerRuntimeOptions::new(current_exe, codex_linux_sandbox_exe)?;
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?
+2 -2
View File
@@ -61,7 +61,7 @@ use codex_core::ExecPolicyError;
use codex_core::check_execpolicy_for_warnings;
use codex_core::config::find_codex_home;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_features::Feature;
use codex_feedback::CodexFeedback;
use codex_protocol::protocol::SessionSource;
@@ -518,7 +518,7 @@ pub async fn run_main_with_transport_options(
)
})?;
let codex_home = find_codex_home()?;
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
let local_runtime_paths = ExecServerRuntimeOptions::from_optional_paths(
arg0_paths.codex_self_exe.clone(),
arg0_paths.codex_linux_sandbox_exe.clone(),
)?;
+5 -1
View File
@@ -22,7 +22,11 @@ fn exec_server_help_documents_remote_options() {
let command = MultitoolCli::command()
.term_width(80)
.mut_subcommand("exec-server", |command| {
command.mut_arg("exit_on_stdin_close", |arg| arg.hide_env_values(true))
command
.mut_arg("exit_on_stdin_close", |arg| arg.hide_env_values(true))
.mut_arg("proxy_private_ips_via_upstream", |arg| {
arg.hide_env_values(true)
})
});
let help = command
.try_get_matches_from(["codex", "exec-server", "--help"])
+14 -3
View File
@@ -14,7 +14,7 @@ use codex_core::config::ConfigLoadOptions;
use codex_core::config::bootstrap_auth_config;
use codex_core::config::find_codex_home;
use codex_core::config::load_config_toml_with_layer_stack;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use codex_login::AuthManager;
@@ -48,6 +48,16 @@ pub(super) struct ExecServerCommand {
#[arg(long, value_name = "MODE", default_value = "isolate", global = true)]
linux_sandbox_pid_namespace: codex_sandboxing::LinuxSandboxPidNamespace,
/// Allow permitted private IP destinations to use the configured upstream proxy.
/// If no valid upstream proxy applies to the request protocol, connect directly.
/// Loopback stays local. This flag does not require upstream routing.
#[arg(
long,
env = "CODEX_EXEC_SERVER_PROXY_PRIVATE_IPS_VIA_UPSTREAM",
global = true
)]
proxy_private_ips_via_upstream: bool,
/// Maximum number of requests to process concurrently on each connection.
#[arg(
long = "concurrent-requests",
@@ -164,11 +174,12 @@ impl ExecServerCommand {
.codex_self_exe
.clone()
.ok_or_else(|| anyhow::anyhow!("Codex executable path is not configured"))?;
let runtime_paths = ExecServerRuntimePaths::new(
let runtime_paths = ExecServerRuntimeOptions::new(
codex_self_exe,
arg0_paths.codex_linux_sandbox_exe.clone(),
)?
.with_linux_sandbox_pid_namespace(self.linux_sandbox_pid_namespace);
.with_linux_sandbox_pid_namespace(self.linux_sandbox_pid_namespace)
.with_proxy_private_ips_via_upstream(self.proxy_private_ips_via_upstream);
if let Some(base_url) = self.remote.take() {
let environment_id = self.environment_id.take().ok_or_else(|| {
anyhow::anyhow!("--environment-id is required when --remote is set")
@@ -1,6 +1,5 @@
---
source: cli/src/exec_server_args_tests.rs
assertion_line: 36
expression: help_text
---
[EXPERIMENTAL] Run the standalone exec-server service
@@ -40,6 +39,14 @@ Options:
[default: isolate]
--proxy-private-ips-via-upstream
Allow permitted private IP destinations to use the configured upstream
proxy. If no valid upstream proxy applies to the request protocol,
connect directly. Loopback stays local. This flag does not require
upstream routing
[env: CODEX_EXEC_SERVER_PROXY_PRIVATE_IPS_VIA_UPSTREAM]
--concurrent-requests <COUNT>
Maximum number of requests to process concurrently on each connection
+1 -1
View File
@@ -84,7 +84,7 @@ pub use codex_exec_server::EnvironmentRegistryHarnessKeyValidationResponse;
pub use codex_exec_server::EnvironmentRegistryRegistrationRequest;
pub use codex_exec_server::EnvironmentRegistryRegistrationResponse;
pub use codex_exec_server::ExecServerError;
pub use codex_exec_server::ExecServerRuntimePaths;
pub use codex_exec_server::ExecServerRuntimeOptions;
pub use codex_exec_server::NoiseChannelIdentity;
pub use codex_exec_server::NoiseChannelPublicKey;
pub use codex_exec_server::NoiseRendezvousConnectBundle;
+2 -2
View File
@@ -13,7 +13,7 @@ use codex_connectors::ConnectorDirectoryCacheKey;
use codex_connectors::apps_config_from_layer_stack;
use codex_connectors::connector_runtime_context_key;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_tools::DiscoverableTool;
use tokio_util::sync::CancellationToken;
use tracing::instrument;
@@ -165,7 +165,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options_and_status(
// TODO: Wire callers that already own an EnvironmentManager into
// list_accessible_connectors_from_mcp_tools_with_environment_manager instead
// of constructing a temporary manager here.
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
let local_runtime_paths = ExecServerRuntimeOptions::from_optional_paths(
config.codex_self_exe.clone(),
config.codex_linux_sandbox_exe.clone(),
)?;
+3 -3
View File
@@ -1183,7 +1183,7 @@ mod tests {
use crate::config::PermissionProfileSnapshot;
use codex_exec_server::Environment;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
use codex_exec_server::REMOTE_ENVIRONMENT_ID;
use codex_exec_server_test_support::environment_manager_without_environments;
@@ -1246,8 +1246,8 @@ mod tests {
turn_environments
}
fn test_runtime_paths() -> ExecServerRuntimePaths {
ExecServerRuntimePaths::new(
fn test_runtime_paths() -> ExecServerRuntimeOptions {
ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
+2 -2
View File
@@ -1,7 +1,7 @@
use std::sync::Arc;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_extension_api::ExtensionRegistry;
use codex_extension_api::UserInstructionsProvider;
use codex_login::AuthManager;
@@ -37,7 +37,7 @@ pub async fn build_prompt_input(
.await
.map_err(|err| CodexErr::Fatal(err.to_string()))?;
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
let local_runtime_paths = ExecServerRuntimeOptions::from_optional_paths(
config.codex_self_exe.clone(),
config.codex_linux_sandbox_exe.clone(),
)?;
+1 -1
View File
@@ -737,7 +737,7 @@ impl TestCodexBuilder {
);
#[cfg(not(target_os = "linux"))]
let codex_linux_sandbox_exe = None;
let local_runtime_paths = codex_exec_server::ExecServerRuntimePaths::new(
let local_runtime_paths = codex_exec_server::ExecServerRuntimeOptions::new(
std::env::current_exe()?,
codex_linux_sandbox_exe,
)?;
+2 -2
View File
@@ -7,7 +7,7 @@ use codex_core::StartThreadOptions;
use codex_core::TurnInputRequest;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_features::Feature;
use codex_protocol::capabilities::CapabilityRootLocation;
use codex_protocol::capabilities::SelectedCapabilityRoot;
@@ -309,7 +309,7 @@ async fn executor_stop_hook_rejects_mismatched_environment() -> Result<()> {
let executor_address = listener.local_addr()?;
let executor_url = format!("ws://{executor_address}");
drop(listener);
let runtime_paths = ExecServerRuntimePaths::new(
let runtime_paths = ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?;
+2 -2
View File
@@ -22,7 +22,7 @@ use codex_exec_server::CopyOptions;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::EnvironmentReadyInfo;
use codex_exec_server::ExecServerError;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_exec_server::FileSystemSandboxContext;
use codex_exec_server::LOCAL_ENVIRONMENT_ID;
use codex_exec_server::NoiseChannelPublicKey;
@@ -2404,7 +2404,7 @@ async fn ready_before_selection_resolves_resumed_thread_capability_root_after_wa
.mount(&registry)
.await;
let runtime_paths = ExecServerRuntimePaths::new(
let runtime_paths = ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?;
@@ -2929,7 +2929,7 @@ async fn production_turn_keeps_rebalanced_catalogs_stable_after_compaction_and_r
let executor_address = listener.local_addr()?;
let executor_url = format!("ws://{executor_address}");
drop(listener);
let runtime_paths = codex_exec_server::ExecServerRuntimePaths::new(
let runtime_paths = codex_exec_server::ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?;
+14 -4
View File
@@ -478,19 +478,29 @@ The crate exports:
- `RemoteExecServerConnectArgs`
- protocol request/response structs for process and filesystem RPCs
- `DEFAULT_LISTEN_URL` and `ExecServerListenUrlParseError`
- `ExecServerRuntimePaths`
- `ExecServerRuntimeOptions`
- `run_main()` for embedding the websocket server
- `RemoteEnvironmentConfig` and `run_remote_environment()` for embedding remote
registration mode
Callers must pass `ExecServerRuntimePaths` and an explicitly configured
Callers must pass `ExecServerRuntimeOptions` and an explicitly configured
`HttpClientFactory` to `run_main()`. The top-level `codex exec-server` command
builds these paths from the `codex` arg0 dispatch state and resolves its HTTP
client factory from the effective Codex configuration.
builds the runtime options from the `codex` arg0 dispatch state and startup flags,
and resolves its HTTP client factory from the effective Codex configuration.
`RemoteEnvironmentConfig::new(...)` also takes the auth provider and HTTP client
factory that remote registration mode should use; the CLI builds the auth
provider from Codex auth state before starting remote mode.
`--proxy-private-ips-via-upstream` (or
`CODEX_EXEC_SERVER_PROXY_PRIVATE_IPS_VIA_UPSTREAM=true`) allows permitted private IP
destinations to use an inherited upstream proxy. If no valid proxy configuration
applies to the request protocol, routing falls back to a direct connection. This
includes unset, malformed, or unsupported proxy settings. Setting
`allow_upstream_proxy=false` also keeps routing direct; loopback always stays local.
Destination policy still applies to every route. Enforcing mandatory upstream routing
would require a separate fail-closed mode. A connection failure after selecting an
upstream proxy is returned as an error, without retrying directly.
## Example session
Initialize:
@@ -13,7 +13,7 @@ use tokio::task::JoinSet;
use tokio_util::task::AbortOnDropHandle;
use super::*;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::NoiseChannelIdentity;
use crate::ProcessId;
use crate::noise_relay::stream_handler::NoiseOutboundMessage;
@@ -148,7 +148,7 @@ impl Executor {
validator: Validator,
identity: NoiseChannelIdentity,
) -> Result<Self> {
let processor = ConnectionProcessor::new(ExecServerRuntimePaths::new(
let processor = ConnectionProcessor::new(ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?);
+18 -18
View File
@@ -18,7 +18,7 @@ use crate::CapabilityRootsDiscoverResponse;
use crate::EnvironmentConfigReadParams;
use crate::EnvironmentConfigReadResponse;
use crate::ExecServerError;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ExecutorFileSystem;
use crate::HttpClient;
use crate::NoiseChannelIdentity;
@@ -90,7 +90,7 @@ pub struct EnvironmentManager {
default_environment: Option<String>,
pub(super) environments: RwLock<HashMap<String, Arc<Environment>>>,
local_environment: Option<Arc<Environment>>,
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
http_client_factory: HttpClientFactory,
}
@@ -161,7 +161,7 @@ impl EnvironmentManager {
/// Builds a test-only manager from a raw exec-server URL value.
pub async fn create_for_tests(
exec_server_url: Option<String>,
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
) -> Self {
let provider = DefaultEnvironmentProvider::new(exec_server_url);
match Self::from_snapshot(
@@ -195,7 +195,7 @@ impl EnvironmentManager {
/// Builds a manager from `CODEX_HOME` with an explicit outbound HTTP policy.
pub async fn from_codex_home(
codex_home: impl AsRef<std::path::Path>,
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
http_client_factory: HttpClientFactory,
) -> Result<Self, ExecServerError> {
Self::prepare_from_codex_home(codex_home)
@@ -216,7 +216,7 @@ impl EnvironmentManager {
/// Builds a manager from environment variables with an explicit outbound HTTP policy.
pub async fn from_env(
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
http_client_factory: HttpClientFactory,
) -> Result<Self, ExecServerError> {
Self::prepare_from_env()
@@ -226,7 +226,7 @@ impl EnvironmentManager {
pub(crate) fn from_noise_environment_config(
config: NoiseRendezvousEnvironmentConfig,
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
http_client_factory: HttpClientFactory,
) -> Result<Self, ExecServerError> {
let connect_provider = config.into_connect_provider(http_client_factory.clone())?;
@@ -254,7 +254,7 @@ impl EnvironmentManager {
/// allowing tests to select the local environment explicitly.
pub async fn create_for_tests_with_local(
exec_server_url: Option<String>,
local_runtime_paths: ExecServerRuntimePaths,
local_runtime_paths: ExecServerRuntimeOptions,
) -> Self {
let mut snapshot = DefaultEnvironmentProvider::new(exec_server_url).snapshot_inner();
snapshot.include_local = true;
@@ -271,7 +271,7 @@ impl EnvironmentManager {
pub(crate) fn from_snapshot(
snapshot: EnvironmentProviderSnapshot,
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
http_client_factory: HttpClientFactory,
) -> Result<Self, ExecServerError> {
let EnvironmentProviderSnapshot {
@@ -675,7 +675,7 @@ pub struct Environment {
exec_backend: Arc<dyn ExecBackend>,
filesystem: Arc<dyn ExecutorFileSystem>,
http_client: Arc<dyn HttpClient>,
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
}
impl Environment {
@@ -707,7 +707,7 @@ impl Environment {
/// Builds an environment using the caller's effective outbound HTTP policy.
pub fn create(
exec_server_url: Option<String>,
local_runtime_paths: ExecServerRuntimePaths,
local_runtime_paths: ExecServerRuntimeOptions,
http_client_factory: HttpClientFactory,
) -> Result<Self, ExecServerError> {
Self::create_inner(
@@ -730,7 +730,7 @@ impl Environment {
/// local runtime paths used when creating local filesystem helpers.
fn create_inner(
exec_server_url: Option<String>,
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
http_client_factory: HttpClientFactory,
) -> Result<Self, ExecServerError> {
let (exec_server_url, disabled) = normalize_exec_server_url(exec_server_url);
@@ -757,7 +757,7 @@ impl Environment {
}
pub(crate) fn local(
local_runtime_paths: ExecServerRuntimePaths,
local_runtime_paths: ExecServerRuntimeOptions,
http_client_factory: HttpClientFactory,
) -> Self {
Self {
@@ -778,7 +778,7 @@ impl Environment {
pub(crate) fn remote_with_transport(
remote_transport: ExecServerTransportParams,
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
http_client_factory: HttpClientFactory,
) -> Self {
let client = LazyRemoteExecServerClient::new(remote_transport, http_client_factory);
@@ -787,7 +787,7 @@ impl Environment {
pub(crate) fn remote_with_client(
client: LazyRemoteExecServerClient,
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
) -> Self {
let exec_backend: Arc<dyn ExecBackend> = Arc::new(RemoteProcess::new(client.clone()));
let filesystem: Arc<dyn ExecutorFileSystem> =
@@ -890,7 +890,7 @@ impl Environment {
.map(LazyRemoteExecServerClient::subscribe_connection_state)
}
pub fn local_runtime_paths(&self) -> Option<&ExecServerRuntimePaths> {
pub fn local_runtime_paths(&self) -> Option<&ExecServerRuntimeOptions> {
self.local_runtime_paths.as_ref()
}
@@ -1171,7 +1171,7 @@ mod tests {
use super::LOCAL_ENVIRONMENT_ID;
use super::REMOTE_ENVIRONMENT_ID;
use super::noise_environment_config_from_values;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ProcessId;
use crate::client_api::DEFAULT_REMOTE_EXEC_SERVER_CONNECT_TIMEOUT;
use crate::client_api::ExecServerTransportParams;
@@ -1196,8 +1196,8 @@ mod tests {
)
}
fn test_runtime_paths() -> ExecServerRuntimePaths {
ExecServerRuntimePaths::new(
fn test_runtime_paths() -> ExecServerRuntimeOptions {
ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
@@ -2,7 +2,7 @@ use codex_http_client::HttpClientFactory;
use crate::EnvironmentManager;
use crate::ExecServerError;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::environment_provider::EnvironmentDefault;
use crate::environment_provider::EnvironmentProviderSnapshot;
use crate::remote::NoiseRendezvousEnvironmentConfig;
@@ -41,7 +41,7 @@ impl PreparedEnvironmentManager {
/// Builds the manager and starts remote connections using the supplied policy.
pub fn build(
self,
local_runtime_paths: Option<ExecServerRuntimePaths>,
local_runtime_paths: Option<ExecServerRuntimeOptions>,
http_client_factory: HttpClientFactory,
) -> Result<EnvironmentManager, ExecServerError> {
match self.source {
+11 -11
View File
@@ -37,7 +37,7 @@ use tokio::io::AsyncBufReadExt;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::FileSystemSandboxContext;
use crate::fs_helper::CODEX_FS_HELPER_ARG1;
use crate::fs_helper::FsHelperPayload;
@@ -69,12 +69,12 @@ struct SandboxCwd {
#[derive(Clone, Debug)]
pub(crate) struct FileSystemSandboxRunner {
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
helper_env: HashMap<String, String>,
}
impl FileSystemSandboxRunner {
pub(crate) fn new(runtime_paths: ExecServerRuntimePaths) -> Self {
pub(crate) fn new(runtime_paths: ExecServerRuntimeOptions) -> Self {
Self {
runtime_paths,
helper_env: helper_env(),
@@ -229,7 +229,7 @@ fn native_workspace_root(root: &PathUri) -> Result<AbsolutePathBuf, JSONRPCError
})
}
fn helper_read_roots(runtime_paths: &ExecServerRuntimePaths) -> Vec<AbsolutePathBuf> {
fn helper_read_roots(runtime_paths: &ExecServerRuntimeOptions) -> Vec<AbsolutePathBuf> {
let mut roots = vec![runtime_paths.codex_self_exe.clone()];
if let Some(path) = &runtime_paths.codex_linux_sandbox_exe
&& !roots.contains(path)
@@ -581,7 +581,7 @@ mod tests {
use codex_utils_path_uri::PathUri;
use pretty_assertions::assert_eq;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use super::FileSystemSandboxRunner;
use super::SandboxCwd;
@@ -622,7 +622,7 @@ mod tests {
fn helper_permissions_preserve_existing_writes() {
let codex_self_exe = std::env::current_exe().expect("current exe");
let runtime_paths =
ExecServerRuntimePaths::new(codex_self_exe, /*codex_linux_sandbox_exe*/ None)
ExecServerRuntimeOptions::new(codex_self_exe, /*codex_linux_sandbox_exe*/ None)
.expect("runtime paths");
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
.expect("absolute cwd");
@@ -743,7 +743,7 @@ mod tests {
let path = path.to_string_lossy().into_owned();
let codex_self_exe = std::env::current_exe().expect("current exe");
let runtime_paths =
ExecServerRuntimePaths::new(codex_self_exe.clone(), Some(codex_self_exe))
ExecServerRuntimeOptions::new(codex_self_exe.clone(), Some(codex_self_exe))
.expect("runtime paths");
let runner = FileSystemSandboxRunner::new(runtime_paths);
let native_cwd = AbsolutePathBuf::current_dir().expect("cwd");
@@ -799,7 +799,7 @@ mod tests {
fn sandbox_exec_request_uses_filesystem_root_and_preserves_policy_cwd() {
let codex_self_exe = std::env::current_exe().expect("current exe");
let runtime_paths =
ExecServerRuntimePaths::new(codex_self_exe.clone(), Some(codex_self_exe))
ExecServerRuntimeOptions::new(codex_self_exe.clone(), Some(codex_self_exe))
.expect("runtime paths");
let runner = FileSystemSandboxRunner::new(runtime_paths);
let selected = tempfile::tempdir().expect("selected directory");
@@ -889,7 +889,7 @@ mod tests {
fn sandbox_exec_request_binds_windows_relative_globs_to_policy_cwd() {
let codex_self_exe = std::env::current_exe().expect("current exe");
let runtime_paths =
ExecServerRuntimePaths::new(codex_self_exe, /*codex_linux_sandbox_exe*/ None)
ExecServerRuntimeOptions::new(codex_self_exe, /*codex_linux_sandbox_exe*/ None)
.expect("runtime paths");
let runner = FileSystemSandboxRunner::new(runtime_paths);
let selected = tempfile::tempdir().expect("selected directory");
@@ -979,7 +979,7 @@ mod tests {
fn helper_permissions_include_only_the_helper_executable() {
let codex_self_exe = std::env::current_exe().expect("current exe");
let runtime_paths =
ExecServerRuntimePaths::new(codex_self_exe, /*codex_linux_sandbox_exe*/ None)
ExecServerRuntimeOptions::new(codex_self_exe, /*codex_linux_sandbox_exe*/ None)
.expect("runtime paths");
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
.expect("absolute cwd");
@@ -1012,7 +1012,7 @@ mod tests {
let codex_self_exe = root.path().join("bin").join("codex");
let codex_linux_sandbox_exe = root.path().join("aliases").join("codex-linux-sandbox");
let runtime_paths =
ExecServerRuntimePaths::new(codex_self_exe, Some(codex_linux_sandbox_exe))
ExecServerRuntimeOptions::new(codex_self_exe, Some(codex_linux_sandbox_exe))
.expect("runtime paths");
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir().as_path())
.expect("absolute cwd");
+2 -2
View File
@@ -39,7 +39,7 @@ mod resolved_capability;
mod rpc;
mod rpc_server_requests;
mod rpc_timing;
mod runtime_paths;
mod runtime_options;
mod sandbox_selection;
mod sandboxed_file_open;
mod sandboxed_file_system;
@@ -228,7 +228,7 @@ pub use remote::run_remote_environment_forward_until_shutdown;
pub use remote::run_remote_environment_until_shutdown;
pub use resolved_capability::ResolvedSelectedCapabilityRoot;
pub use resolved_capability::SelectedCapabilityRootsStatus;
pub use runtime_paths::ExecServerRuntimePaths;
pub use runtime_options::ExecServerRuntimeOptions;
pub use server::ConcurrentRequestLimit;
pub use server::DEFAULT_LISTEN_URL;
pub use server::ExecServerListenUrlParseError;
@@ -20,7 +20,7 @@ use tokio_util::sync::CancellationToken;
use crate::CopyOptions;
use crate::CreateDirectoryOptions;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ExecutorFileSystem;
use crate::ExecutorFileSystemFuture;
use crate::FILE_READ_CHUNK_SIZE;
@@ -76,7 +76,7 @@ impl LocalFileSystem {
}
}
pub fn with_runtime_paths(runtime_paths: ExecServerRuntimePaths) -> Self {
pub fn with_runtime_paths(runtime_paths: ExecServerRuntimeOptions) -> Self {
Self {
unsandboxed: UnsandboxedFileSystem::default(),
sandboxed: Some(SandboxedFileSystem::new(runtime_paths)),
+6 -6
View File
@@ -40,7 +40,7 @@ use crate::ExecProcessEvent;
use crate::ExecProcessEventReceiver;
use crate::ExecProcessFuture;
use crate::ExecServerError;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ProcessId;
use crate::StartedExecProcess;
use crate::network_policy_decisions::network_policy_decider;
@@ -174,7 +174,7 @@ struct Inner {
#[derive(Clone)]
pub(crate) struct LocalProcess {
inner: Arc<Inner>,
runtime_paths: Option<ExecServerRuntimePaths>,
runtime_paths: Option<ExecServerRuntimeOptions>,
}
struct LocalExecProcess {
@@ -191,11 +191,11 @@ impl Default for LocalProcess {
}
impl LocalProcess {
pub(crate) fn with_local_runtime_paths(runtime_paths: ExecServerRuntimePaths) -> Self {
pub(crate) fn with_local_runtime_paths(runtime_paths: ExecServerRuntimeOptions) -> Self {
Self::with_discarded_notifications(Some(runtime_paths))
}
fn with_discarded_notifications(runtime_paths: Option<ExecServerRuntimePaths>) -> Self {
fn with_discarded_notifications(runtime_paths: Option<ExecServerRuntimeOptions>) -> Self {
let (outgoing_tx, mut outgoing_rx) =
mpsc::channel::<RpcServerOutboundMessage>(NOTIFICATION_CHANNEL_CAPACITY);
tokio::spawn(async move { while outgoing_rx.recv().await.is_some() {} });
@@ -209,7 +209,7 @@ impl LocalProcess {
pub(crate) fn new(
notifications: RpcNotificationSender,
telemetry: ExecServerTelemetry,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
) -> Self {
Self::with_runtime_paths(notifications, telemetry, Some(runtime_paths))
}
@@ -217,7 +217,7 @@ impl LocalProcess {
fn with_runtime_paths(
notifications: RpcNotificationSender,
telemetry: ExecServerTelemetry,
runtime_paths: Option<ExecServerRuntimePaths>,
runtime_paths: Option<ExecServerRuntimeOptions>,
) -> Self {
let requests = notifications.request_sender();
Self {
@@ -11,7 +11,7 @@ use tokio::time::timeout;
use super::ClosedNoiseVirtualStream;
use super::spawn_noise_virtual_stream;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::connection::CHANNEL_CAPACITY;
use crate::noise_channel::InitiatorHandshake;
use crate::noise_channel::NoiseChannelIdentity;
@@ -92,7 +92,7 @@ async fn processor_exit_reports_closed_virtual_stream() -> Result<()> {
let mut stream = spawn_noise_virtual_stream(
"stream-1".to_string(),
/*instance_id*/ 7,
ConnectionProcessor::new(ExecServerRuntimePaths::new(
ConnectionProcessor::new(ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?),
+16 -13
View File
@@ -35,7 +35,7 @@ use codex_utils_path_uri::PathUri;
#[cfg(unix)]
use crate::CODEX_ARG0_EXEC_HELPER_ARG1;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::process_telemetry::trace_process_id;
use crate::protocol::ExecParams;
use crate::rpc::internal_error;
@@ -86,7 +86,7 @@ impl PreparedExecRequest {
pub(crate) async fn prepare_exec_request_with_telemetry(
params: &ExecParams,
env: HashMap<String, String>,
runtime_paths: Option<&ExecServerRuntimePaths>,
runtime_paths: Option<&ExecServerRuntimeOptions>,
network_policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
network_policy_audit_observer: Option<NetworkPolicyAuditObserver>,
telemetry: &ProcessTelemetry,
@@ -123,15 +123,9 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
let (env, managed_network, network_proxy_handle, network_proxy_restricting_sid) =
prepare_managed_network(
params.managed_network.as_ref(),
params,
network_proxy,
if params.sandbox.as_ref().is_some_and(|sandbox| {
sandbox.windows_sandbox_selection == WindowsSandboxSelection::Mxc
}) {
ManagedProxyRouting::DedicatedListeners
} else {
ManagedProxyRouting::SharedIngress
},
runtime_paths.is_some_and(|paths| paths.proxy_private_ips_via_upstream),
env,
network_policy_decider,
network_policy_audit_observer,
@@ -332,9 +326,9 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
}
async fn prepare_managed_network(
managed_network: Option<&ManagedNetworkSandboxContext>,
params: &ExecParams,
network_proxy: Option<&RemoteNetworkProxyLaunchConfig>,
routing: ManagedProxyRouting,
proxy_private_ips_via_upstream: bool,
env: HashMap<String, String>,
network_policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
network_policy_audit_observer: Option<NetworkPolicyAuditObserver>,
@@ -349,13 +343,22 @@ async fn prepare_managed_network(
JSONRPCErrorError,
> {
let Some(network_proxy) = network_proxy.cloned() else {
return Ok((env, managed_network.cloned(), None, None));
return Ok((env, params.managed_network.clone(), None, None));
};
let routing =
if params.sandbox.as_ref().is_some_and(|sandbox| {
sandbox.windows_sandbox_selection == WindowsSandboxSelection::Mxc
}) {
ManagedProxyRouting::DedicatedListeners
} else {
ManagedProxyRouting::SharedIngress
};
let mut state = NetworkProxyState::from_remote_launch_config(
network_proxy,
codex_utils_path_uri::Platform::native(),
)
.map_err(|err| invalid_params(format!("invalid network proxy config: {err}")))?;
state.set_proxy_private_ips_via_upstream(proxy_private_ips_via_upstream);
if let Some(observer) = network_policy_audit_observer {
state.set_policy_audit_observer(observer);
}
@@ -40,7 +40,7 @@ use super::prepare_exec_request_with_telemetry;
#[cfg(unix)]
use crate::CODEX_ARG0_EXEC_HELPER_ARG1;
use crate::ExecParams;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
#[cfg(any(unix, windows))]
use crate::FileSystemSandboxContext;
use crate::ProcessId;
@@ -49,7 +49,7 @@ use crate::process_telemetry::ProcessTelemetry;
async fn prepare_exec_request(
params: &ExecParams,
env: HashMap<String, String>,
runtime_paths: Option<&ExecServerRuntimePaths>,
runtime_paths: Option<&ExecServerRuntimeOptions>,
network_policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
network_policy_audit_observer: Option<NetworkPolicyAuditObserver>,
) -> Result<PreparedExecRequest, JSONRPCErrorError> {
@@ -72,7 +72,7 @@ async fn sandbox_request_wraps_native_argv_on_executor() {
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");
ExecServerRuntimeOptions::new(self_exe.clone(), Some(self_exe)).expect("runtime paths");
let sandbox = FileSystemSandboxContext::from_permission_profile(
PermissionProfile::workspace_write(),
cwd_uri.clone(),
@@ -192,7 +192,7 @@ async fn sandbox_request_routes_custom_arg0_to_inner_helper() {
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");
ExecServerRuntimeOptions::new(self_exe.clone(), Some(self_exe)).expect("runtime paths");
let sandbox = FileSystemSandboxContext::from_permission_profile(
PermissionProfile::workspace_write(),
cwd_uri.clone(),
@@ -246,7 +246,7 @@ async fn sandbox_request_routes_custom_arg0_to_inner_helper() {
}
#[cfg(target_os = "macos")]
fn managed_network_sandbox_request() -> (ExecParams, ExecServerRuntimePaths) {
fn managed_network_sandbox_request() -> (ExecParams, ExecServerRuntimeOptions) {
let cwd: AbsolutePathBuf = std::env::current_dir()
.expect("current directory")
.try_into()
@@ -254,7 +254,7 @@ fn managed_network_sandbox_request() -> (ExecParams, ExecServerRuntimePaths) {
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");
ExecServerRuntimeOptions::new(self_exe.clone(), Some(self_exe)).expect("runtime paths");
let sandbox = FileSystemSandboxContext::from_permission_profile(
PermissionProfile::workspace_write(),
cwd_uri.clone(),
@@ -646,7 +646,7 @@ async fn managed_network_honors_windows_sandbox_level(windows_sandbox_level: Win
.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, None).expect("runtime paths");
let runtime_paths = ExecServerRuntimeOptions::new(self_exe, None).expect("runtime paths");
let permissions = PermissionProfile::read_only();
let mut sandbox =
FileSystemSandboxContext::from_permission_profile(permissions.clone(), cwd_uri.clone());
+1 -1
View File
@@ -1116,7 +1116,7 @@ mod tests {
#[tokio::test]
async fn multiplexed_environment_sends_keepalive() -> anyhow::Result<()> {
let (client_websocket, mut server_websocket) = websocket_pair().await?;
let runtime_paths = crate::ExecServerRuntimePaths::new(
let runtime_paths = crate::ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)
@@ -28,7 +28,7 @@ use super::NOISE_HANDSHAKE_FAILURE_COOLDOWN;
use super::RendezvousDisconnectReason;
use super::run_multiplexed_environment;
use crate::ExecServerError;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::connection::JsonRpcConnectionEvent;
use crate::noise_channel::InitiatorHandshake;
use crate::noise_channel::NoiseChannelIdentity;
@@ -140,7 +140,7 @@ async fn missing_pong_disconnects_physical_relay() -> Result<()> {
let environment_task = tokio::spawn(run_multiplexed_environment(
environment_websocket,
ConnectionProcessor::new(ExecServerRuntimePaths::new(
ConnectionProcessor::new(ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?),
@@ -171,7 +171,7 @@ async fn pong_keeps_physical_relay_connected() -> Result<()> {
let environment_task = tokio::spawn(run_multiplexed_environment(
environment_websocket,
ConnectionProcessor::new(ExecServerRuntimePaths::new(
ConnectionProcessor::new(ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?),
@@ -245,7 +245,7 @@ async fn processor_exit_resets_noise_harness_stream() -> Result<()> {
let environment_task = AbortOnDropHandle::new(tokio::spawn(run_multiplexed_environment(
environment_websocket,
ObservedRegistration(
ConnectionProcessor::new(ExecServerRuntimePaths::new(
ConnectionProcessor::new(ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?),
@@ -315,7 +315,7 @@ async fn pending_harness_key_validation_does_not_block_new_handshakes() -> Resul
let calls = Arc::new(AtomicUsize::new(0));
let environment_task = tokio::spawn(run_multiplexed_environment(
environment_websocket,
ConnectionProcessor::new(ExecServerRuntimePaths::new(
ConnectionProcessor::new(ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?),
@@ -369,7 +369,7 @@ async fn duplicate_handshakes_pause_admission_without_disconnecting() -> Result<
let release = Arc::new(Notify::new());
let environment_task = tokio::spawn(run_multiplexed_environment(
environment_websocket,
ConnectionProcessor::new(ExecServerRuntimePaths::new(
ConnectionProcessor::new(ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?),
@@ -478,7 +478,7 @@ async fn oversized_harness_authorization_is_rejected_before_validation() -> Resu
let calls = Arc::new(AtomicUsize::new(0));
let environment_task = tokio::spawn(run_multiplexed_environment(
environment_websocket,
ConnectionProcessor::new(ExecServerRuntimePaths::new(
ConnectionProcessor::new(ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?),
@@ -697,7 +697,7 @@ async fn early_data_during_validation_pauses_admission_without_disconnecting() -
let harness_identity = NoiseChannelIdentity::generate()?;
let environment_task = tokio::spawn(run_multiplexed_environment(
environment_websocket,
ConnectionProcessor::new(ExecServerRuntimePaths::new(
ConnectionProcessor::new(ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?),
+3 -3
View File
@@ -33,7 +33,7 @@ use crate::EnvironmentRegistryHarnessKeyValidationResponse;
use crate::EnvironmentRegistryRegistrationRequest;
use crate::EnvironmentRegistryRegistrationResponse;
use crate::ExecServerError;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ExecServerTelemetry;
use crate::NoiseChannelIdentity;
use crate::NoiseChannelPublicKey;
@@ -603,7 +603,7 @@ impl RemoteEnvironmentConfig {
/// conflict refreshes the registration; other permanent client errors stop the runner.
pub async fn run_remote_environment(
config: RemoteEnvironmentConfig,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
) -> Result<(), ExecServerError> {
run_remote_environment_until_shutdown(config, runtime_paths, std::future::pending()).await
}
@@ -613,7 +613,7 @@ pub async fn run_remote_environment(
/// Active sessions and their processes are drained before this function returns.
pub async fn run_remote_environment_until_shutdown<F>(
config: RemoteEnvironmentConfig,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
shutdown: F,
) -> Result<(), ExecServerError>
where
@@ -28,7 +28,7 @@ use wiremock::matchers::method;
use wiremock::matchers::path;
use super::*;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::RemoteEnvironmentTransport;
#[derive(Debug)]
@@ -324,7 +324,7 @@ async fn direct_registration_handles_policy_outages_and_permanent_failures() ->
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault)
.with_network_policy(policy.clone().for_current_account()),
)?;
let runtime_paths = ExecServerRuntimePaths::new(
let runtime_paths = ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?;
@@ -414,7 +414,7 @@ async fn direct_websocket_reuses_registration_and_stops_on_permanent_errors() ->
Arc::new(StaticAuthProvider),
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
)?;
let runtime_paths = ExecServerRuntimePaths::new(
let runtime_paths = ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?;
@@ -188,7 +188,7 @@ async fn reconnect_reuses_registration_until_url_is_rejected() -> Result<()> {
)?;
let environment_task = tokio::spawn(run_remote_environment(
config,
ExecServerRuntimePaths::new(
ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?,
@@ -1,3 +1,5 @@
//! Executor-local paths and trusted startup settings used when preparing execution.
use std::path::PathBuf;
use codex_sandboxing::LinuxSandboxPidNamespace;
@@ -5,7 +7,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
/// Paths and sandbox settings initialized when creating an executor.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ExecServerRuntimePaths {
pub struct ExecServerRuntimeOptions {
/// Stable path to the Codex executable used to launch hidden helper modes.
pub codex_self_exe: AbsolutePathBuf,
/// Path to the Linux sandbox helper alias used when the platform sandbox
@@ -13,12 +15,14 @@ pub struct ExecServerRuntimePaths {
pub codex_linux_sandbox_exe: Option<AbsolutePathBuf>,
/// Trusted startup policy; requests and repository config cannot change PID isolation.
pub linux_sandbox_pid_namespace: LinuxSandboxPidNamespace,
/// Trusted startup routing; request policy still controls destination access.
pub proxy_private_ips_via_upstream: bool,
/// User-config opt-out of writable-root symlink checks beneath this host's home.
#[cfg(target_os = "macos")]
pub allowed_symlinked_codex_home: Option<AbsolutePathBuf>,
}
impl ExecServerRuntimePaths {
impl ExecServerRuntimeOptions {
pub fn from_optional_paths(
codex_self_exe: Option<PathBuf>,
codex_linux_sandbox_exe: Option<PathBuf>,
@@ -38,6 +42,7 @@ impl ExecServerRuntimePaths {
) -> std::io::Result<Self> {
Ok(Self {
linux_sandbox_pid_namespace: LinuxSandboxPidNamespace::default(),
proxy_private_ips_via_upstream: false,
codex_self_exe: absolute_path(codex_self_exe)?,
codex_linux_sandbox_exe: codex_linux_sandbox_exe.map(absolute_path).transpose()?,
#[cfg(target_os = "macos")]
@@ -51,6 +56,12 @@ impl ExecServerRuntimePaths {
self
}
/// Routes permitted private IPs through the executor's configured upstream proxy.
pub fn with_proxy_private_ips_via_upstream(mut self, enabled: bool) -> Self {
self.proxy_private_ips_via_upstream = enabled;
self
}
/// Applies the symlink opt-in resolved by the execution host's config loader.
#[cfg(target_os = "macos")]
pub fn with_allowed_symlinked_codex_home(
@@ -10,7 +10,7 @@ use crate::CapabilityRootsDiscoverResponse;
use crate::CopyOptions;
use crate::CreateDirectoryOptions;
use crate::DiscoverV2CapabilitiesResponse;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ExecutorFileSystem;
use crate::ExecutorFileSystemFuture;
use crate::FILE_READ_CHUNK_SIZE;
@@ -96,7 +96,7 @@ impl SandboxedFileSystem {
.map_err(map_sandbox_error)
}
pub fn new(runtime_paths: ExecServerRuntimePaths) -> Self {
pub fn new(runtime_paths: ExecServerRuntimeOptions) -> Self {
Self {
sandbox_runner: FileSystemSandboxRunner::new(runtime_paths),
}
@@ -129,7 +129,7 @@ fn sandbox_routing_uses_independent_read_write_policies_and_executor_temp_rules(
#[tokio::test]
async fn sandboxed_file_system_rejects_non_native_uri_as_invalid_input() {
let runtime_paths = ExecServerRuntimePaths::new(
let runtime_paths = ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
+5 -5
View File
@@ -20,14 +20,14 @@ pub use request_dispatcher::RequestDispatchMode;
pub use transport::DEFAULT_LISTEN_URL;
pub use transport::ExecServerListenUrlParseError;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ExecServerTelemetry;
use codex_http_client::HttpClientFactory;
use codex_websocket_auth::WebsocketAuthSettings;
pub async fn run_main(
listen_url: &str,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
http_client_factory: HttpClientFactory,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
run_main_with_telemetry(
@@ -48,7 +48,7 @@ pub async fn run_main(
)]
pub async fn run_main_with_telemetry(
listen_url: &str,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
telemetry: ExecServerTelemetry,
http_client_factory: HttpClientFactory,
request_dispatch_mode: RequestDispatchMode,
@@ -77,7 +77,7 @@ mod tests {
use tracing_subscriber::prelude::*;
use super::run_main_with_telemetry;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ExecServerTelemetry;
#[tokio::test]
@@ -93,7 +93,7 @@ mod tests {
tracing::callsite::rebuild_interest_cache();
run_main_with_telemetry(
"invalid",
ExecServerRuntimePaths::new(
ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current executable"),
/*codex_linux_sandbox_exe*/ None,
)
@@ -8,7 +8,7 @@ use crate::CapabilityRootsDiscoverParams;
use crate::CapabilityRootsDiscoverResponse;
use crate::CopyOptions;
use crate::CreateDirectoryOptions;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ExecutorFileSystem;
use crate::GetMetadataOptions;
use crate::ReadFileOptions;
@@ -59,7 +59,7 @@ pub(crate) struct FileSystemHandler {
}
impl FileSystemHandler {
pub(crate) fn new(runtime_paths: ExecServerRuntimePaths) -> Self {
pub(crate) fn new(runtime_paths: ExecServerRuntimeOptions) -> Self {
Self {
file_system: LocalFileSystem::with_runtime_paths(runtime_paths),
file_reads: FileReadHandleManager::default(),
@@ -369,7 +369,7 @@ mod tests {
#[tokio::test]
async fn no_platform_sandbox_policies_do_not_require_configured_sandbox_helper() {
let temp_dir = tempfile::tempdir().expect("tempdir");
let runtime_paths = ExecServerRuntimePaths::new(
let runtime_paths = ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
+3 -3
View File
@@ -13,7 +13,7 @@ use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::client::http_client::PendingRouteAwareHttpBodyStream;
use crate::client::http_client::RouteAwareHttpClient;
use crate::client::http_client::RouteAwareHttpRequestRunner;
@@ -83,7 +83,7 @@ pub(crate) struct ExecServerHandler {
background_task_shutdown: CancellationToken,
background_tasks: TaskTracker,
file_system: FileSystemHandler,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
http_client: RouteAwareHttpClient,
initialize_requested: AtomicBool,
initialized: AtomicBool,
@@ -93,7 +93,7 @@ impl ExecServerHandler {
pub(crate) fn new(
session_registry: Arc<SessionRegistry>,
notifications: RpcNotificationSender,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
http_client_factory: HttpClientFactory,
) -> Self {
Self {
@@ -10,7 +10,7 @@ use tokio::sync::mpsc;
use uuid::Uuid;
use super::ExecServerHandler;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ProcessId;
use crate::protocol::ExecParams;
use crate::protocol::InitializeParams;
@@ -77,8 +77,8 @@ fn windows_command_processor() -> String {
std::env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".to_string())
}
fn test_runtime_paths() -> ExecServerRuntimePaths {
ExecServerRuntimePaths::new(
fn test_runtime_paths() -> ExecServerRuntimeOptions {
ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
@@ -1,7 +1,7 @@
use crate::process_telemetry::ProcessTelemetry;
use codex_exec_server_protocol::JSONRPCErrorError;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::local_process::LocalProcess;
use crate::protocol::ExecParams;
use crate::protocol::ExecResponse;
@@ -25,7 +25,7 @@ impl ProcessHandler {
pub(crate) fn new(
notifications: RpcNotificationSender,
telemetry: ExecServerTelemetry,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
) -> Self {
Self {
process: LocalProcess::new(notifications, telemetry, runtime_paths),
@@ -37,7 +37,7 @@ use wiremock::matchers::method;
use super::ExecServerHandler;
use super::registry::build_router;
use super::session_registry::SessionRegistry;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ExecServerTelemetry;
use crate::connection::JsonRpcConnectionEvent;
use crate::protocol::EXEC_METHOD;
@@ -739,7 +739,7 @@ fn new_handler(
let mut handler = ExecServerHandler::new(
Arc::clone(sessions),
RpcNotificationSender::new(outgoing),
ExecServerRuntimePaths::new(
ExecServerRuntimeOptions::new(
std::env::current_exe().expect("test executable"),
/*codex_linux_sandbox_exe*/ None,
)
+9 -9
View File
@@ -9,7 +9,7 @@ use tokio_util::task::AbortOnDropHandle;
use tracing::debug;
use tracing::warn;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::LocalFileSystem;
use crate::connection::CHANNEL_CAPACITY;
use crate::connection::JsonRpcConnection;
@@ -38,7 +38,7 @@ pub(crate) struct ConnectionProcessor {
capability_manager: Arc<CapabilityManager>,
capability_prewarm: Arc<AbortOnDropHandle<()>>,
session_registry: Arc<SessionRegistry>,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
telemetry: ExecServerTelemetry,
http_client_factory: HttpClientFactory,
request_dispatch_mode: RequestDispatchMode,
@@ -46,7 +46,7 @@ pub(crate) struct ConnectionProcessor {
impl ConnectionProcessor {
#[cfg(test)]
pub(crate) fn new(runtime_paths: ExecServerRuntimePaths) -> Self {
pub(crate) fn new(runtime_paths: ExecServerRuntimeOptions) -> Self {
Self::new_with_location_request(
runtime_paths,
ExecServerTelemetry::default(),
@@ -62,7 +62,7 @@ impl ConnectionProcessor {
}
pub(crate) fn new_with_telemetry(
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
telemetry: ExecServerTelemetry,
http_client_factory: HttpClientFactory,
request_dispatch_mode: RequestDispatchMode,
@@ -83,7 +83,7 @@ impl ConnectionProcessor {
}
fn new_with_location_request(
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
telemetry: ExecServerTelemetry,
http_client_factory: HttpClientFactory,
request_dispatch_mode: RequestDispatchMode,
@@ -342,7 +342,7 @@ mod tests {
use super::complete_queued_client_responses;
use super::run_connection;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ProcessId;
use crate::connection::JsonRpcConnection;
use crate::connection::JsonRpcConnectionEvent;
@@ -372,7 +372,7 @@ mod tests {
#[tokio::test]
async fn startup_prewarms_without_a_client_connection() -> anyhow::Result<()> {
let paths = ExecServerRuntimePaths::new(
let paths = ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?;
@@ -637,8 +637,8 @@ mod tests {
(client_writer, BufReader::new(client_reader).lines(), task)
}
fn test_runtime_paths() -> ExecServerRuntimePaths {
ExecServerRuntimePaths::new(
fn test_runtime_paths() -> ExecServerRuntimeOptions {
ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
@@ -31,7 +31,7 @@ use super::ConcurrentRequestLimit;
use super::RequestDispatchMode;
use super::RequestDispatcher;
use super::RequestTaskResult;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::connection::JsonRpcConnectionEvent;
use crate::rpc::RpcNotificationSender;
use crate::rpc::RpcRouter;
@@ -178,7 +178,7 @@ async fn request_queue_waits_for_dispatcher_admission_before_recording_telemetry
let handler = Arc::new(ExecServerHandler::new(
SessionRegistry::new(telemetry.clone()),
notifications,
ExecServerRuntimePaths::new(
ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current executable"),
/*codex_linux_sandbox_exe*/ None,
)
@@ -387,7 +387,7 @@ fn request_telemetry_fixture(router: RpcRouter<ExecServerHandler>) -> RequestTel
let handler = Arc::new(ExecServerHandler::new(
SessionRegistry::new(telemetry.clone()),
notifications,
ExecServerRuntimePaths::new(
ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current executable"),
/*codex_linux_sandbox_exe*/ None,
)
@@ -7,7 +7,7 @@ use codex_exec_server_protocol::JSONRPCErrorError;
use tokio::sync::Mutex;
use uuid::Uuid;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::rpc::RpcNotificationSender;
use crate::rpc::invalid_request;
use crate::rpc::session_already_attached;
@@ -64,7 +64,7 @@ impl SessionRegistry {
self: &Arc<Self>,
resume_session_id: Option<String>,
notifications: RpcNotificationSender,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
) -> Result<SessionHandle, JSONRPCErrorError> {
enum AttachOutcome {
Attached(Arc<SessionEntry>),
+5 -5
View File
@@ -30,7 +30,7 @@ use tokio::net::TcpListener;
use tracing::info;
use tracing::warn;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::ExecServerTelemetry;
use crate::connection::JsonRpcConnection;
use crate::server::RequestDispatchMode;
@@ -91,7 +91,7 @@ pub(crate) fn parse_listen_url(
pub(crate) async fn run_transport(
listen_url: &str,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
telemetry: ExecServerTelemetry,
http_client_factory: HttpClientFactory,
request_dispatch_mode: RequestDispatchMode,
@@ -129,7 +129,7 @@ pub(crate) async fn run_transport(
}
async fn run_stdio_connection(
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
telemetry: ExecServerTelemetry,
http_client_factory: HttpClientFactory,
request_dispatch_mode: RequestDispatchMode,
@@ -148,7 +148,7 @@ async fn run_stdio_connection(
async fn run_stdio_connection_with_io<R, W>(
reader: R,
writer: W,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
telemetry: ExecServerTelemetry,
http_client_factory: HttpClientFactory,
request_dispatch_mode: RequestDispatchMode,
@@ -177,7 +177,7 @@ where
async fn run_websocket_listener(
bind_address: SocketAddr,
runtime_paths: ExecServerRuntimePaths,
runtime_paths: ExecServerRuntimeOptions,
telemetry: ExecServerTelemetry,
http_client_factory: HttpClientFactory,
request_dispatch_mode: RequestDispatchMode,
@@ -17,7 +17,7 @@ use super::DEFAULT_LISTEN_URL;
use super::ExecServerListenTransport;
use super::parse_listen_url;
use super::run_stdio_connection_with_io;
use crate::ExecServerRuntimePaths;
use crate::ExecServerRuntimeOptions;
use crate::protocol::INITIALIZE_METHOD;
use crate::protocol::INITIALIZED_METHOD;
use crate::protocol::InitializeParams;
@@ -163,8 +163,8 @@ async fn write_jsonrpc_line(writer: &mut tokio::io::DuplexStream, message: &JSON
.expect("JSON-RPC newline should write");
}
fn test_runtime_paths() -> ExecServerRuntimePaths {
ExecServerRuntimePaths::new(
fn test_runtime_paths() -> ExecServerRuntimeOptions {
ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)
+2 -2
View File
@@ -4,7 +4,7 @@
//! when a test only needs a WebSocket executor endpoint. It handles the arg0
//! helper mode because sandboxed process requests re-exec this binary.
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use std::ffi::OsStr;
@@ -28,7 +28,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let current_exe = std::env::current_exe()?;
let codex_linux_sandbox_exe =
std::env::var_os(CODEX_LINUX_SANDBOX_EXE_ENV_VAR).map(std::path::PathBuf::from);
let runtime_paths = ExecServerRuntimePaths::new(current_exe, codex_linux_sandbox_exe)?;
let runtime_paths = ExecServerRuntimeOptions::new(current_exe, codex_linux_sandbox_exe)?;
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?
@@ -24,7 +24,7 @@ use codex_exec_server::ExecParams;
use codex_exec_server::ExecProcessEvent;
use codex_exec_server::ExecResponse;
use codex_exec_server::ExecServerClientConnectOptions;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_exec_server::InitializeParams;
use codex_exec_server::InitializeResponse;
use codex_exec_server::ProcessId;
@@ -174,7 +174,7 @@ async fn accepted_websocket_interoperates_and_recovers_with_real_direct_executor
http_client_factory.clone(),
)?;
let (codex_exe, codex_linux_sandbox_exe) = common::current_test_binary_helper_paths()?;
let runtime_paths = ExecServerRuntimePaths::new(codex_exe, codex_linux_sandbox_exe)?;
let runtime_paths = ExecServerRuntimeOptions::new(codex_exe, codex_linux_sandbox_exe)?;
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
let executor_task = AbortOnDropHandle::new(tokio::spawn(
codex_exec_server::run_remote_environment_until_shutdown(
+2 -2
View File
@@ -8,7 +8,7 @@ use std::time::Duration;
use codex_exec_server::CODEX_ARG0_EXEC_HELPER_ARG1;
use codex_exec_server::CODEX_FS_HELPER_ARG1;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_exec_server::ExecServerTelemetry;
use codex_exec_server::RequestDispatchMode;
use codex_http_client::HttpClientFactory;
@@ -214,7 +214,7 @@ fn maybe_run_exec_server_from_test_binary(guard: Option<&TestBinaryDispatchGuard
std::process::exit(1);
}
};
let runtime_paths = match ExecServerRuntimePaths::new(
let runtime_paths = match ExecServerRuntimeOptions::new(
current_exe.clone(),
linux_sandbox_exe(guard, &current_exe),
) {
@@ -40,7 +40,7 @@ async fn shell_snapshot_concurrent_replays_keep_independent_readers(
let (exe, linux_sandbox) = current_test_binary_helper_paths()?;
let environment = Environment::create(
/*exec_server_url*/ None,
codex_exec_server::ExecServerRuntimePaths::new(exe, linux_sandbox)?,
codex_exec_server::ExecServerRuntimeOptions::new(exe, linux_sandbox)?,
codex_http_client::HttpClientFactory::new(
codex_http_client::OutboundProxyPolicy::ReqwestDefault,
),
@@ -5,7 +5,7 @@ use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::EnvironmentAccess;
use codex_exec_server::EnvironmentAccessExt;
#[cfg(unix)]
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::FILE_READ_CHUNK_SIZE;
use codex_exec_server::FileMetadata;
@@ -1155,7 +1155,7 @@ async fn sandboxed_file_operations_cannot_read_helper_siblings() -> Result<()> {
None
};
let file_system =
LocalFileSystem::with_runtime_paths(ExecServerRuntimePaths::new(helper, linux_sandbox)?);
LocalFileSystem::with_runtime_paths(ExecServerRuntimeOptions::new(helper, linux_sandbox)?);
let sibling = runtime_dir.join("credentials.json");
std::fs::write(&sibling, "secret")?;
@@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::Result;
use codex_exec_server::Environment;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::FileSystemSandboxContext;
use codex_exec_server::LocalFileSystem;
@@ -50,7 +50,7 @@ pub(crate) async fn create_file_system_context(
match implementation {
FileSystemImplementation::Local => {
let helper_paths = test_codex_helper_paths()?;
let runtime_paths = ExecServerRuntimePaths::new(
let runtime_paths = ExecServerRuntimeOptions::new(
helper_paths.codex_exe.clone(),
helper_paths.codex_linux_sandbox_exe.clone(),
)?;
+3 -3
View File
@@ -22,7 +22,7 @@ use codex_exec_server::EnvironmentReadyInfo;
use codex_exec_server::ExecParams;
use codex_exec_server::ExecResponse;
use codex_exec_server::ExecServerError;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_exec_server::FsReadFileParams;
use codex_exec_server::NoiseChannelPublicKey;
use codex_exec_server::NoiseRendezvousConnectBundle;
@@ -162,7 +162,7 @@ async fn failed_noise_environment_recovers_and_reconnects_after_ready_report() -
.await;
let (codex_exe, codex_linux_sandbox_exe) = common::current_test_binary_helper_paths()?;
let runtime_paths = ExecServerRuntimePaths::new(codex_exe, codex_linux_sandbox_exe)?;
let runtime_paths = ExecServerRuntimeOptions::new(codex_exe, codex_linux_sandbox_exe)?;
let http_client_factory = HttpClientFactory::new(OutboundProxyPolicy::RespectSystemProxy);
let config = RemoteEnvironmentConfig::new(
registry.uri(),
@@ -361,7 +361,7 @@ async fn next_connection_state(
async fn remote_environment_routes_encrypted_exec_server_rpc() -> Result<()> {
let relay = RelayTest::new().await?;
let (codex_exe, codex_linux_sandbox_exe) = common::current_test_binary_helper_paths()?;
let runtime_paths = ExecServerRuntimePaths::new(codex_exe, codex_linux_sandbox_exe)?;
let runtime_paths = ExecServerRuntimeOptions::new(codex_exe, codex_linux_sandbox_exe)?;
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
let remote_environment = AbortOnDropHandle::new(tokio::spawn(
codex_exec_server::run_remote_environment_until_shutdown(
@@ -76,7 +76,7 @@ impl RegistryFixture {
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
)?;
let (codex_exe, sandbox_exe) = common::current_test_binary_helper_paths()?;
let runtime_paths = ExecServerRuntimePaths::new(codex_exe, sandbox_exe)?;
let runtime_paths = ExecServerRuntimeOptions::new(codex_exe, sandbox_exe)?;
let (shutdown_tx, shutdown_rx) = oneshot::channel();
let task = AbortOnDropHandle::new(tokio::spawn(
codex_exec_server::run_remote_environment_until_shutdown(
+2 -2
View File
@@ -17,7 +17,7 @@ pub use cli::Command;
pub use cli::ReviewArgs;
use codex_app_server_client::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY;
use codex_app_server_client::EnvironmentManager;
use codex_app_server_client::ExecServerRuntimePaths;
use codex_app_server_client::ExecServerRuntimeOptions;
use codex_app_server_client::InProcessAppServerClient;
use codex_app_server_client::InProcessClientStartArgs;
use codex_app_server_client::InProcessServerEvent;
@@ -686,7 +686,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
range: None,
})
.collect();
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
let local_runtime_paths = ExecServerRuntimeOptions::from_optional_paths(
arg0_paths.codex_self_exe.clone(),
arg0_paths.codex_linux_sandbox_exe.clone(),
)?;
+1 -1
View File
@@ -20,7 +20,7 @@ pub(super) async fn fork_source(
let state_db = codex_core::init_state_db(config).await;
let environment_manager = EnvironmentManager::from_codex_home(
config.codex_home.clone(),
Some(ExecServerRuntimePaths::from_optional_paths(
Some(ExecServerRuntimeOptions::from_optional_paths(
arg0_paths.codex_self_exe.clone(),
arg0_paths.codex_linux_sandbox_exe.clone(),
)?),
+50 -1
View File
@@ -478,7 +478,7 @@ where
}
};
let proxy = if allow_upstream_proxy {
proxy_for_connect(&authority)
proxy_for_connect(&authority, &app_state)
} else {
None
};
@@ -1819,6 +1819,55 @@ mod tests {
);
}
#[tokio::test]
async fn private_ip_upstream_routing_preserves_destination_policy() {
for (allowlisted, denied, expected_error) in [
(true, true, "blocked-by-denylist"),
(false, false, "blocked-by-allowlist"),
] {
let mut policy = NetworkProxyConfig {
allow_local_binding: Some(true),
..NetworkProxyConfig::default()
};
if allowlisted {
policy.set_allowed_domains(vec!["100.68.58.50".to_string()]);
}
if denied {
policy.set_denied_domains(vec!["100.68.58.50".to_string()]);
}
let mut state = network_proxy_state_for_policy(policy);
state.set_proxy_private_ips_via_upstream(/*enabled*/ true);
let state = Arc::new(state);
for method in [Method::GET, Method::CONNECT] {
let mut req = Request::builder()
.method(method.clone())
.uri("http://100.68.58.50:80/api/items")
.header("host", "100.68.58.50:80")
.body(Body::empty())
.unwrap();
req.extensions_mut().insert(state.clone());
let response = if method == Method::CONNECT {
http_connect_accept(
/*policy_decider*/ None, /*environment_id*/ None, req,
)
.await
.unwrap_err()
} else {
http_plain_proxy(
/*policy_decider*/ None, /*environment_id*/ None, req,
)
.await
.unwrap()
};
assert_eq!(response.status(), StatusCode::FORBIDDEN);
assert_eq!(
response.headers().get("x-proxy-error").unwrap(),
expected_error
);
}
}
}
#[tokio::test]
async fn http_plain_proxy_rejects_absolute_uri_host_header_mismatch() {
let state = Arc::new(network_proxy_state_for_policy(NetworkProxyConfig::default()));
+12
View File
@@ -90,6 +90,18 @@ fn ipv4_in_cidr(ip: Ipv4Addr, base: [u8; 4], prefix: u8) -> bool {
(ip & mask) == (base & mask)
}
/// Private unicast ranges that may be reachable through an upstream VPN proxy.
/// Loopback, link-local, and other special-use addresses retain their normal routing.
pub(crate) fn is_private_network_ip(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(ip) => ip.is_private() || ipv4_in_cidr(ip, [100, 64, 0, 0], /*prefix*/ 10),
IpAddr::V6(ip) => match ip.to_ipv4() {
Some(ip) => is_private_network_ip(IpAddr::V4(ip)),
None => ip.is_unique_local(),
},
}
}
fn is_non_public_ipv6(ip: Ipv6Addr) -> bool {
if let Some(v4) = ip.to_ipv4() {
return is_non_public_ipv4(v4) || ip.is_loopback();
+9
View File
@@ -262,6 +262,7 @@ pub struct NetworkProxyState {
state: Arc<RwLock<ConfigState>>,
/// Belongs to this proxy; config retains omission for launches on other executors.
pub(crate) local_binding_policy: LocalBindingPolicy,
pub(crate) proxy_private_ips_via_upstream: bool,
reloader: Arc<dyn ConfigReloader>,
blocked_request_observer: Arc<RwLock<Option<Arc<dyn BlockedRequestObserver>>>>,
pub(crate) policy_audit_observer: Option<NetworkPolicyAuditObserver>,
@@ -300,6 +301,7 @@ impl Clone for NetworkProxyState {
Self {
state: self.state.clone(),
local_binding_policy: self.local_binding_policy,
proxy_private_ips_via_upstream: self.proxy_private_ips_via_upstream,
reloader: self.reloader.clone(),
blocked_request_observer: self.blocked_request_observer.clone(),
policy_audit_observer: self.policy_audit_observer.clone(),
@@ -315,6 +317,12 @@ impl Clone for NetworkProxyState {
}
impl NetworkProxyState {
/// Selects routing for permitted private IPs without changing destination policy.
/// Set by the execution host, independently of reloadable or remote policy.
pub fn set_proxy_private_ips_via_upstream(&mut self, enabled: bool) {
self.proxy_private_ips_via_upstream = enabled;
}
/// Builds runtime state for one executor-local proxy launch.
/// The launching executor supplies its own OS; the wire policy cannot override it.
pub fn from_remote_launch_config(
@@ -397,6 +405,7 @@ impl NetworkProxyState {
credential_broker,
state: Arc::new(RwLock::new(state)),
local_binding_policy,
proxy_private_ips_via_upstream: false,
reloader,
blocked_request_observer: Arc::new(RwLock::new(blocked_request_observer)),
policy_audit_observer: None,
+21 -7
View File
@@ -1,5 +1,6 @@
use crate::connect_policy::TargetCheckedTcpConnector;
use crate::connect_policy::is_non_public_target;
use crate::policy::is_private_network_ip;
use crate::state::NetworkProxyState;
use codex_utils_rustls_provider::ensure_rustls_crypto_provider;
use rama_core::Layer;
@@ -18,6 +19,7 @@ use rama_http::layer::version_adapter::RequestVersionAdapter;
use rama_http_backend::client::HttpClientService;
use rama_http_backend::client::HttpConnector;
use rama_http_backend::client::proxy::layer::HttpProxyConnectorLayer;
use rama_net::address::Host;
use rama_net::address::HostWithPort;
use rama_net::address::ProxyAddress;
use rama_net::client::EstablishedClientConnection;
@@ -39,14 +41,20 @@ struct ProxyConfig {
http: Option<ProxyAddress>,
https: Option<ProxyAddress>,
all: Option<ProxyAddress>,
proxy_private_ips_via_upstream: bool,
}
impl ProxyConfig {
fn from_env() -> Self {
fn from_env(proxy_private_ips_via_upstream: bool) -> Self {
let http = read_proxy_env(&["HTTP_PROXY", "http_proxy"]);
let https = read_proxy_env(&["HTTPS_PROXY", "https_proxy"]);
let all = read_proxy_env(&["ALL_PROXY", "all_proxy"]);
Self { http, https, all }
Self {
http,
https,
all,
proxy_private_ips_via_upstream,
}
}
fn proxy_for_protocol(&self, is_secure: bool) -> Option<ProxyAddress> {
@@ -61,7 +69,9 @@ impl ProxyConfig {
}
fn proxy_for_target(&self, target: &HostWithPort, is_secure: bool) -> Option<ProxyAddress> {
if is_non_public_target(&target.host) {
let proxy_private_ip = self.proxy_private_ips_via_upstream
&& matches!(&target.host, Host::Address(ip) if is_private_network_ip(*ip));
if is_non_public_target(&target.host) && !proxy_private_ip {
return None;
}
self.proxy_for_protocol(is_secure)
@@ -97,8 +107,12 @@ fn read_proxy_env(keys: &[&str]) -> Option<ProxyAddress> {
None
}
pub(crate) fn proxy_for_connect(target: &HostWithPort) -> Option<ProxyAddress> {
ProxyConfig::from_env().proxy_for_target(target, /*is_secure*/ true)
pub(crate) fn proxy_for_connect(
target: &HostWithPort,
state: &NetworkProxyState,
) -> Option<ProxyAddress> {
ProxyConfig::from_env(state.proxy_private_ips_via_upstream)
.proxy_for_target(target, /*is_secure*/ true)
}
#[derive(Clone)]
@@ -122,7 +136,7 @@ impl UpstreamClient {
pub(crate) fn from_env_proxy(state: Arc<NetworkProxyState>) -> Self {
Self::new(
ProxyConfig::from_env(),
ProxyConfig::from_env(state.proxy_private_ips_via_upstream),
TargetCheckedTcpConnector::new(state),
client_root_certs(),
)
@@ -144,7 +158,7 @@ impl UpstreamClient {
tls_root_store: Arc<rustls::RootCertStore>,
) -> Self {
Self::new(
ProxyConfig::from_env(),
ProxyConfig::from_env(state.proxy_private_ips_via_upstream),
TargetCheckedTcpConnector::new(state),
tls_root_store,
)
+37 -21
View File
@@ -119,27 +119,43 @@ async fn mitm_upstream_client_trusts_startup_custom_ca() {
}
#[test]
fn inherited_upstream_proxy_is_bypassed_for_non_public_targets() {
fn private_ip_upstream_routing_is_opt_in() {
let proxy = ProxyAddress::try_from("http://127.0.0.1:43128").unwrap();
let config = ProxyConfig {
http: Some(proxy.clone()),
https: Some(proxy),
all: None,
};
for target in [
HostWithPort::new(Host::LOCALHOST_NAME, 8080),
HostWithPort::new(Host::LOCALHOST_IPV4, 8080),
HostWithPort::new(Host::Address("10.0.0.1".parse().unwrap()), 8080),
] {
assert_eq!(config.proxy_for_target(&target, /*is_secure*/ false), None);
assert_eq!(config.proxy_for_target(&target, /*is_secure*/ true), None);
for enabled in [false, true] {
let config = ProxyConfig {
http: Some(proxy.clone()),
https: Some(proxy.clone()),
proxy_private_ips_via_upstream: enabled,
..ProxyConfig::default()
};
for (host, use_proxy) in [
("10.0.0.1", enabled),
("172.16.0.1", enabled),
("192.168.0.1", enabled),
("100.68.58.50", enabled),
("fd00::1", enabled),
("::ffff:100.68.58.50", enabled),
("localhost", false),
("127.0.0.1", false),
("::1", false),
("169.254.169.254", false),
("fe80::1", false),
("0.0.0.0", false),
("224.0.0.1", false),
("192.0.2.1", false),
("240.0.0.1", false),
("example.com", true),
("100.63.255.255", true),
("100.128.0.0", true),
] {
let target = HostWithPort::new(host.parse::<Host>().unwrap(), /*port*/ 443);
for is_secure in [false, true] {
assert_eq!(
config.proxy_for_target(&target, is_secure),
use_proxy.then(|| proxy.clone()),
"host={host}, enabled={enabled}, secure={is_secure}"
);
}
}
}
let public = HostWithPort::new(Host::EXAMPLE_NAME, 443);
assert!(
config
.proxy_for_target(&public, /*is_secure*/ true)
.is_some()
);
}
+2 -2
View File
@@ -27,7 +27,7 @@ use codex_core_api::ConfigRequirementsToml;
use codex_core_api::Constrained;
use codex_core_api::EnvironmentManager;
use codex_core_api::EventMsg;
use codex_core_api::ExecServerRuntimePaths;
use codex_core_api::ExecServerRuntimeOptions;
use codex_core_api::ExtensionRegistryBuilder;
use codex_core_api::Feature;
use codex_core_api::Features;
@@ -125,7 +125,7 @@ async fn run_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
let auth_manager =
AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ false).await?;
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
let local_runtime_paths = ExecServerRuntimeOptions::from_optional_paths(
config.codex_self_exe.clone(),
config.codex_linux_sandbox_exe.clone(),
)?;
+2 -2
View File
@@ -7713,7 +7713,7 @@ async fn remote_exec_resume_current_cwd_is_rejected() -> Result<()> {
app.environment_manager = Arc::new(
EnvironmentManager::create_for_tests(
Some("ws://127.0.0.1:8765".to_string()),
Some(codex_exec_server::ExecServerRuntimePaths::new(
Some(codex_exec_server::ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?),
@@ -7960,7 +7960,7 @@ async fn in_app_resume_uses_configured_or_explicit_cwd() -> Result<()> {
Arc::new(
EnvironmentManager::create_for_tests(
Some("ws://127.0.0.1:8765".to_string()),
Some(codex_exec_server::ExecServerRuntimePaths::new(
Some(codex_exec_server::ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?),
+3 -3
View File
@@ -52,7 +52,7 @@ use codex_config::LoaderOverrides;
use codex_config::format_config_error_with_source;
use codex_config::types::ResumeCwdMode;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_features::Feature;
use codex_login::AuthConfig;
use codex_login::default_client::originator;
@@ -2625,7 +2625,7 @@ requires_openai_auth = {requires_openai_auth}
.cloud_config_bundle(cloud_config_bundle)
.build()
.await?;
let runtime_paths = ExecServerRuntimePaths::new(
let runtime_paths = ExecServerRuntimeOptions::new(
std::env::current_exe()?,
/*codex_linux_sandbox_exe*/ None,
)?;
@@ -3638,7 +3638,7 @@ requires_openai_auth = {requires_openai_auth}
let target = AppServerTarget::Embedded;
let environment_manager = EnvironmentManager::create_for_tests(
Some("ws://127.0.0.1:8765".to_string()),
Some(ExecServerRuntimePaths::new(
Some(ExecServerRuntimeOptions::new(
std::env::current_exe().expect("current exe"),
/*codex_linux_sandbox_exe*/ None,
)?),
+2 -2
View File
@@ -25,7 +25,7 @@ use codex_config::CloudConfigBundleLoader;
use codex_config::ConfigLoadOptions;
use codex_config::LoaderOverrides;
use codex_exec_server::EnvironmentManager;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_protocol::ThreadId;
use codex_utils_cli::CliConfigOverrides;
use codex_utils_home_dir::find_codex_home;
@@ -274,7 +274,7 @@ pub(super) async fn start_app_server_for_session_command(
.clone()
.filter(|_| app_server_target.uses_remote_workspace());
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
let local_runtime_paths = ExecServerRuntimeOptions::from_optional_paths(
arg0_paths.codex_self_exe.clone(),
arg0_paths.codex_linux_sandbox_exe.clone(),
)
+1 -1
View File
@@ -318,7 +318,7 @@ pub(super) async fn run_main_inner(
.clone()
.filter(|_| app_server_target.uses_remote_workspace());
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
let local_runtime_paths = ExecServerRuntimeOptions::from_optional_paths(
arg0_paths.codex_self_exe.clone(),
arg0_paths.codex_linux_sandbox_exe.clone(),
)?;
+1 -1
View File
@@ -139,7 +139,7 @@ pub(super) async fn prepare(
color_eyre::eyre::bail!("`--worktree` is only supported for local sessions");
}
let environment = prepared.build(
Some(ExecServerRuntimePaths::from_optional_paths(
Some(ExecServerRuntimeOptions::from_optional_paths(
arg0_paths.codex_self_exe.clone(),
arg0_paths.codex_linux_sandbox_exe.clone(),
)?),