mirror of
https://github.com/openai/codex.git
synced 2026-09-09 15:58:47 +00:00
codex: add exec-server managed-network follow-up
Split the remote managed-network wiring and verification fixes out of the main exec-server sandbox PR. This keeps the original review-response branch focused while carrying the exec-server-owned proxy startup, the unsupported approval-callback guard, and the added regression coverage in a stacked follow-up. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
1
codex-rs/Cargo.lock
generated
1
codex-rs/Cargo.lock
generated
@@ -2064,6 +2064,7 @@ dependencies = [
|
||||
"clap",
|
||||
"codex-app-server-protocol",
|
||||
"codex-linux-sandbox",
|
||||
"codex-network-proxy",
|
||||
"codex-protocol",
|
||||
"codex-sandboxing",
|
||||
"codex-utils-absolute-path",
|
||||
|
||||
@@ -1279,6 +1279,18 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn managed_network_proxy_spec(
|
||||
&self,
|
||||
) -> Option<crate::config::NetworkProxySpec> {
|
||||
let state = self.state.lock().await;
|
||||
state
|
||||
.session_configuration
|
||||
.original_config_do_not_use
|
||||
.permissions
|
||||
.network
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Builds the `x-codex-beta-features` header value for this session.
|
||||
///
|
||||
/// `ModelClient` is session-scoped and intentionally does not depend on the full `Config`, so
|
||||
|
||||
@@ -78,6 +78,25 @@ impl NetworkProxySpec {
|
||||
self.config.network.enabled
|
||||
}
|
||||
|
||||
pub(crate) fn config(&self) -> &NetworkProxyConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
pub(crate) fn constraints(&self) -> &NetworkProxyConstraints {
|
||||
&self.constraints
|
||||
}
|
||||
|
||||
pub(crate) fn requires_remote_approval_callbacks(
|
||||
&self,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
) -> bool {
|
||||
!self.hard_deny_allowlist_misses
|
||||
&& matches!(
|
||||
sandbox_policy,
|
||||
SandboxPolicy::ReadOnly { .. } | SandboxPolicy::WorkspaceWrite { .. }
|
||||
)
|
||||
}
|
||||
|
||||
pub fn proxy_host_and_port(&self) -> String {
|
||||
host_and_port_from_network_addr(&self.config.network.proxy_url, /*default_port*/ 3128)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,32 @@ fn build_state_with_audit_metadata_threads_metadata_to_state() {
|
||||
assert_eq!(state.audit_metadata(), &metadata);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requires_remote_approval_callbacks_only_for_restricted_expandable_managed_network() {
|
||||
let expandable_spec = NetworkProxySpec {
|
||||
config: NetworkProxyConfig::default(),
|
||||
constraints: NetworkProxyConstraints::default(),
|
||||
hard_deny_allowlist_misses: false,
|
||||
};
|
||||
assert!(
|
||||
expandable_spec.requires_remote_approval_callbacks(&SandboxPolicy::new_read_only_policy())
|
||||
);
|
||||
assert!(
|
||||
expandable_spec
|
||||
.requires_remote_approval_callbacks(&SandboxPolicy::new_workspace_write_policy())
|
||||
);
|
||||
assert!(!expandable_spec.requires_remote_approval_callbacks(&SandboxPolicy::DangerFullAccess));
|
||||
|
||||
let hard_deny_spec = NetworkProxySpec {
|
||||
config: NetworkProxyConfig::default(),
|
||||
constraints: NetworkProxyConstraints::default(),
|
||||
hard_deny_allowlist_misses: true,
|
||||
};
|
||||
assert!(
|
||||
!hard_deny_spec.requires_remote_approval_callbacks(&SandboxPolicy::new_read_only_policy())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn requirements_allowed_domains_are_a_baseline_for_user_allowlist() {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
|
||||
@@ -33,7 +33,9 @@ use crate::unified_exec::NoopSpawnLifecycle;
|
||||
use crate::unified_exec::UnifiedExecError;
|
||||
use crate::unified_exec::UnifiedExecProcess;
|
||||
use crate::unified_exec::UnifiedExecProcessManager;
|
||||
use codex_exec_server::ManagedNetworkConfig;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
@@ -83,6 +85,11 @@ pub struct UnifiedExecRuntime<'a> {
|
||||
shell_mode: UnifiedExecShellMode,
|
||||
}
|
||||
|
||||
struct RemoteManagedNetworkLaunch {
|
||||
config: ManagedNetworkConfig,
|
||||
requires_approval_callbacks: bool,
|
||||
}
|
||||
|
||||
fn build_remote_exec_sandbox_config(
|
||||
attempt: &SandboxAttempt<'_>,
|
||||
additional_permissions: Option<PermissionProfile>,
|
||||
@@ -105,6 +112,35 @@ fn build_remote_exec_sandbox_config(
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_remote_exec_managed_network_launch(
|
||||
session: &crate::codex::Session,
|
||||
sandbox_policy: &codex_protocol::protocol::SandboxPolicy,
|
||||
) -> Option<RemoteManagedNetworkLaunch> {
|
||||
let spec = session.managed_network_proxy_spec().await?;
|
||||
let spec = spec
|
||||
.with_exec_policy_network_rules(session.services.exec_policy.current().as_ref())
|
||||
.map_err(|err| {
|
||||
tracing::warn!(
|
||||
"failed to apply execpolicy network rules to remote managed proxy; continuing with configured network policy: {err}"
|
||||
);
|
||||
err
|
||||
})
|
||||
.unwrap_or_else(|_| spec.clone());
|
||||
|
||||
Some(RemoteManagedNetworkLaunch {
|
||||
config: ManagedNetworkConfig {
|
||||
config: spec.config().clone(),
|
||||
constraints: spec.constraints().clone(),
|
||||
audit_metadata: NetworkProxyAuditMetadata {
|
||||
conversation_id: Some(session.conversation_id.to_string()),
|
||||
app_version: Some(env!("CARGO_PKG_VERSION").to_string()),
|
||||
..NetworkProxyAuditMetadata::default()
|
||||
},
|
||||
},
|
||||
requires_approval_callbacks: spec.requires_remote_approval_callbacks(sandbox_policy),
|
||||
})
|
||||
}
|
||||
|
||||
impl<'a> UnifiedExecRuntime<'a> {
|
||||
/// Creates a runtime bound to the shared unified-exec process manager.
|
||||
pub fn new(manager: &'a UnifiedExecProcessManager, shell_mode: UnifiedExecShellMode) -> Self {
|
||||
@@ -238,10 +274,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
command
|
||||
};
|
||||
|
||||
let mut env = req.env.clone();
|
||||
if let Some(network) = req.network.as_ref() {
|
||||
network.apply_to_env(&mut env);
|
||||
}
|
||||
let base_env = req.env.clone();
|
||||
if let Some(environment) = ctx
|
||||
.turn
|
||||
.environment
|
||||
@@ -254,17 +287,38 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
let managed_network = if req.network.is_some() {
|
||||
let managed_network =
|
||||
build_remote_exec_managed_network_launch(ctx.session.as_ref(), attempt.policy)
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
ToolError::Rejected(
|
||||
"remote unified_exec is missing managed-network proxy configuration"
|
||||
.to_string(),
|
||||
)
|
||||
})?;
|
||||
if managed_network.requires_approval_callbacks {
|
||||
return Err(ToolError::Rejected(
|
||||
"remote unified_exec does not yet support managed-network approval callbacks"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
Some(managed_network.config)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let exec_params = codex_exec_server::ExecParams {
|
||||
process_id: req.process_id.to_string().into(),
|
||||
argv: command,
|
||||
cwd: req.cwd.clone(),
|
||||
env,
|
||||
env: base_env.clone(),
|
||||
tty: req.tty,
|
||||
arg0: None,
|
||||
sandbox: build_remote_exec_sandbox_config(
|
||||
attempt,
|
||||
req.additional_permissions.clone(),
|
||||
),
|
||||
managed_network,
|
||||
};
|
||||
return self
|
||||
.manager
|
||||
@@ -280,6 +334,10 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
|
||||
other => ToolError::Rejected(other.to_string()),
|
||||
});
|
||||
}
|
||||
let mut env = base_env;
|
||||
if let Some(network) = req.network.as_ref() {
|
||||
network.apply_to_env(&mut env);
|
||||
}
|
||||
let Some(environment) = ctx.turn.environment.as_ref() else {
|
||||
return Err(ToolError::Rejected(
|
||||
"exec_command is unavailable in this session".to_string(),
|
||||
|
||||
@@ -610,6 +610,7 @@ impl UnifiedExecProcessManager {
|
||||
tty,
|
||||
arg0: request.arg0.clone(),
|
||||
sandbox: codex_sandboxing::SandboxLaunchConfig::no_sandbox(request.cwd.clone()),
|
||||
managed_network: None,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| UnifiedExecError::create_process(err.to_string()))?;
|
||||
|
||||
@@ -20,6 +20,7 @@ arc-swap = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
codex-network-proxy = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-sandboxing = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
|
||||
@@ -280,6 +280,7 @@ mod tests {
|
||||
sandbox: SandboxLaunchConfig::no_sandbox(
|
||||
std::env::current_dir().expect("read current dir"),
|
||||
),
|
||||
managed_network: None,
|
||||
})
|
||||
.await
|
||||
.expect("start process");
|
||||
|
||||
@@ -54,6 +54,7 @@ pub use protocol::ExecParams;
|
||||
pub use protocol::ExecResponse;
|
||||
pub use protocol::InitializeParams;
|
||||
pub use protocol::InitializeResponse;
|
||||
pub use protocol::ManagedNetworkConfig;
|
||||
pub use protocol::ReadParams;
|
||||
pub use protocol::ReadResponse;
|
||||
pub use protocol::TerminateParams;
|
||||
|
||||
@@ -9,6 +9,13 @@ use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_network_proxy::ConfigReloader;
|
||||
use codex_network_proxy::ConfigState;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::NetworkProxyHandle;
|
||||
use codex_network_proxy::NetworkProxyState;
|
||||
use codex_network_proxy::build_config_state;
|
||||
use codex_network_proxy::validate_policy_against_constraints;
|
||||
use codex_sandboxing::SandboxCommand;
|
||||
use codex_sandboxing::SandboxExecRequest;
|
||||
use codex_sandboxing::SandboxType;
|
||||
@@ -62,6 +69,7 @@ struct RetainedOutputChunk {
|
||||
|
||||
struct RunningProcess {
|
||||
session: ExecCommandSession,
|
||||
_managed_network: Option<ManagedNetworkRuntime>,
|
||||
tty: bool,
|
||||
output: VecDeque<RetainedOutputChunk>,
|
||||
retained_bytes: usize,
|
||||
@@ -122,6 +130,36 @@ struct StartedProcess {
|
||||
wake_tx: watch::Sender<u64>,
|
||||
}
|
||||
|
||||
struct ManagedNetworkRuntime {
|
||||
proxy: NetworkProxy,
|
||||
_handle: NetworkProxyHandle,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct StaticNetworkProxyReloader {
|
||||
state: ConfigState,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ConfigReloader for StaticNetworkProxyReloader {
|
||||
async fn maybe_reload(&self) -> anyhow::Result<Option<ConfigState>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn reload_now(&self) -> anyhow::Result<ConfigState> {
|
||||
Ok(self.state.clone())
|
||||
}
|
||||
|
||||
fn source_label(&self) -> String {
|
||||
"ExecServerStaticNetworkProxyReloader".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
struct PreparedExecLaunch {
|
||||
request: SandboxExecRequest,
|
||||
managed_network: Option<ManagedNetworkRuntime>,
|
||||
}
|
||||
|
||||
impl Default for LocalProcess {
|
||||
fn default() -> Self {
|
||||
let (outgoing_tx, mut outgoing_rx) =
|
||||
@@ -197,8 +235,9 @@ impl LocalProcess {
|
||||
async fn start_process(&self, params: ExecParams) -> Result<StartedProcess, JSONRPCErrorError> {
|
||||
self.require_initialized_for("exec")?;
|
||||
let process_id = params.process_id.clone();
|
||||
let launch = prepare_exec_launch(¶ms, &self.inner.runtime)?;
|
||||
let launch = prepare_exec_launch(¶ms, &self.inner.runtime).await?;
|
||||
let (program, args) = launch
|
||||
.request
|
||||
.command
|
||||
.split_first()
|
||||
.ok_or_else(|| invalid_params("argv must not be empty".to_string()))?;
|
||||
@@ -218,8 +257,8 @@ impl LocalProcess {
|
||||
program,
|
||||
args,
|
||||
params.cwd.as_path(),
|
||||
&launch.env,
|
||||
&launch.arg0,
|
||||
&launch.request.env,
|
||||
&launch.request.arg0,
|
||||
TerminalSize::default(),
|
||||
)
|
||||
.await
|
||||
@@ -228,8 +267,8 @@ impl LocalProcess {
|
||||
program,
|
||||
args,
|
||||
params.cwd.as_path(),
|
||||
&launch.env,
|
||||
&launch.arg0,
|
||||
&launch.request.env,
|
||||
&launch.request.arg0,
|
||||
)
|
||||
.await
|
||||
};
|
||||
@@ -252,6 +291,7 @@ impl LocalProcess {
|
||||
process_id.clone(),
|
||||
ProcessEntry::Running(Box::new(RunningProcess {
|
||||
session: spawned.session,
|
||||
_managed_network: launch.managed_network,
|
||||
tty: params.tty,
|
||||
output: VecDeque::new(),
|
||||
retained_bytes: 0,
|
||||
@@ -296,7 +336,7 @@ impl LocalProcess {
|
||||
|
||||
Ok(StartedProcess {
|
||||
process_id,
|
||||
sandbox_type: launch.sandbox,
|
||||
sandbox_type: launch.request.sandbox,
|
||||
wake_tx,
|
||||
})
|
||||
}
|
||||
@@ -513,27 +553,70 @@ fn build_sandbox_command(
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_exec_launch(
|
||||
async fn start_managed_network_runtime(
|
||||
config: &crate::protocol::ManagedNetworkConfig,
|
||||
) -> Result<ManagedNetworkRuntime, JSONRPCErrorError> {
|
||||
validate_policy_against_constraints(&config.config, &config.constraints)
|
||||
.map_err(|err| internal_error(format!("invalid managed network config: {err}")))?;
|
||||
let state =
|
||||
build_config_state(config.config.clone(), config.constraints.clone()).map_err(|err| {
|
||||
internal_error(format!(
|
||||
"failed to build managed network proxy state: {err}"
|
||||
))
|
||||
})?;
|
||||
let reloader_state = state.clone();
|
||||
let state = NetworkProxyState::with_reloader_and_audit_metadata(
|
||||
state,
|
||||
Arc::new(StaticNetworkProxyReloader {
|
||||
state: reloader_state,
|
||||
}),
|
||||
config.audit_metadata.clone(),
|
||||
);
|
||||
let proxy = NetworkProxy::builder()
|
||||
.state(Arc::new(state))
|
||||
.build()
|
||||
.await
|
||||
.map_err(|err| internal_error(format!("failed to build managed network proxy: {err}")))?;
|
||||
let handle = proxy
|
||||
.run()
|
||||
.await
|
||||
.map_err(|err| internal_error(format!("failed to run managed network proxy: {err}")))?;
|
||||
Ok(ManagedNetworkRuntime {
|
||||
proxy,
|
||||
_handle: handle,
|
||||
})
|
||||
}
|
||||
|
||||
async fn prepare_exec_launch(
|
||||
params: &ExecParams,
|
||||
runtime: &ExecServerRuntimeConfig,
|
||||
) -> Result<SandboxExecRequest, JSONRPCErrorError> {
|
||||
) -> Result<PreparedExecLaunch, JSONRPCErrorError> {
|
||||
let managed_network = match params.managed_network.as_ref() {
|
||||
Some(config) => Some(start_managed_network_runtime(config).await?),
|
||||
None => None,
|
||||
};
|
||||
let mut env = params.env.clone();
|
||||
if let Some(network) = managed_network.as_ref() {
|
||||
network.proxy.apply_to_env(&mut env);
|
||||
}
|
||||
let command = build_sandbox_command(
|
||||
¶ms.argv,
|
||||
params.cwd.as_path(),
|
||||
¶ms.env,
|
||||
&env,
|
||||
params.sandbox.additional_permissions.clone(),
|
||||
)?;
|
||||
params
|
||||
let request = params
|
||||
.sandbox
|
||||
.transform(
|
||||
command,
|
||||
// TODO: Thread managed-network proxy state across exec-server so
|
||||
// sandbox profile generation preserves proxy-specific allowances.
|
||||
/*network*/
|
||||
None,
|
||||
managed_network.as_ref().map(|network| &network.proxy),
|
||||
runtime.codex_linux_sandbox_exe.as_ref(),
|
||||
)
|
||||
.map_err(|err| internal_error(format!("failed to build sandbox launch: {err}")))
|
||||
.map_err(|err| internal_error(format!("failed to build sandbox launch: {err}")))?;
|
||||
Ok(PreparedExecLaunch {
|
||||
request,
|
||||
managed_network,
|
||||
})
|
||||
}
|
||||
|
||||
impl LocalProcess {
|
||||
|
||||
@@ -2,6 +2,9 @@ use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||
use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
use codex_network_proxy::NetworkProxyConstraints;
|
||||
use codex_sandboxing::SandboxLaunchConfig;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -51,6 +54,14 @@ pub struct InitializeParams {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct InitializeResponse {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ManagedNetworkConfig {
|
||||
pub config: NetworkProxyConfig,
|
||||
pub constraints: NetworkProxyConstraints,
|
||||
pub audit_metadata: NetworkProxyAuditMetadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExecParams {
|
||||
@@ -63,6 +74,7 @@ pub struct ExecParams {
|
||||
pub tty: bool,
|
||||
pub arg0: Option<String>,
|
||||
pub sandbox: SandboxLaunchConfig,
|
||||
pub managed_network: Option<ManagedNetworkConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
@@ -31,6 +31,7 @@ fn exec_params(process_id: &str) -> ExecParams {
|
||||
tty: false,
|
||||
arg0: None,
|
||||
sandbox: SandboxLaunchConfig::no_sandbox(std::env::current_dir().expect("cwd")),
|
||||
managed_network: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,13 @@ use codex_exec_server::Environment;
|
||||
use codex_exec_server::ExecBackend;
|
||||
use codex_exec_server::ExecParams;
|
||||
use codex_exec_server::ExecProcess;
|
||||
use codex_exec_server::ManagedNetworkConfig;
|
||||
use codex_exec_server::ProcessId;
|
||||
use codex_exec_server::ReadResponse;
|
||||
use codex_exec_server::StartedExecProcess;
|
||||
use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
use codex_network_proxy::NetworkProxyConstraints;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
@@ -63,6 +67,7 @@ async fn assert_exec_process_starts_and_exits(use_remote: bool) -> Result<()> {
|
||||
tty: false,
|
||||
arg0: None,
|
||||
sandbox: SandboxLaunchConfig::no_sandbox(cwd),
|
||||
managed_network: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), "proc-1");
|
||||
@@ -141,6 +146,7 @@ async fn assert_exec_process_streams_output(use_remote: bool) -> Result<()> {
|
||||
tty: false,
|
||||
arg0: None,
|
||||
sandbox: SandboxLaunchConfig::no_sandbox(cwd),
|
||||
managed_network: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
@@ -172,6 +178,7 @@ async fn assert_exec_process_write_then_read(use_remote: bool) -> Result<()> {
|
||||
tty: true,
|
||||
arg0: None,
|
||||
sandbox: SandboxLaunchConfig::no_sandbox(cwd),
|
||||
managed_network: None,
|
||||
})
|
||||
.await?;
|
||||
assert_eq!(session.process.process_id().as_str(), process_id);
|
||||
@@ -210,6 +217,7 @@ async fn assert_exec_process_preserves_queued_events_before_subscribe(
|
||||
tty: false,
|
||||
arg0: None,
|
||||
sandbox: SandboxLaunchConfig::no_sandbox(cwd),
|
||||
managed_network: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -262,13 +270,32 @@ fn write_outside_workspace_sandbox(workspace_root: &std::path::Path) -> SandboxL
|
||||
}
|
||||
}
|
||||
|
||||
fn managed_network_config() -> ManagedNetworkConfig {
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.network.enabled = true;
|
||||
ManagedNetworkConfig {
|
||||
config,
|
||||
constraints: NetworkProxyConstraints {
|
||||
enabled: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
audit_metadata: NetworkProxyAuditMetadata {
|
||||
conversation_id: Some("exec-server-smoke".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn assert_exec_process_sandbox_denies_write_outside_workspace(
|
||||
use_remote: bool,
|
||||
) -> Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let workspace_root = temp_dir.path().join("workspace");
|
||||
std::fs::create_dir(&workspace_root)?;
|
||||
let blocked_path = temp_dir.path().join("blocked.txt");
|
||||
let blocked_dir = tempfile::Builder::new()
|
||||
.prefix("exec-server-sandbox-blocked-")
|
||||
.tempdir_in(std::env::current_dir()?)?;
|
||||
let blocked_path = blocked_dir.path().join("blocked.txt");
|
||||
let context = create_process_context(use_remote).await?;
|
||||
let session = context
|
||||
.backend
|
||||
@@ -286,6 +313,7 @@ async fn assert_exec_process_sandbox_denies_write_outside_workspace(
|
||||
tty: false,
|
||||
arg0: None,
|
||||
sandbox: write_outside_workspace_sandbox(&workspace_root),
|
||||
managed_network: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -303,6 +331,44 @@ async fn assert_exec_process_sandbox_denies_write_outside_workspace(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn assert_remote_exec_process_applies_managed_network_proxy_env() -> Result<()> {
|
||||
let context = create_process_context(/*use_remote*/ true).await?;
|
||||
let session = context
|
||||
.backend
|
||||
.start(ExecParams {
|
||||
process_id: ProcessId::from("proc-managed-network"),
|
||||
argv: vec![
|
||||
"/usr/bin/python3".to_string(),
|
||||
"-c".to_string(),
|
||||
"import os; print('HTTP_PROXY=' + os.environ['HTTP_PROXY']); print('HTTPS_PROXY=' + os.environ['HTTPS_PROXY'])"
|
||||
.to_string(),
|
||||
],
|
||||
cwd: std::env::current_dir()?,
|
||||
env: Default::default(),
|
||||
tty: false,
|
||||
arg0: None,
|
||||
sandbox: SandboxLaunchConfig::no_sandbox(std::env::current_dir()?),
|
||||
managed_network: Some(managed_network_config()),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let StartedExecProcess { process, .. } = session;
|
||||
let wake_rx = process.subscribe_wake();
|
||||
let (output, exit_code, closed) = collect_process_output_from_reads(process, wake_rx).await?;
|
||||
|
||||
assert!(
|
||||
output.contains("HTTP_PROXY=http://127.0.0.1:"),
|
||||
"expected HTTP proxy env from managed network runtime, got {output:?}"
|
||||
);
|
||||
assert!(
|
||||
output.contains("HTTPS_PROXY=http://127.0.0.1:"),
|
||||
"expected HTTPS proxy env from managed network runtime, got {output:?}"
|
||||
);
|
||||
assert_eq!(exit_code, Some(0));
|
||||
assert!(closed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn remote_exec_process_reports_transport_disconnect() -> Result<()> {
|
||||
let mut context = create_process_context(/*use_remote*/ true).await?;
|
||||
@@ -322,6 +388,7 @@ async fn remote_exec_process_reports_transport_disconnect() -> Result<()> {
|
||||
sandbox: SandboxLaunchConfig::no_sandbox(
|
||||
std::env::current_dir().expect("read current dir"),
|
||||
),
|
||||
managed_network: None,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -381,3 +448,8 @@ async fn exec_process_preserves_queued_events_before_subscribe(use_remote: bool)
|
||||
async fn remote_exec_process_sandbox_denies_write_outside_workspace() -> Result<()> {
|
||||
assert_exec_process_sandbox_denies_write_outside_workspace(/*use_remote*/ true).await
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn remote_exec_process_applies_managed_network_proxy_env() -> Result<()> {
|
||||
assert_remote_exec_process_applies_managed_network_proxy_env().await
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use globset::GlobSet;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::VecDeque;
|
||||
@@ -39,7 +40,7 @@ const MAX_BLOCKED_EVENTS: usize = 200;
|
||||
const DNS_LOOKUP_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
const NETWORK_POLICY_VIOLATION_PREFIX: &str = "CODEX_NETWORK_POLICY_VIOLATION";
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct NetworkProxyAuditMetadata {
|
||||
pub conversation_id: Option<String>,
|
||||
pub app_version: Option<String>,
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::policy::compile_denylist_globset;
|
||||
use crate::policy::is_global_wildcard_domain_pattern;
|
||||
use crate::runtime::ConfigState;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -19,7 +20,7 @@ pub use crate::runtime::NetworkProxyState;
|
||||
#[cfg(test)]
|
||||
pub(crate) use crate::runtime::network_proxy_state_for_policy;
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct NetworkProxyConstraints {
|
||||
pub enabled: Option<bool>,
|
||||
pub mode: Option<NetworkMode>,
|
||||
|
||||
Reference in New Issue
Block a user