Files
open-matt b8d5e3f12e 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
2026-09-26 23:17:41 +00:00

177 lines
6.0 KiB
Rust

use std::fmt;
use std::sync::Arc;
use anyhow::Result;
use codex_exec_server::Environment;
use codex_exec_server::ExecServerRuntimeOptions;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::FileSystemSandboxContext;
use codex_exec_server::LocalFileSystem;
use codex_exec_server::WindowsSandboxSelection;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSandboxPolicy;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use crate::common::exec_server::ExecServerHarness;
use crate::common::exec_server::TestCodexHelperPaths;
use crate::common::exec_server::exec_server;
use crate::common::exec_server::test_codex_helper_paths;
pub(crate) struct FileSystemContext {
pub(crate) file_system: Arc<dyn ExecutorFileSystem>,
_helper_paths: Option<TestCodexHelperPaths>,
_server: Option<ExecServerHarness>,
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum FileSystemImplementation {
Local,
Remote,
}
impl fmt::Display for FileSystemImplementation {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Local => formatter.write_str("local"),
Self::Remote => formatter.write_str("remote"),
}
}
}
pub(crate) async fn create_file_system_context(
implementation: FileSystemImplementation,
) -> Result<FileSystemContext> {
match implementation {
FileSystemImplementation::Local => {
let helper_paths = test_codex_helper_paths()?;
let runtime_paths = ExecServerRuntimeOptions::new(
helper_paths.codex_exe.clone(),
helper_paths.codex_linux_sandbox_exe.clone(),
)?;
Ok(FileSystemContext {
file_system: Arc::new(LocalFileSystem::with_runtime_paths(runtime_paths)),
_helper_paths: Some(helper_paths),
_server: None,
})
}
FileSystemImplementation::Remote => {
let server = exec_server().await?;
let environment =
Environment::create_for_tests(Some(server.websocket_url().to_string()))?;
Ok(FileSystemContext {
file_system: environment.get_filesystem(),
_helper_paths: None,
_server: Some(server),
})
}
}
}
#[cfg(windows)]
pub(crate) fn is_unsupported_restricted_token_host<T>(result: &std::io::Result<T>) -> bool {
result
.as_ref()
.err()
.is_some_and(|err| err.to_string().contains("CreateRestrictedToken failed: 87"))
}
pub(crate) fn absolute_path(path: std::path::PathBuf) -> AbsolutePathBuf {
assert!(
path.is_absolute(),
"path must be absolute: {}",
path.display()
);
AbsolutePathBuf::try_from(path).expect("path should be absolute")
}
pub(crate) fn read_only_sandbox(readable_root: std::path::PathBuf) -> FileSystemSandboxContext {
let readable_root = absolute_path(readable_root);
let cwd = PathUri::from_abs_path(&readable_root);
let entries = vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: readable_root.into(),
},
access: FileSystemAccessMode::Read,
missing_path_behavior: None,
}];
sandbox_context(entries, cwd)
}
#[cfg(not(windows))]
pub(crate) fn workspace_write_sandbox(
writable_root: std::path::PathBuf,
) -> FileSystemSandboxContext {
let writable_root = absolute_path(writable_root);
let cwd = PathUri::from_abs_path(&writable_root);
let entries = vec![FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: writable_root.into(),
},
access: FileSystemAccessMode::Write,
missing_path_behavior: None,
}];
sandbox_context(entries, cwd)
}
#[cfg(windows)]
pub(crate) fn workspace_write_sandbox(
writable_root: std::path::PathBuf,
) -> FileSystemSandboxContext {
let writable_root = absolute_path(writable_root);
// Keep the runtime policy aligned with the legacy workspace-write projection used by the
// unelevated restricted-token preflight.
let policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry::new(
FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
FileSystemAccessMode::Read,
),
FileSystemSandboxEntry::new(
FileSystemPath::Special {
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
FileSystemAccessMode::Write,
),
]);
let mut sandbox = FileSystemSandboxContext::from_permission_profile(
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
PathUri::from_abs_path(&writable_root),
);
sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken;
sandbox
}
fn sandbox_context(
mut entries: Vec<FileSystemSandboxEntry>,
cwd: PathUri,
) -> FileSystemSandboxContext {
if cfg!(windows) {
// Restricted-token sandboxing cannot enforce read restrictions, so leave the root
// readable while exercising the requested write restrictions.
entries.push(FileSystemSandboxEntry::new(
FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
FileSystemAccessMode::Read,
));
}
let mut sandbox = FileSystemSandboxContext::from_permission_profile(
PermissionProfile::from_runtime_permissions(
&FileSystemSandboxPolicy::restricted(entries),
NetworkSandboxPolicy::Restricted,
),
cwd,
);
if cfg!(windows) {
sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken;
}
sandbox
}