Preserve Windows sandbox proxy settings in guardian sessions (#35036)

## Why

Guardian review commands can run without the proxy-port environment used to
configure the parent session. Reconciling persistent Windows sandbox settings
for those commands can discard the parent's proxy configuration.

## What changed

- Add a session-level Windows sandbox proxy-settings mode and use `Preserve`
  for guardian review sessions while keeping `Reconcile` as the default.
- Carry the mode through unified exec and the exec-server sandbox context to
  Windows process launches.
- In preserve mode, reuse the proxy settings recorded by the existing sandbox
  setup marker.

## Testing

Added coverage for guardian session configuration, exec-server transport, and
preserving an existing setup marker when proxy-port environment variables are
absent.

GitOrigin-RevId: 214655c6e6d97360906431773dc0de0fdda2db6e
This commit is contained in:
iceweasel-oai authored and copyberry committed 2026-07-23 23:35:29 +00:00
1 parent d45055ae58
commit b115de97d7
23 files changed
+181 -35

No files matched your search

+3
View File
@@ -87,6 +87,7 @@ pub(crate) async fn run_codex_thread_interactive(
subagent_source: SubAgentSource, subagent_source: SubAgentSource,
initial_history: Option<InitialHistory>, initial_history: Option<InitialHistory>,
git_enrichment_policy: GitEnrichmentPolicy, git_enrichment_policy: GitEnrichmentPolicy,
windows_sandbox_proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode,
) -> Result<(Arc<Session>, SessionIo), CodexErr> { ) -> Result<(Arc<Session>, SessionIo), CodexErr> {
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY); let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
let (tx_ops, rx_ops) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY); let (tx_ops, rx_ops) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
@@ -139,6 +140,7 @@ pub(crate) async fn run_codex_thread_interactive(
external_time_provider: Some(Arc::clone(&parent_session.services.time_provider)), external_time_provider: Some(Arc::clone(&parent_session.services.time_provider)),
inherited_multi_agent_version: Some(MultiAgentVersion::Disabled), inherited_multi_agent_version: Some(MultiAgentVersion::Disabled),
git_enrichment_policy, git_enrichment_policy,
windows_sandbox_proxy_settings_mode,
})) }))
.or_cancel(&cancel_token) .or_cancel(&cancel_token)
.await??; .await??;
@@ -224,6 +226,7 @@ pub(crate) async fn run_codex_thread_one_shot(
subagent_source, subagent_source,
initial_history, initial_history,
GitEnrichmentPolicy::Fresh, GitEnrichmentPolicy::Fresh,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
)) ))
.await?; .await?;
@@ -212,6 +212,7 @@ async fn run_codex_thread_interactive_respects_pre_cancelled_spawn() {
SubAgentSource::Review, SubAgentSource::Review,
/*initial_history*/ None, /*initial_history*/ None,
crate::session::GitEnrichmentPolicy::Fresh, crate::session::GitEnrichmentPolicy::Fresh,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
), ),
) )
.await .await
@@ -680,6 +680,7 @@ async fn spawn_guardian_review_session(
SubAgentSource::Other(GUARDIAN_REVIEWER_NAME.to_string()), SubAgentSource::Other(GUARDIAN_REVIEWER_NAME.to_string()),
initial_history, initial_history,
GitEnrichmentPolicy::Skip, GitEnrichmentPolicy::Skip,
codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve,
)) ))
.await?; .await?;
@@ -1254,6 +1255,31 @@ mod tests {
} }
} }
#[tokio::test]
async fn spawned_guardian_session_preserves_windows_sandbox_proxy_settings() {
let params = test_review_params().await;
let manager = GuardianReviewSessionManager::default();
manager
.initialize(params.parent_session, params.parent_turn)
.await
.expect("initialize Guardian session");
let mode = manager
.state
.lock()
.await
.trunk
.as_ref()
.expect("Guardian session")
.session
.windows_sandbox_proxy_settings_mode;
assert_eq!(
mode,
codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve
);
manager.shutdown().await;
}
#[tokio::test] #[tokio::test]
async fn guardian_review_session_config_change_invalidates_cached_session() { async fn guardian_review_session_config_change_invalidates_cached_session() {
let parent_config = crate::config::test_config().await; let parent_config = crate::config::test_config().await;
+4
View File
@@ -455,6 +455,8 @@ pub(crate) struct SessionSpawnArgs {
pub(crate) external_time_provider: Option<Arc<dyn TimeProvider>>, pub(crate) external_time_provider: Option<Arc<dyn TimeProvider>>,
pub(crate) inherited_multi_agent_version: Option<MultiAgentVersion>, pub(crate) inherited_multi_agent_version: Option<MultiAgentVersion>,
pub(crate) git_enrichment_policy: GitEnrichmentPolicy, pub(crate) git_enrichment_policy: GitEnrichmentPolicy,
pub(crate) windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode,
} }
pub(crate) fn resolve_multi_agent_version( pub(crate) fn resolve_multi_agent_version(
@@ -544,6 +546,7 @@ impl Session {
external_time_provider, external_time_provider,
inherited_multi_agent_version, inherited_multi_agent_version,
git_enrichment_policy, git_enrichment_policy,
windows_sandbox_proxy_settings_mode,
} = args; } = args;
let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY); let (tx_sub, rx_sub) = async_channel::bounded(SUBMISSION_CHANNEL_CAPACITY);
let (tx_event, rx_event) = async_channel::unbounded(); let (tx_event, rx_event) = async_channel::unbounded();
@@ -732,6 +735,7 @@ impl Session {
external_time_provider, external_time_provider,
multi_agent_version, multi_agent_version,
git_enrichment_policy, git_enrichment_policy,
windows_sandbox_proxy_settings_mode,
)) ))
.await .await
.map_err(|e| { .map_err(|e| {
+4
View File
@@ -37,6 +37,8 @@ pub(crate) struct Session {
/// The set of enabled features should be invariant for the lifetime of the /// The set of enabled features should be invariant for the lifetime of the
/// session. /// session.
pub(super) features: ManagedFeatures, pub(super) features: ManagedFeatures,
pub(crate) windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode,
pub(super) multi_agent_version: OnceLock<MultiAgentVersion>, pub(super) multi_agent_version: OnceLock<MultiAgentVersion>,
pub(super) mcp_refresh_pending: std::sync::atomic::AtomicBool, pub(super) mcp_refresh_pending: std::sync::atomic::AtomicBool,
/// Serializes runtime refreshes without blocking calls that own a snapshot. /// Serializes runtime refreshes without blocking calls that own a snapshot.
@@ -514,6 +516,7 @@ impl Session {
external_time_provider: Option<Arc<dyn TimeProvider>>, external_time_provider: Option<Arc<dyn TimeProvider>>,
multi_agent_version: Option<MultiAgentVersion>, multi_agent_version: Option<MultiAgentVersion>,
git_enrichment_policy: GitEnrichmentPolicy, git_enrichment_policy: GitEnrichmentPolicy,
windows_sandbox_proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode,
) -> anyhow::Result<Arc<Self>> { ) -> anyhow::Result<Arc<Self>> {
debug!( debug!(
"Configuring session: model={}; provider={:?}", "Configuring session: model={}; provider={:?}",
@@ -1150,6 +1153,7 @@ impl Session {
state: Mutex::new(state), state: Mutex::new(state),
managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1), managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1),
features: config.features.clone(), features: config.features.clone(),
windows_sandbox_proxy_settings_mode,
multi_agent_version, multi_agent_version,
mcp_refresh_pending: std::sync::atomic::AtomicBool::new(false), mcp_refresh_pending: std::sync::atomic::AtomicBool::new(false),
mcp_refresh_lock: Semaphore::new(/*permits*/ 1), mcp_refresh_lock: Semaphore::new(/*permits*/ 1),
+7
View File
@@ -5288,6 +5288,7 @@ async fn session_new_fails_when_zsh_fork_enabled_without_packaged_zsh() {
/*external_time_provider*/ None, /*external_time_provider*/ None,
Some(config.multi_agent_version_from_features()), Some(config.multi_agent_version_from_features()),
GitEnrichmentPolicy::Fresh, GitEnrichmentPolicy::Fresh,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
) )
.await; .await;
@@ -5539,6 +5540,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
state: Mutex::new(state), state: Mutex::new(state),
managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1), managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1),
features: config.features.clone(), features: config.features.clone(),
windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()), multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()),
mcp_refresh_pending: std::sync::atomic::AtomicBool::new(true), mcp_refresh_pending: std::sync::atomic::AtomicBool::new(true),
mcp_refresh_lock: Semaphore::new(/*permits*/ 1), mcp_refresh_lock: Semaphore::new(/*permits*/ 1),
@@ -5673,6 +5676,7 @@ async fn make_session_with_config_and_rx(
/*external_time_provider*/ None, /*external_time_provider*/ None,
Some(config.multi_agent_version_from_features()), Some(config.multi_agent_version_from_features()),
GitEnrichmentPolicy::Fresh, GitEnrichmentPolicy::Fresh,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
) )
.await?; .await?;
@@ -5788,6 +5792,7 @@ async fn make_session_with_history_source_and_agent_control_and_rx(
/*external_time_provider*/ None, /*external_time_provider*/ None,
Some(config.multi_agent_version_from_features()), Some(config.multi_agent_version_from_features()),
GitEnrichmentPolicy::Fresh, GitEnrichmentPolicy::Fresh,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
) )
.await?; .await?;
@@ -7702,6 +7707,8 @@ where
state: Mutex::new(state), state: Mutex::new(state),
managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1), managed_network_proxy_refresh_lock: Semaphore::new(/*permits*/ 1),
features: config.features.clone(), features: config.features.clone(),
windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()), multi_agent_version: OnceLock::from(config.multi_agent_version_from_features()),
mcp_refresh_pending: std::sync::atomic::AtomicBool::new(true), mcp_refresh_pending: std::sync::atomic::AtomicBool::new(true),
mcp_refresh_lock: Semaphore::new(/*permits*/ 1), mcp_refresh_lock: Semaphore::new(/*permits*/ 1),
@@ -766,6 +766,8 @@ async fn guardian_subagent_does_not_inherit_parent_exec_policy_rules() {
external_time_provider: None, external_time_provider: None,
inherited_multi_agent_version: None, inherited_multi_agent_version: None,
git_enrichment_policy: GitEnrichmentPolicy::Skip, git_enrichment_policy: GitEnrichmentPolicy::Skip,
windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve,
}) })
.await .await
.expect("spawn guardian subagent"); .expect("spawn guardian subagent");
@@ -360,6 +360,7 @@ impl TurnContext {
.config .config
.permissions .permissions
.windows_sandbox_private_desktop, .windows_sandbox_private_desktop,
windows_sandbox_proxy_settings_mode: None,
use_legacy_landlock: self.config.features.use_legacy_landlock(), use_legacy_landlock: self.config.features.use_legacy_landlock(),
} }
} }
+2
View File
@@ -1676,6 +1676,8 @@ impl ThreadManagerState {
external_time_provider: self.external_time_provider.clone(), external_time_provider: self.external_time_provider.clone(),
inherited_multi_agent_version: multi_agent_version, inherited_multi_agent_version: multi_agent_version,
git_enrichment_policy: GitEnrichmentPolicy::Fresh, git_enrichment_policy: GitEnrichmentPolicy::Fresh,
windows_sandbox_proxy_settings_mode:
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
})) }))
.await?; .await?;
let new_thread = self let new_thread = self
@@ -99,6 +99,7 @@ impl ApplyPatchRuntime {
workspace_roots: attempt.workspace_roots.to_vec(), workspace_roots: attempt.workspace_roots.to_vec(),
windows_sandbox_level: attempt.windows_sandbox_level, windows_sandbox_level: attempt.windows_sandbox_level,
windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop, windows_sandbox_private_desktop: attempt.windows_sandbox_private_desktop,
windows_sandbox_proxy_settings_mode: None,
use_legacy_landlock: attempt.use_legacy_landlock, use_legacy_landlock: attempt.use_legacy_landlock,
}) })
} }
@@ -302,6 +302,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
ctx: &ToolCtx, ctx: &ToolCtx,
) -> Result<UnifiedExecProcess, ToolError> { ) -> Result<UnifiedExecProcess, ToolError> {
let base_command = &req.command; let base_command = &req.command;
let windows_sandbox_proxy_settings_mode = ctx.session.windows_sandbox_proxy_settings_mode;
let session_shell = ctx.session.user_shell(); let session_shell = ctx.session.user_shell();
let shell = req let shell = req
.turn_environment .turn_environment
@@ -464,6 +465,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
.open_session_with_prepared_exec_env( .open_session_with_prepared_exec_env(
req.process_id, req.process_id,
&prepared.exec_request, &prepared.exec_request,
windows_sandbox_proxy_settings_mode,
req.tty, req.tty,
prepared.spawn_lifecycle, prepared.spawn_lifecycle,
req.turn_environment.environment.as_ref(), req.turn_environment.environment.as_ref(),
@@ -510,6 +512,7 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecProcess> for UnifiedExecRunt
network_proxy_launch, network_proxy_launch,
/*environment_id*/ Some(&req.turn_environment.environment_id), /*environment_id*/ Some(&req.turn_environment.environment_id),
req.exec_server_env_config.clone(), req.exec_server_env_config.clone(),
windows_sandbox_proxy_settings_mode,
req.tty, req.tty,
Box::new(NoopSpawnLifecycle), Box::new(NoopSpawnLifecycle),
req.turn_environment.environment.as_ref(), req.turn_environment.environment.as_ref(),
+1
View File
@@ -507,6 +507,7 @@ impl<'a> SandboxAttempt<'a> {
workspace_roots: self.workspace_roots.to_vec(), workspace_roots: self.workspace_roots.to_vec(),
windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop, windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
windows_sandbox_proxy_settings_mode: None,
use_legacy_landlock: self.use_legacy_landlock, use_legacy_landlock: self.use_legacy_landlock,
}); });
exec_request.exec_server_enforce_managed_network = self.enforce_managed_network; exec_request.exec_server_enforce_managed_network = self.enforce_managed_network;
@@ -268,6 +268,7 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
workspace_roots: vec![cwd_uri.clone()], workspace_roots: vec![cwd_uri.clone()],
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled, windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
windows_sandbox_proxy_settings_mode: None,
use_legacy_landlock: false, use_legacy_landlock: false,
}) })
); );
@@ -113,6 +113,7 @@ async fn exec_command_with_tty(
.open_session_with_prepared_exec_env( .open_session_with_prepared_exec_env(
process_id, process_id,
&request, &request,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
tty, tty,
Box::new(NoopSpawnLifecycle), Box::new(NoopSpawnLifecycle),
turn.environments turn.environments
@@ -753,6 +754,7 @@ async fn completed_pipe_commands_preserve_exit_code() -> anyhow::Result<()> {
.open_session_with_prepared_exec_env( .open_session_with_prepared_exec_env(
/*process_id*/ 1234, /*process_id*/ 1234,
&request, &request,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
/*tty*/ false, /*tty*/ false,
Box::new(NoopSpawnLifecycle), Box::new(NoopSpawnLifecycle),
&environment, &environment,
@@ -793,6 +795,7 @@ async fn unified_exec_uses_remote_exec_server_when_configured() -> anyhow::Resul
.open_session_with_prepared_exec_env( .open_session_with_prepared_exec_env(
/*process_id*/ 1234, /*process_id*/ 1234,
&request, &request,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
/*tty*/ true, /*tty*/ true,
Box::new(NoopSpawnLifecycle), Box::new(NoopSpawnLifecycle),
remote_test_env.environment(), remote_test_env.environment(),
@@ -851,6 +854,7 @@ async fn remote_exec_server_rejects_inherited_fd_launches() -> anyhow::Result<()
.open_session_with_prepared_exec_env( .open_session_with_prepared_exec_env(
/*process_id*/ 1234, /*process_id*/ 1234,
&request, &request,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
/*tty*/ true, /*tty*/ true,
Box::new(TestSpawnLifecycle { Box::new(TestSpawnLifecycle {
inherited_fds: vec![42], inherited_fds: vec![42],
@@ -177,9 +177,14 @@ fn exec_server_env_for_request(
fn exec_server_params_for_request( fn exec_server_params_for_request(
process_id: i32, process_id: i32,
request: &ExecRequest, request: &ExecRequest,
windows_sandbox_proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode,
tty: bool, tty: bool,
) -> codex_exec_server::ExecParams { ) -> codex_exec_server::ExecParams {
let (env_policy, env) = exec_server_env_for_request(request); let (env_policy, env) = exec_server_env_for_request(request);
let sandbox = request.exec_server_sandbox.clone().map(|mut sandbox| {
sandbox.windows_sandbox_proxy_settings_mode = Some(windows_sandbox_proxy_settings_mode);
sandbox
});
// Sandbox retries reuse the unified-exec ID but start a distinct executor process. // Sandbox retries reuse the unified-exec ID but start a distinct executor process.
let exec_server_process_id = if request.exec_server_sandbox.is_some() { let exec_server_process_id = if request.exec_server_sandbox.is_some() {
format!("{process_id}-{}", Uuid::new_v4()) format!("{process_id}-{}", Uuid::new_v4())
@@ -195,7 +200,7 @@ fn exec_server_params_for_request(
tty, tty,
pipe_stdin: false, pipe_stdin: false,
arg0: request.arg0.clone(), arg0: request.arg0.clone(),
sandbox: request.exec_server_sandbox.clone(), sandbox,
enforce_managed_network: request.exec_server_enforce_managed_network, enforce_managed_network: request.exec_server_enforce_managed_network,
managed_network: request.exec_server_managed_network.clone(), managed_network: request.exec_server_managed_network.clone(),
network_proxy: request.exec_server_network_proxy.clone(), network_proxy: request.exec_server_network_proxy.clone(),
@@ -1003,6 +1008,7 @@ impl UnifiedExecProcessManager {
network_proxy_launch: Option<codex_network_proxy::RemoteNetworkProxyLaunchConfig>, network_proxy_launch: Option<codex_network_proxy::RemoteNetworkProxyLaunchConfig>,
environment_id: Option<&str>, environment_id: Option<&str>,
exec_server_env_config: Option<ExecServerEnvConfig>, exec_server_env_config: Option<ExecServerEnvConfig>,
windows_sandbox_proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode,
tty: bool, tty: bool,
spawn_lifecycle: SpawnLifecycleHandle, spawn_lifecycle: SpawnLifecycleHandle,
environment: &codex_exec_server::Environment, environment: &codex_exec_server::Environment,
@@ -1018,6 +1024,7 @@ impl UnifiedExecProcessManager {
self.open_session_with_prepared_exec_env( self.open_session_with_prepared_exec_env(
process_id, process_id,
&request, &request,
windows_sandbox_proxy_settings_mode,
tty, tty,
spawn_lifecycle, spawn_lifecycle,
environment, environment,
@@ -1038,6 +1045,7 @@ impl UnifiedExecProcessManager {
&self, &self,
process_id: i32, process_id: i32,
request: &ExecRequest, request: &ExecRequest,
windows_sandbox_proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode,
tty: bool, tty: bool,
mut spawn_lifecycle: SpawnLifecycleHandle, mut spawn_lifecycle: SpawnLifecycleHandle,
environment: &codex_exec_server::Environment, environment: &codex_exec_server::Environment,
@@ -1053,7 +1061,12 @@ impl UnifiedExecProcessManager {
let started = environment let started = environment
.get_exec_backend() .get_exec_backend()
.start(exec_server_params_for_request(process_id, request, tty)) .start(exec_server_params_for_request(
process_id,
request,
windows_sandbox_proxy_settings_mode,
tty,
))
.await .await
.map_err(|err| UnifiedExecError::create_process(err.to_string()))?; .map_err(|err| UnifiedExecError::create_process(err.to_string()))?;
spawn_lifecycle.after_spawn(); spawn_lifecycle.after_spawn();
@@ -1100,22 +1113,21 @@ impl UnifiedExecProcessManager {
None::<String> None::<String>
} }
}; };
let windows_sandbox = if request.sandbox let windows_sandbox =
== codex_sandboxing::SandboxType::WindowsRestrictedToken if request.sandbox == codex_sandboxing::SandboxType::WindowsRestrictedToken {
{ Some(codex_sandboxing::WindowsSandboxSpawnRequest {
Some(codex_sandboxing::WindowsSandboxSpawnRequest { permission_profile: &request.permission_profile,
permission_profile: &request.permission_profile, workspace_roots: &request.windows_sandbox_workspace_roots,
workspace_roots: &request.windows_sandbox_workspace_roots, windows_sandbox_level: request.windows_sandbox_level,
windows_sandbox_level: request.windows_sandbox_level, proxy_enforced: request.network.is_some(),
proxy_enforced: request.network.is_some(), network_proxy_restricting_sid: network_proxy_restricting_sid.as_deref(),
network_proxy_restricting_sid: network_proxy_restricting_sid.as_deref(), proxy_settings_mode: windows_sandbox_proxy_settings_mode,
proxy_settings_mode: codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile, filesystem_overrides: request.windows_sandbox_filesystem_overrides.as_ref(),
filesystem_overrides: request.windows_sandbox_filesystem_overrides.as_ref(), use_private_desktop: request.windows_sandbox_private_desktop,
use_private_desktop: request.windows_sandbox_private_desktop, })
}) } else {
} else { None
None };
};
let spawn_result = codex_sandboxing::spawn_process(codex_sandboxing::SpawnRequest { let spawn_result = codex_sandboxing::spawn_process(codex_sandboxing::SpawnRequest {
command: &request.command, command: &request.command,
cwd: native_cwd.as_path(), cwd: native_cwd.as_path(),
@@ -177,8 +177,16 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() {
exec_server_network_proxy: None, exec_server_network_proxy: None,
}; };
let params = let proxy_settings_mode = codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve;
exec_server_params_for_request(/*process_id*/ 123, &request, /*tty*/ true); let params_for_request = |request: &ExecRequest| {
exec_server_params_for_request(
/*process_id*/ 123,
request,
proxy_settings_mode,
/*tty*/ true,
)
};
let params = params_for_request(&request);
assert_eq!(params.process_id.as_str(), "123"); assert_eq!(params.process_id.as_str(), "123");
assert_eq!(params.cwd, request.cwd); assert_eq!(params.cwd, request.cwd);
@@ -200,10 +208,15 @@ fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() {
request.exec_server_sandbox = Some( request.exec_server_sandbox = Some(
codex_exec_server::FileSystemSandboxContext::from_permission_profile(permission_profile), codex_exec_server::FileSystemSandboxContext::from_permission_profile(permission_profile),
); );
let first = let first = params_for_request(&request);
exec_server_params_for_request(/*process_id*/ 123, &request, /*tty*/ true); let second = params_for_request(&request);
let second = assert_eq!(
exec_server_params_for_request(/*process_id*/ 123, &request, /*tty*/ true); first
.sandbox
.as_ref()
.and_then(|sandbox| sandbox.windows_sandbox_proxy_settings_mode),
Some(codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve)
);
assert!(first.process_id.as_str().starts_with("123-")); assert!(first.process_id.as_str().starts_with("123-"));
assert!(second.process_id.as_str().starts_with("123-")); assert!(second.process_id.as_str().starts_with("123-"));
assert_ne!(first.process_id, second.process_id); assert_ne!(first.process_id, second.process_id);
@@ -743,6 +743,7 @@ mod tests {
use codex_network_proxy::NetworkProxyConfig; use codex_network_proxy::NetworkProxyConfig;
use codex_network_proxy::RemoteNetworkProxyConfig; use codex_network_proxy::RemoteNetworkProxyConfig;
use codex_network_proxy::RemoteNetworkProxyLaunchConfig; use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
use codex_protocol::config_types::WindowsSandboxProxySettingsMode;
use codex_protocol::models::ManagedFileSystemPermissions; use codex_protocol::models::ManagedFileSystemPermissions;
use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::FileSystemAccessMode; use codex_protocol::permissions::FileSystemAccessMode;
@@ -938,6 +939,17 @@ mod tests {
.expect("deserialize sandbox"), .expect("deserialize sandbox"),
sandbox sandbox
); );
let preserve = FileSystemSandboxContext {
windows_sandbox_proxy_settings_mode: Some(WindowsSandboxProxySettingsMode::Preserve),
..sandbox
};
let serialized = serde_json::to_value(&preserve).expect("serialize preserve mode");
assert_eq!(serialized["windowsSandboxProxySettingsMode"], "preserve");
assert_eq!(
serde_json::from_value::<FileSystemSandboxContext>(serialized)
.expect("deserialize preserve mode"),
preserve
);
} }
#[test] #[test]
+7 -3
View File
@@ -52,6 +52,7 @@ struct PreparedWindowsSandboxRequest {
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel, windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
proxy_enforced: bool, proxy_enforced: bool,
network_proxy_restricting_sid: Option<String>, network_proxy_restricting_sid: Option<String>,
proxy_settings_mode: WindowsSandboxProxySettingsMode,
filesystem_overrides: Option<WindowsSandboxFilesystemOverrides>, filesystem_overrides: Option<WindowsSandboxFilesystemOverrides>,
use_private_desktop: bool, use_private_desktop: bool,
} }
@@ -66,7 +67,7 @@ impl PreparedExecRequest {
windows_sandbox_level: request.windows_sandbox_level, windows_sandbox_level: request.windows_sandbox_level,
proxy_enforced: request.proxy_enforced, proxy_enforced: request.proxy_enforced,
network_proxy_restricting_sid: request.network_proxy_restricting_sid.as_deref(), network_proxy_restricting_sid: request.network_proxy_restricting_sid.as_deref(),
proxy_settings_mode: WindowsSandboxProxySettingsMode::Reconcile, proxy_settings_mode: request.proxy_settings_mode,
filesystem_overrides: request.filesystem_overrides.as_ref(), filesystem_overrides: request.filesystem_overrides.as_ref(),
use_private_desktop: request.use_private_desktop, use_private_desktop: request.use_private_desktop,
}) })
@@ -114,6 +115,9 @@ pub(crate) async fn prepare_exec_request(
windows_sandbox: None, windows_sandbox: None,
}); });
}; };
let windows_sandbox_proxy_settings_mode = sandbox_context
.windows_sandbox_proxy_settings_mode
.unwrap_or_default();
let runtime_paths = runtime_paths let runtime_paths = runtime_paths
.ok_or_else(|| invalid_params("sandbox runtime paths are not configured".to_string()))?; .ok_or_else(|| invalid_params("sandbox runtime paths are not configured".to_string()))?;
// TODO(jif): Transport permissions before orchestrator-local paths are materialized, // TODO(jif): Transport permissions before orchestrator-local paths are materialized,
@@ -212,8 +216,7 @@ pub(crate) async fn prepare_exec_request(
let (program, args) = (program.into(), args.to_vec()); let (program, args) = (program.into(), args.to_vec());
let transform_request = SandboxDirectSpawnTransformRequest { let transform_request = SandboxDirectSpawnTransformRequest {
workspace_roots, workspace_roots,
windows_sandbox_proxy_settings_mode: windows_sandbox_proxy_settings_mode,
codex_sandboxing::WindowsSandboxProxySettingsMode::Reconcile,
transform: SandboxTransformRequest { transform: SandboxTransformRequest {
command: SandboxCommand { command: SandboxCommand {
program, program,
@@ -271,6 +274,7 @@ pub(crate) async fn prepare_exec_request(
windows_sandbox_level: sandbox_context.windows_sandbox_level, windows_sandbox_level: sandbox_context.windows_sandbox_level,
proxy_enforced, proxy_enforced,
network_proxy_restricting_sid, network_proxy_restricting_sid,
proxy_settings_mode: windows_sandbox_proxy_settings_mode,
filesystem_overrides, filesystem_overrides,
use_private_desktop: sandbox_context.windows_sandbox_private_desktop, use_private_desktop: sandbox_context.windows_sandbox_private_desktop,
}) })
@@ -394,6 +394,8 @@ async fn managed_network_selects_elevated_windows_spawn() {
cwd_uri.clone(), cwd_uri.clone(),
); );
sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken; sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken;
sandbox.windows_sandbox_proxy_settings_mode =
Some(codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve);
let proxy_config = RemoteNetworkProxyConfig::from_effective_config(&NetworkProxyConfig { let proxy_config = RemoteNetworkProxyConfig::from_effective_config(&NetworkProxyConfig {
enabled: true, enabled: true,
enable_socks5: false, enable_socks5: false,
@@ -434,6 +436,10 @@ async fn managed_network_selects_elevated_windows_spawn() {
); );
assert!(spawn.proxy_enforced); assert!(spawn.proxy_enforced);
assert!(spawn.network_proxy_restricting_sid.is_some()); assert!(spawn.network_proxy_restricting_sid.is_some());
assert_eq!(
spawn.proxy_settings_mode,
codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve
);
assert_eq!(spawn.permission_profile, &permissions); assert_eq!(spawn.permission_profile, &permissions);
assert_eq!(spawn.workspace_roots, std::slice::from_ref(&cwd)); assert_eq!(spawn.workspace_roots, std::slice::from_ref(&cwd));
} }
+4
View File
@@ -2,6 +2,7 @@ mod find_up;
use bytes::Bytes; use bytes::Bytes;
use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::config_types::WindowsSandboxProxySettingsMode;
use codex_protocol::models::ManagedFileSystemPermissions; use codex_protocol::models::ManagedFileSystemPermissions;
use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfile;
use codex_protocol::models::SandboxEnforcement; use codex_protocol::models::SandboxEnforcement;
@@ -291,6 +292,8 @@ pub struct FileSystemSandboxContext {
pub windows_sandbox_level: WindowsSandboxLevel, pub windows_sandbox_level: WindowsSandboxLevel,
#[serde(default)] #[serde(default)]
pub windows_sandbox_private_desktop: bool, pub windows_sandbox_private_desktop: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub windows_sandbox_proxy_settings_mode: Option<WindowsSandboxProxySettingsMode>,
#[serde(default)] #[serde(default)]
pub use_legacy_landlock: bool, pub use_legacy_landlock: bool,
} }
@@ -332,6 +335,7 @@ impl FileSystemSandboxContext {
workspace_roots, workspace_roots,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false, windows_sandbox_private_desktop: false,
windows_sandbox_proxy_settings_mode: None,
use_legacy_landlock: false, use_legacy_landlock: false,
} }
} }
+10
View File
@@ -283,6 +283,16 @@ pub enum WindowsSandboxLevel {
Elevated, Elevated,
} }
/// Controls whether a Windows sandbox launch reconciles persistent proxy settings or preserves
/// the settings established by another launch.
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub enum WindowsSandboxProxySettingsMode {
#[default]
Reconcile,
Preserve,
}
#[derive( #[derive(
Debug, Debug,
Serialize, Serialize,
@@ -371,4 +371,36 @@ mod tests {
vec![8080] vec![8080]
); );
} }
#[test]
fn guardian_preserve_mode_does_not_churn_marker_with_empty_proxy_ports() {
let marker = SetupMarker {
version: crate::setup::SETUP_VERSION,
offline_username: "offline".to_string(),
online_username: "online".to_string(),
created_at: None,
proxy_ports: vec![3128, 8081],
allow_local_binding: true,
};
let env_map = HashMap::new();
let reconciled = desired_offline_proxy_settings(
Some(&marker),
WindowsSandboxProxySettingsMode::Reconcile,
&env_map,
SandboxNetworkIdentity::Offline,
);
assert_eq!(reconciled.proxy_ports, Vec::<u16>::new());
let desired = desired_offline_proxy_settings(
Some(&marker),
WindowsSandboxProxySettingsMode::Preserve,
&env_map,
SandboxNetworkIdentity::Offline,
);
assert_eq!(desired, marker.offline_proxy_settings());
assert_eq!(
marker.request_mismatch_reason(SandboxNetworkIdentity::Offline, &desired),
None
);
}
} }
+1 -8
View File
@@ -35,14 +35,7 @@ impl fmt::Debug for WindowsSandboxCancellationToken {
} }
} }
/// Controls whether a Windows sandbox launch reconciles persistent proxy pub use codex_protocol::config_types::WindowsSandboxProxySettingsMode;
/// firewall settings or preserves the settings established by another launch.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum WindowsSandboxProxySettingsMode {
#[default]
Reconcile,
Preserve,
}
/// Network settings installed by an administrator during managed Windows sandbox setup. /// Network settings installed by an administrator during managed Windows sandbox setup.
#[derive(Clone, Debug, Default, PartialEq, Eq)] #[derive(Clone, Debug, Default, PartialEq, Eq)]