sandboxing: plumb split sandbox policies through runtime

This commit is contained in:
Michael Bolin
2026-03-06 13:03:45 -08:00
parent 929eeaf2c9
commit b4e9baaaff
18 changed files with 655 additions and 64 deletions

View File

@@ -1534,9 +1534,19 @@ impl CodexMessageProcessor {
}; };
let requested_policy = params.sandbox_policy.map(|policy| policy.to_core()); let requested_policy = params.sandbox_policy.map(|policy| policy.to_core());
let effective_policy = match requested_policy { let (
effective_policy,
effective_file_system_sandbox_policy,
effective_network_sandbox_policy,
) = match requested_policy {
Some(policy) => match self.config.permissions.sandbox_policy.can_set(&policy) { Some(policy) => match self.config.permissions.sandbox_policy.can_set(&policy) {
Ok(()) => policy, Ok(()) => {
let file_system_sandbox_policy =
codex_protocol::protocol::FileSystemSandboxPolicy::from(&policy);
let network_sandbox_policy =
codex_protocol::protocol::NetworkSandboxPolicy::from(&policy);
(policy, file_system_sandbox_policy, network_sandbox_policy)
}
Err(err) => { Err(err) => {
let error = JSONRPCErrorError { let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE, code: INVALID_REQUEST_ERROR_CODE,
@@ -1547,7 +1557,11 @@ impl CodexMessageProcessor {
return; return;
} }
}, },
None => self.config.permissions.sandbox_policy.get().clone(), None => (
self.config.permissions.sandbox_policy.get().clone(),
self.config.permissions.file_system_sandbox_policy.clone(),
self.config.permissions.network_sandbox_policy,
),
}; };
let codex_linux_sandbox_exe = self.arg0_paths.codex_linux_sandbox_exe.clone(); let codex_linux_sandbox_exe = self.arg0_paths.codex_linux_sandbox_exe.clone();
@@ -1562,6 +1576,8 @@ impl CodexMessageProcessor {
match codex_core::exec::process_exec_tool_call( match codex_core::exec::process_exec_tool_call(
exec_params, exec_params,
&effective_policy, &effective_policy,
&effective_file_system_sandbox_policy,
effective_network_sandbox_policy,
sandbox_cwd.as_path(), sandbox_cwd.as_path(),
&codex_linux_sandbox_exe, &codex_linux_sandbox_exe,
use_linux_sandbox_bwrap, use_linux_sandbox_bwrap,

View File

@@ -223,10 +223,12 @@ use crate::protocol::ErrorEvent;
use crate::protocol::Event; use crate::protocol::Event;
use crate::protocol::EventMsg; use crate::protocol::EventMsg;
use crate::protocol::ExecApprovalRequestEvent; use crate::protocol::ExecApprovalRequestEvent;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::McpServerRefreshConfig; use crate::protocol::McpServerRefreshConfig;
use crate::protocol::ModelRerouteEvent; use crate::protocol::ModelRerouteEvent;
use crate::protocol::ModelRerouteReason; use crate::protocol::ModelRerouteReason;
use crate::protocol::NetworkApprovalContext; use crate::protocol::NetworkApprovalContext;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::Op; use crate::protocol::Op;
use crate::protocol::PlanDeltaEvent; use crate::protocol::PlanDeltaEvent;
use crate::protocol::RateLimitSnapshot; use crate::protocol::RateLimitSnapshot;
@@ -488,6 +490,8 @@ impl Codex {
compact_prompt: config.compact_prompt.clone(), compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_policy.clone(), approval_policy: config.permissions.approval_policy.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(), sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config), windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(), cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(), codex_home: config.codex_home.clone(),
@@ -683,6 +687,8 @@ pub(crate) struct TurnContext {
pub(crate) personality: Option<Personality>, pub(crate) personality: Option<Personality>,
pub(crate) approval_policy: Constrained<AskForApproval>, pub(crate) approval_policy: Constrained<AskForApproval>,
pub(crate) sandbox_policy: Constrained<SandboxPolicy>, pub(crate) sandbox_policy: Constrained<SandboxPolicy>,
pub(crate) file_system_sandbox_policy: FileSystemSandboxPolicy,
pub(crate) network_sandbox_policy: NetworkSandboxPolicy,
pub(crate) network: Option<NetworkProxy>, pub(crate) network: Option<NetworkProxy>,
pub(crate) windows_sandbox_level: WindowsSandboxLevel, pub(crate) windows_sandbox_level: WindowsSandboxLevel,
pub(crate) shell_environment_policy: ShellEnvironmentPolicy, pub(crate) shell_environment_policy: ShellEnvironmentPolicy,
@@ -773,6 +779,8 @@ impl TurnContext {
personality: self.personality, personality: self.personality,
approval_policy: self.approval_policy.clone(), approval_policy: self.approval_policy.clone(),
sandbox_policy: self.sandbox_policy.clone(), sandbox_policy: self.sandbox_policy.clone(),
file_system_sandbox_policy: self.file_system_sandbox_policy.clone(),
network_sandbox_policy: self.network_sandbox_policy,
network: self.network.clone(), network: self.network.clone(),
windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_level: self.windows_sandbox_level,
shell_environment_policy: self.shell_environment_policy.clone(), shell_environment_policy: self.shell_environment_policy.clone(),
@@ -878,6 +886,8 @@ pub(crate) struct SessionConfiguration {
approval_policy: Constrained<AskForApproval>, approval_policy: Constrained<AskForApproval>,
/// How to sandbox commands executed in the system /// How to sandbox commands executed in the system
sandbox_policy: Constrained<SandboxPolicy>, sandbox_policy: Constrained<SandboxPolicy>,
file_system_sandbox_policy: FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
windows_sandbox_level: WindowsSandboxLevel, windows_sandbox_level: WindowsSandboxLevel,
/// Working directory that should be treated as the *root* of the /// Working directory that should be treated as the *root* of the
@@ -944,6 +954,10 @@ impl SessionConfiguration {
} }
if let Some(sandbox_policy) = updates.sandbox_policy.clone() { if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
next_configuration.sandbox_policy.set(sandbox_policy)?; next_configuration.sandbox_policy.set(sandbox_policy)?;
next_configuration.file_system_sandbox_policy =
FileSystemSandboxPolicy::from(next_configuration.sandbox_policy.get());
next_configuration.network_sandbox_policy =
NetworkSandboxPolicy::from(next_configuration.sandbox_policy.get());
} }
if let Some(windows_sandbox_level) = updates.windows_sandbox_level { if let Some(windows_sandbox_level) = updates.windows_sandbox_level {
next_configuration.windows_sandbox_level = windows_sandbox_level; next_configuration.windows_sandbox_level = windows_sandbox_level;
@@ -1156,6 +1170,8 @@ impl Session {
personality: session_configuration.personality, personality: session_configuration.personality,
approval_policy: session_configuration.approval_policy.clone(), approval_policy: session_configuration.approval_policy.clone(),
sandbox_policy: session_configuration.sandbox_policy.clone(), sandbox_policy: session_configuration.sandbox_policy.clone(),
file_system_sandbox_policy: session_configuration.file_system_sandbox_policy.clone(),
network_sandbox_policy: session_configuration.network_sandbox_policy,
network, network,
windows_sandbox_level: session_configuration.windows_sandbox_level, windows_sandbox_level: session_configuration.windows_sandbox_level,
shell_environment_policy: per_turn_config.permissions.shell_environment_policy.clone(), shell_environment_policy: per_turn_config.permissions.shell_environment_policy.clone(),
@@ -4983,6 +4999,8 @@ async fn spawn_review_thread(
personality: parent_turn_context.personality, personality: parent_turn_context.personality,
approval_policy: parent_turn_context.approval_policy.clone(), approval_policy: parent_turn_context.approval_policy.clone(),
sandbox_policy: parent_turn_context.sandbox_policy.clone(), sandbox_policy: parent_turn_context.sandbox_policy.clone(),
file_system_sandbox_policy: parent_turn_context.file_system_sandbox_policy.clone(),
network_sandbox_policy: parent_turn_context.network_sandbox_policy,
network: parent_turn_context.network.clone(), network: parent_turn_context.network.clone(),
windows_sandbox_level: parent_turn_context.windows_sandbox_level, windows_sandbox_level: parent_turn_context.windows_sandbox_level,
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(), shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),

View File

@@ -1416,6 +1416,8 @@ async fn set_rate_limits_retains_previous_credits() {
compact_prompt: config.compact_prompt.clone(), compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_policy.clone(), approval_policy: config.permissions.approval_policy.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(), sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config), windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(), cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(), codex_home: config.codex_home.clone(),
@@ -1510,6 +1512,8 @@ async fn set_rate_limits_updates_plan_type_when_present() {
compact_prompt: config.compact_prompt.clone(), compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_policy.clone(), approval_policy: config.permissions.approval_policy.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(), sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config), windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(), cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(), codex_home: config.codex_home.clone(),
@@ -1862,6 +1866,8 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
compact_prompt: config.compact_prompt.clone(), compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_policy.clone(), approval_policy: config.permissions.approval_policy.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(), sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config), windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(), cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(), codex_home: config.codex_home.clone(),
@@ -1919,6 +1925,8 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
compact_prompt: config.compact_prompt.clone(), compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_policy.clone(), approval_policy: config.permissions.approval_policy.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(), sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config), windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(), cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(), codex_home: config.codex_home.clone(),
@@ -2009,6 +2017,8 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
compact_prompt: config.compact_prompt.clone(), compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_policy.clone(), approval_policy: config.permissions.approval_policy.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(), sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config), windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(), cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(), codex_home: config.codex_home.clone(),
@@ -2414,6 +2424,8 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
compact_prompt: config.compact_prompt.clone(), compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_policy.clone(), approval_policy: config.permissions.approval_policy.clone(),
sandbox_policy: config.permissions.sandbox_policy.clone(), sandbox_policy: config.permissions.sandbox_policy.clone(),
file_system_sandbox_policy: config.permissions.file_system_sandbox_policy.clone(),
network_sandbox_policy: config.permissions.network_sandbox_policy,
windows_sandbox_level: WindowsSandboxLevel::from_config(&config), windows_sandbox_level: WindowsSandboxLevel::from_config(&config),
cwd: config.cwd.clone(), cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(), codex_home: config.codex_home.clone(),
@@ -3841,11 +3853,15 @@ async fn rejects_escalated_permissions_when_policy_not_on_request() {
// Now retry the same command WITHOUT escalated permissions; should succeed. // Now retry the same command WITHOUT escalated permissions; should succeed.
// Force DangerFullAccess to avoid platform sandbox dependencies in tests. // Force DangerFullAccess to avoid platform sandbox dependencies in tests.
Arc::get_mut(&mut turn_context) let turn_context_mut = Arc::get_mut(&mut turn_context).expect("unique turn context Arc");
.expect("unique turn context Arc") turn_context_mut
.sandbox_policy .sandbox_policy
.set(SandboxPolicy::DangerFullAccess) .set(SandboxPolicy::DangerFullAccess)
.expect("test setup should allow updating sandbox policy"); .expect("test setup should allow updating sandbox policy");
turn_context_mut.file_system_sandbox_policy =
FileSystemSandboxPolicy::from(turn_context_mut.sandbox_policy.get());
turn_context_mut.network_sandbox_policy =
NetworkSandboxPolicy::from(turn_context_mut.sandbox_policy.get());
let resp2 = handler let resp2 = handler
.handle(ToolInvocation { .handle(ToolInvocation {

View File

@@ -24,6 +24,9 @@ use crate::protocol::Event;
use crate::protocol::EventMsg; use crate::protocol::EventMsg;
use crate::protocol::ExecCommandOutputDeltaEvent; use crate::protocol::ExecCommandOutputDeltaEvent;
use crate::protocol::ExecOutputStream; use crate::protocol::ExecOutputStream;
use crate::protocol::FileSystemSandboxKind;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy; use crate::protocol::SandboxPolicy;
use crate::sandboxing::CommandSpec; use crate::sandboxing::CommandSpec;
use crate::sandboxing::ExecRequest; use crate::sandboxing::ExecRequest;
@@ -149,9 +152,12 @@ pub struct StdoutStream {
pub tx_event: Sender<Event>, pub tx_event: Sender<Event>,
} }
#[allow(clippy::too_many_arguments)]
pub async fn process_exec_tool_call( pub async fn process_exec_tool_call(
params: ExecParams, params: ExecParams,
sandbox_policy: &SandboxPolicy, sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_cwd: &Path, sandbox_cwd: &Path,
codex_linux_sandbox_exe: &Option<PathBuf>, codex_linux_sandbox_exe: &Option<PathBuf>,
use_linux_sandbox_bwrap: bool, use_linux_sandbox_bwrap: bool,
@@ -159,8 +165,8 @@ pub async fn process_exec_tool_call(
) -> Result<ExecToolCallOutput> { ) -> Result<ExecToolCallOutput> {
let windows_sandbox_level = params.windows_sandbox_level; let windows_sandbox_level = params.windows_sandbox_level;
let enforce_managed_network = params.network.is_some(); let enforce_managed_network = params.network.is_some();
let sandbox_type = match &sandbox_policy { let sandbox_type = match file_system_sandbox_policy.kind {
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => { FileSystemSandboxKind::Unrestricted | FileSystemSandboxKind::ExternalSandbox => {
if enforce_managed_network { if enforce_managed_network {
get_platform_sandbox( get_platform_sandbox(
windows_sandbox_level windows_sandbox_level
@@ -215,6 +221,8 @@ pub async fn process_exec_tool_call(
.transform(crate::sandboxing::SandboxTransformRequest { .transform(crate::sandboxing::SandboxTransformRequest {
spec, spec,
policy: sandbox_policy, policy: sandbox_policy,
file_system_policy: file_system_sandbox_policy,
network_policy: network_sandbox_policy,
sandbox: sandbox_type, sandbox: sandbox_type,
enforce_managed_network, enforce_managed_network,
network: network.as_ref(), network: network.as_ref(),
@@ -247,9 +255,12 @@ pub(crate) async fn execute_exec_request(
windows_sandbox_level, windows_sandbox_level,
sandbox_permissions, sandbox_permissions,
sandbox_policy: _sandbox_policy_from_env, sandbox_policy: _sandbox_policy_from_env,
file_system_sandbox_policy,
network_sandbox_policy,
justification, justification,
arg0, arg0,
} = exec_request; } = exec_request;
let _ = _sandbox_policy_from_env;
let params = ExecParams { let params = ExecParams {
command, command,
@@ -264,7 +275,16 @@ pub(crate) async fn execute_exec_request(
}; };
let start = Instant::now(); let start = Instant::now();
let raw_output_result = exec(params, sandbox, sandbox_policy, stdout_stream, after_spawn).await; let raw_output_result = exec(
params,
sandbox,
sandbox_policy,
&file_system_sandbox_policy,
network_sandbox_policy,
stdout_stream,
after_spawn,
)
.await;
let duration = start.elapsed(); let duration = start.elapsed();
finalize_exec_result(raw_output_result, sandbox, duration) finalize_exec_result(raw_output_result, sandbox, duration)
} }
@@ -693,16 +713,17 @@ async fn exec(
params: ExecParams, params: ExecParams,
sandbox: SandboxType, sandbox: SandboxType,
sandbox_policy: &SandboxPolicy, sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
stdout_stream: Option<StdoutStream>, stdout_stream: Option<StdoutStream>,
after_spawn: Option<Box<dyn FnOnce() + Send>>, after_spawn: Option<Box<dyn FnOnce() + Send>>,
) -> Result<RawExecToolCallOutput> { ) -> Result<RawExecToolCallOutput> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
if sandbox == SandboxType::WindowsRestrictedToken if should_use_windows_restricted_token_sandbox(
&& !matches!( sandbox,
sandbox_policy, sandbox_policy,
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } file_system_sandbox_policy,
) ) {
{
return exec_windows_sandbox(params, sandbox_policy).await; return exec_windows_sandbox(params, sandbox_policy).await;
} }
let ExecParams { let ExecParams {
@@ -731,7 +752,7 @@ async fn exec(
args: args.into(), args: args.into(),
arg0: arg0_ref, arg0: arg0_ref,
cwd, cwd,
sandbox_policy, network_sandbox_policy,
// The environment already has attempt-scoped proxy settings from // The environment already has attempt-scoped proxy settings from
// apply_to_env_for_attempt above. Passing network here would reapply // apply_to_env_for_attempt above. Passing network here would reapply
// non-attempt proxy vars and drop attempt correlation metadata. // non-attempt proxy vars and drop attempt correlation metadata.
@@ -746,6 +767,20 @@ async fn exec(
consume_truncated_output(child, expiration, stdout_stream).await consume_truncated_output(child, expiration, stdout_stream).await
} }
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
fn should_use_windows_restricted_token_sandbox(
sandbox: SandboxType,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
) -> bool {
sandbox == SandboxType::WindowsRestrictedToken
&& file_system_sandbox_policy.kind == FileSystemSandboxKind::Restricted
&& !matches!(
sandbox_policy,
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. }
)
}
/// Consumes the output of a child process, truncating it so it is suitable for /// Consumes the output of a child process, truncating it so it is suitable for
/// use as the output of a `shell` tool call. Also enforces specified timeout. /// use as the output of a `shell` tool call. Also enforces specified timeout.
async fn consume_truncated_output( async fn consume_truncated_output(
@@ -1098,6 +1133,38 @@ mod tests {
assert_eq!(aggregated.truncated_after_lines, None); assert_eq!(aggregated.truncated_after_lines, None);
} }
#[test]
fn windows_restricted_token_skips_external_sandbox_policies() {
let policy = SandboxPolicy::ExternalSandbox {
network_access: codex_protocol::protocol::NetworkAccess::Restricted,
};
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![]);
assert_eq!(
should_use_windows_restricted_token_sandbox(
SandboxType::WindowsRestrictedToken,
&policy,
&file_system_policy,
),
false
);
}
#[test]
fn windows_restricted_token_runs_for_legacy_restricted_policies() {
let policy = SandboxPolicy::new_read_only_policy();
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![]);
assert_eq!(
should_use_windows_restricted_token_sandbox(
SandboxType::WindowsRestrictedToken,
&policy,
&file_system_policy,
),
true
);
}
#[cfg(unix)] #[cfg(unix)]
#[test] #[test]
fn sandbox_detection_flags_sigsys_exit_code() { fn sandbox_detection_flags_sigsys_exit_code() {
@@ -1140,6 +1207,8 @@ mod tests {
params, params,
SandboxType::None, SandboxType::None,
&SandboxPolicy::new_read_only_policy(), &SandboxPolicy::new_read_only_policy(),
&FileSystemSandboxPolicy::from(&SandboxPolicy::new_read_only_policy()),
NetworkSandboxPolicy::Restricted,
None, None,
None, None,
) )
@@ -1196,6 +1265,8 @@ mod tests {
let result = process_exec_tool_call( let result = process_exec_tool_call(
params, params,
&SandboxPolicy::DangerFullAccess, &SandboxPolicy::DangerFullAccess,
&FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
NetworkSandboxPolicy::Enabled,
cwd.as_path(), cwd.as_path(),
&None, &None,
false, false,

View File

@@ -1,3 +1,4 @@
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy; use crate::protocol::SandboxPolicy;
use crate::spawn::SpawnChildRequest; use crate::spawn::SpawnChildRequest;
use crate::spawn::StdioPolicy; use crate::spawn::StdioPolicy;
@@ -44,7 +45,7 @@ where
args, args,
arg0, arg0,
cwd: command_cwd, cwd: command_cwd,
sandbox_policy, network_sandbox_policy: NetworkSandboxPolicy::from(sandbox_policy),
network, network,
stdio_policy, stdio_policy,
env, env,

View File

@@ -15,6 +15,13 @@ use crate::exec::StdoutStream;
use crate::exec::execute_exec_request; use crate::exec::execute_exec_request;
use crate::landlock::allow_network_for_proxy; use crate::landlock::allow_network_for_proxy;
use crate::landlock::create_linux_sandbox_command_args; use crate::landlock::create_linux_sandbox_command_args;
use crate::protocol::FileSystemAccessMode;
use crate::protocol::FileSystemPath;
use crate::protocol::FileSystemSandboxEntry;
use crate::protocol::FileSystemSandboxKind;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::FileSystemSpecialPathKind;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy; use crate::protocol::SandboxPolicy;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE; use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
@@ -30,6 +37,7 @@ use codex_protocol::models::FileSystemPermissions;
use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::models::MacOsSeatbeltProfileExtensions;
use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfile;
pub use codex_protocol::models::SandboxPermissions; pub use codex_protocol::models::SandboxPermissions;
use codex_protocol::protocol::NetworkAccess;
use codex_protocol::protocol::ReadOnlyAccess; use codex_protocol::protocol::ReadOnlyAccess;
use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBuf;
use dunce::canonicalize; use dunce::canonicalize;
@@ -62,6 +70,8 @@ pub struct ExecRequest {
pub windows_sandbox_level: WindowsSandboxLevel, pub windows_sandbox_level: WindowsSandboxLevel,
pub sandbox_permissions: SandboxPermissions, pub sandbox_permissions: SandboxPermissions,
pub sandbox_policy: SandboxPolicy, pub sandbox_policy: SandboxPolicy,
pub file_system_sandbox_policy: FileSystemSandboxPolicy,
pub network_sandbox_policy: NetworkSandboxPolicy,
pub justification: Option<String>, pub justification: Option<String>,
pub arg0: Option<String>, pub arg0: Option<String>,
} }
@@ -72,6 +82,8 @@ pub struct ExecRequest {
pub(crate) struct SandboxTransformRequest<'a> { pub(crate) struct SandboxTransformRequest<'a> {
pub spec: CommandSpec, pub spec: CommandSpec,
pub policy: &'a SandboxPolicy, pub policy: &'a SandboxPolicy,
pub file_system_policy: &'a FileSystemSandboxPolicy,
pub network_policy: NetworkSandboxPolicy,
pub sandbox: SandboxType, pub sandbox: SandboxType,
pub enforce_managed_network: bool, pub enforce_managed_network: bool,
// TODO(viyatb): Evaluate switching this to Option<Arc<NetworkProxy>> // TODO(viyatb): Evaluate switching this to Option<Arc<NetworkProxy>>
@@ -203,6 +215,41 @@ fn additional_permission_roots(
) )
} }
#[cfg_attr(not(test), allow(dead_code))]
fn merge_file_system_policy_with_additional_permissions(
file_system_policy: &FileSystemSandboxPolicy,
extra_reads: Vec<AbsolutePathBuf>,
extra_writes: Vec<AbsolutePathBuf>,
) -> FileSystemSandboxPolicy {
match file_system_policy.kind {
FileSystemSandboxKind::Restricted => {
let mut merged_policy = file_system_policy.clone();
for path in extra_reads {
let entry = FileSystemSandboxEntry {
path: FileSystemPath::Path { path },
access: FileSystemAccessMode::Read,
};
if !merged_policy.entries.contains(&entry) {
merged_policy.entries.push(entry);
}
}
for path in extra_writes {
let entry = FileSystemSandboxEntry {
path: FileSystemPath::Path { path },
access: FileSystemAccessMode::Write,
};
if !merged_policy.entries.contains(&entry) {
merged_policy.entries.push(entry);
}
}
merged_policy
}
FileSystemSandboxKind::Unrestricted | FileSystemSandboxKind::ExternalSandbox => {
file_system_policy.clone()
}
}
}
fn merge_read_only_access_with_additional_reads( fn merge_read_only_access_with_additional_reads(
read_only_access: &ReadOnlyAccess, read_only_access: &ReadOnlyAccess,
extra_reads: Vec<AbsolutePathBuf>, extra_reads: Vec<AbsolutePathBuf>,
@@ -246,9 +293,17 @@ fn sandbox_policy_with_additional_permissions(
let (extra_reads, extra_writes) = additional_permission_roots(additional_permissions); let (extra_reads, extra_writes) = additional_permission_roots(additional_permissions);
match sandbox_policy { match sandbox_policy {
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => { SandboxPolicy::DangerFullAccess => SandboxPolicy::DangerFullAccess,
sandbox_policy.clone() SandboxPolicy::ExternalSandbox { network_access } => SandboxPolicy::ExternalSandbox {
} network_access: if merge_network_access(
network_access.is_enabled(),
additional_permissions,
) {
NetworkAccess::Enabled
} else {
NetworkAccess::Restricted
},
},
SandboxPolicy::WorkspaceWrite { SandboxPolicy::WorkspaceWrite {
writable_roots, writable_roots,
read_only_access, read_only_access,
@@ -297,6 +352,36 @@ fn sandbox_policy_with_additional_permissions(
} }
} }
pub(crate) fn should_require_platform_sandbox(
file_system_policy: &FileSystemSandboxPolicy,
network_policy: NetworkSandboxPolicy,
has_managed_network_requirements: bool,
) -> bool {
if has_managed_network_requirements {
return true;
}
if !network_policy.is_enabled() {
return !matches!(
file_system_policy.kind,
FileSystemSandboxKind::ExternalSandbox
);
}
match file_system_policy.kind {
FileSystemSandboxKind::Restricted => !file_system_policy.entries.iter().any(|entry| {
entry.access == FileSystemAccessMode::Write
&& matches!(
&entry.path,
FileSystemPath::Special { value }
if value.kind == FileSystemSpecialPathKind::Root
&& value.subpath.is_none()
)
}),
FileSystemSandboxKind::Unrestricted | FileSystemSandboxKind::ExternalSandbox => false,
}
}
#[derive(Default)] #[derive(Default)]
pub struct SandboxManager; pub struct SandboxManager;
@@ -307,7 +392,8 @@ impl SandboxManager {
pub(crate) fn select_initial( pub(crate) fn select_initial(
&self, &self,
policy: &SandboxPolicy, file_system_policy: &FileSystemSandboxPolicy,
network_policy: NetworkSandboxPolicy,
pref: SandboxablePreference, pref: SandboxablePreference,
windows_sandbox_level: WindowsSandboxLevel, windows_sandbox_level: WindowsSandboxLevel,
has_managed_network_requirements: bool, has_managed_network_requirements: bool,
@@ -322,22 +408,20 @@ impl SandboxManager {
) )
.unwrap_or(SandboxType::None) .unwrap_or(SandboxType::None)
} }
SandboxablePreference::Auto => match policy { SandboxablePreference::Auto => {
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => { if should_require_platform_sandbox(
if has_managed_network_requirements { file_system_policy,
crate::safety::get_platform_sandbox( network_policy,
windows_sandbox_level != WindowsSandboxLevel::Disabled, has_managed_network_requirements,
) ) {
.unwrap_or(SandboxType::None) crate::safety::get_platform_sandbox(
} else { windows_sandbox_level != WindowsSandboxLevel::Disabled,
SandboxType::None )
} .unwrap_or(SandboxType::None)
} else {
SandboxType::None
} }
_ => crate::safety::get_platform_sandbox( }
windows_sandbox_level != WindowsSandboxLevel::Disabled,
)
.unwrap_or(SandboxType::None),
},
} }
} }
@@ -348,6 +432,8 @@ impl SandboxManager {
let SandboxTransformRequest { let SandboxTransformRequest {
mut spec, mut spec,
policy, policy,
file_system_policy,
network_policy,
sandbox, sandbox,
enforce_managed_network, enforce_managed_network,
network, network,
@@ -360,16 +446,38 @@ impl SandboxManager {
} = request; } = request;
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
let macos_seatbelt_profile_extensions = None; let macos_seatbelt_profile_extensions = None;
let effective_permissions = EffectiveSandboxPermissions::new( let additional_permissions = spec.additional_permissions.take();
let EffectiveSandboxPermissions {
sandbox_policy: effective_policy,
macos_seatbelt_profile_extensions: effective_macos_seatbelt_profile_extensions,
} = EffectiveSandboxPermissions::new(
policy, policy,
macos_seatbelt_profile_extensions, macos_seatbelt_profile_extensions,
spec.additional_permissions.as_ref(), additional_permissions.as_ref(),
); );
let (effective_file_system_policy, effective_network_policy) =
if let Some(additional_permissions) = additional_permissions {
let (extra_reads, extra_writes) =
additional_permission_roots(&additional_permissions);
let file_system_sandbox_policy =
if extra_reads.is_empty() && extra_writes.is_empty() {
file_system_policy.clone()
} else {
match file_system_policy.kind {
FileSystemSandboxKind::Restricted => {
FileSystemSandboxPolicy::from(&effective_policy)
}
FileSystemSandboxKind::Unrestricted
| FileSystemSandboxKind::ExternalSandbox => file_system_policy.clone(),
}
};
let network_sandbox_policy = NetworkSandboxPolicy::from(&effective_policy);
(file_system_sandbox_policy, network_sandbox_policy)
} else {
(file_system_policy.clone(), network_policy)
};
let mut env = spec.env; let mut env = spec.env;
if !effective_permissions if !effective_network_policy.is_enabled() {
.sandbox_policy
.has_full_network_access()
{
env.insert( env.insert(
CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR.to_string(), CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR.to_string(),
"1".to_string(), "1".to_string(),
@@ -388,13 +496,11 @@ impl SandboxManager {
seatbelt_env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string()); seatbelt_env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
let mut args = create_seatbelt_command_args_with_extensions( let mut args = create_seatbelt_command_args_with_extensions(
command.clone(), command.clone(),
&effective_permissions.sandbox_policy, &effective_policy,
sandbox_policy_cwd, sandbox_policy_cwd,
enforce_managed_network, enforce_managed_network,
network, network,
effective_permissions effective_macos_seatbelt_profile_extensions.as_ref(),
.macos_seatbelt_profile_extensions
.as_ref(),
); );
let mut full_command = Vec::with_capacity(1 + args.len()); let mut full_command = Vec::with_capacity(1 + args.len());
full_command.push(MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string()); full_command.push(MACOS_PATH_TO_SEATBELT_EXECUTABLE.to_string());
@@ -409,7 +515,7 @@ impl SandboxManager {
let allow_proxy_network = allow_network_for_proxy(enforce_managed_network); let allow_proxy_network = allow_network_for_proxy(enforce_managed_network);
let mut args = create_linux_sandbox_command_args( let mut args = create_linux_sandbox_command_args(
command.clone(), command.clone(),
&effective_permissions.sandbox_policy, &effective_policy,
sandbox_policy_cwd, sandbox_policy_cwd,
use_linux_sandbox_bwrap, use_linux_sandbox_bwrap,
allow_proxy_network, allow_proxy_network,
@@ -444,7 +550,9 @@ impl SandboxManager {
sandbox, sandbox,
windows_sandbox_level, windows_sandbox_level,
sandbox_permissions: spec.sandbox_permissions, sandbox_permissions: spec.sandbox_permissions,
sandbox_policy: effective_permissions.sandbox_policy, sandbox_policy: effective_policy,
file_system_sandbox_policy: effective_file_system_policy,
network_sandbox_policy: effective_network_policy,
justification: spec.justification, justification: spec.justification,
arg0: arg0_override, arg0: arg0_override,
}) })
@@ -477,9 +585,19 @@ mod tests {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use super::EffectiveSandboxPermissions; use super::EffectiveSandboxPermissions;
use super::SandboxManager; use super::SandboxManager;
use super::merge_file_system_policy_with_additional_permissions;
use super::normalize_additional_permissions; use super::normalize_additional_permissions;
use super::sandbox_policy_with_additional_permissions; use super::sandbox_policy_with_additional_permissions;
use super::should_require_platform_sandbox;
use crate::exec::SandboxType; use crate::exec::SandboxType;
use crate::protocol::FileSystemAccessMode;
use crate::protocol::FileSystemPath;
use crate::protocol::FileSystemSandboxEntry;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::FileSystemSpecialPath;
use crate::protocol::FileSystemSpecialPathKind;
use crate::protocol::NetworkAccess;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::ReadOnlyAccess; use crate::protocol::ReadOnlyAccess;
use crate::protocol::SandboxPolicy; use crate::protocol::SandboxPolicy;
use crate::tools::sandboxing::SandboxablePreference; use crate::tools::sandboxing::SandboxablePreference;
@@ -496,13 +614,15 @@ mod tests {
use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBuf;
use dunce::canonicalize; use dunce::canonicalize;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::collections::HashMap;
use tempfile::TempDir; use tempfile::TempDir;
#[test] #[test]
fn danger_full_access_defaults_to_no_sandbox_without_network_requirements() { fn danger_full_access_defaults_to_no_sandbox_without_network_requirements() {
let manager = SandboxManager::new(); let manager = SandboxManager::new();
let sandbox = manager.select_initial( let sandbox = manager.select_initial(
&SandboxPolicy::DangerFullAccess, &FileSystemSandboxPolicy::unrestricted(),
NetworkSandboxPolicy::Enabled,
SandboxablePreference::Auto, SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled, WindowsSandboxLevel::Disabled,
false, false,
@@ -515,7 +635,8 @@ mod tests {
let manager = SandboxManager::new(); let manager = SandboxManager::new();
let expected = crate::safety::get_platform_sandbox(false).unwrap_or(SandboxType::None); let expected = crate::safety::get_platform_sandbox(false).unwrap_or(SandboxType::None);
let sandbox = manager.select_initial( let sandbox = manager.select_initial(
&SandboxPolicy::DangerFullAccess, &FileSystemSandboxPolicy::unrestricted(),
NetworkSandboxPolicy::Enabled,
SandboxablePreference::Auto, SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled, WindowsSandboxLevel::Disabled,
true, true,
@@ -523,6 +644,107 @@ mod tests {
assert_eq!(sandbox, expected); assert_eq!(sandbox, expected);
} }
#[test]
fn restricted_file_system_uses_platform_sandbox_without_managed_network() {
let manager = SandboxManager::new();
let expected = crate::safety::get_platform_sandbox(false).unwrap_or(SandboxType::None);
let sandbox = manager.select_initial(
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath {
kind: FileSystemSpecialPathKind::Root,
subpath: None,
},
},
access: FileSystemAccessMode::Read,
}]),
NetworkSandboxPolicy::Enabled,
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
false,
);
assert_eq!(sandbox, expected);
}
#[test]
fn full_access_restricted_policy_skips_platform_sandbox_when_network_is_enabled() {
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath {
kind: FileSystemSpecialPathKind::Root,
subpath: None,
},
},
access: FileSystemAccessMode::Write,
}]);
assert_eq!(
should_require_platform_sandbox(&policy, NetworkSandboxPolicy::Enabled, false),
false
);
}
#[test]
fn full_access_restricted_policy_still_uses_platform_sandbox_for_restricted_network() {
let policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath {
kind: FileSystemSpecialPathKind::Root,
subpath: None,
},
},
access: FileSystemAccessMode::Write,
}]);
assert_eq!(
should_require_platform_sandbox(&policy, NetworkSandboxPolicy::Restricted, false),
true
);
}
#[test]
fn transform_preserves_unrestricted_file_system_policy_for_restricted_network() {
let manager = SandboxManager::new();
let cwd = std::env::current_dir().expect("current dir");
let exec_request = manager
.transform(super::SandboxTransformRequest {
spec: super::CommandSpec {
program: "true".to_string(),
args: Vec::new(),
cwd: cwd.clone(),
env: HashMap::new(),
expiration: crate::exec::ExecExpiration::DefaultTimeout,
sandbox_permissions: super::SandboxPermissions::UseDefault,
additional_permissions: None,
justification: None,
},
policy: &SandboxPolicy::ExternalSandbox {
network_access: crate::protocol::NetworkAccess::Restricted,
},
file_system_policy: &FileSystemSandboxPolicy::unrestricted(),
network_policy: NetworkSandboxPolicy::Restricted,
sandbox: SandboxType::None,
enforce_managed_network: false,
network: None,
sandbox_policy_cwd: cwd.as_path(),
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: None,
codex_linux_sandbox_exe: None,
use_linux_sandbox_bwrap: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
})
.expect("transform");
assert_eq!(
exec_request.file_system_sandbox_policy,
FileSystemSandboxPolicy::unrestricted()
);
assert_eq!(
exec_request.network_sandbox_policy,
NetworkSandboxPolicy::Restricted
);
}
#[test] #[test]
fn normalize_additional_permissions_preserves_network() { fn normalize_additional_permissions_preserves_network() {
let temp_dir = TempDir::new().expect("create temp dir"); let temp_dir = TempDir::new().expect("create temp dir");
@@ -624,7 +846,6 @@ mod tests {
} }
); );
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
#[test] #[test]
fn effective_permissions_merge_macos_extensions_with_additional_permissions() { fn effective_permissions_merge_macos_extensions_with_additional_permissions() {
@@ -679,4 +900,141 @@ mod tests {
}) })
); );
} }
#[test]
fn external_sandbox_additional_permissions_can_enable_network() {
let temp_dir = TempDir::new().expect("create temp dir");
let path = AbsolutePathBuf::from_absolute_path(
canonicalize(temp_dir.path()).expect("canonicalize temp dir"),
)
.expect("absolute temp dir");
let policy = sandbox_policy_with_additional_permissions(
&SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Restricted,
},
&PermissionProfile {
network: Some(NetworkPermissions {
enabled: Some(true),
}),
file_system: Some(FileSystemPermissions {
read: Some(vec![path]),
write: Some(Vec::new()),
}),
..Default::default()
},
);
assert_eq!(
policy,
SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Enabled,
}
);
}
#[test]
fn transform_additional_permissions_enable_network_for_external_sandbox() {
let manager = SandboxManager::new();
let cwd = std::env::current_dir().expect("current dir");
let temp_dir = TempDir::new().expect("create temp dir");
let path = AbsolutePathBuf::from_absolute_path(
canonicalize(temp_dir.path()).expect("canonicalize temp dir"),
)
.expect("absolute temp dir");
let exec_request = manager
.transform(super::SandboxTransformRequest {
spec: super::CommandSpec {
program: "true".to_string(),
args: Vec::new(),
cwd: cwd.clone(),
env: HashMap::new(),
expiration: crate::exec::ExecExpiration::DefaultTimeout,
sandbox_permissions: super::SandboxPermissions::WithAdditionalPermissions,
additional_permissions: Some(PermissionProfile {
network: Some(NetworkPermissions {
enabled: Some(true),
}),
file_system: Some(FileSystemPermissions {
read: Some(vec![path]),
write: Some(Vec::new()),
}),
..Default::default()
}),
justification: None,
},
policy: &SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Restricted,
},
file_system_policy: &FileSystemSandboxPolicy::unrestricted(),
network_policy: NetworkSandboxPolicy::Restricted,
sandbox: SandboxType::None,
enforce_managed_network: false,
network: None,
sandbox_policy_cwd: cwd.as_path(),
#[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: None,
codex_linux_sandbox_exe: None,
use_linux_sandbox_bwrap: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
})
.expect("transform");
assert_eq!(
exec_request.sandbox_policy,
SandboxPolicy::ExternalSandbox {
network_access: NetworkAccess::Enabled,
}
);
assert_eq!(
exec_request.network_sandbox_policy,
NetworkSandboxPolicy::Enabled
);
}
#[test]
fn merge_file_system_policy_with_additional_permissions_preserves_unreadable_roots() {
let temp_dir = TempDir::new().expect("create temp dir");
let cwd = AbsolutePathBuf::from_absolute_path(
canonicalize(temp_dir.path()).expect("canonicalize temp dir"),
)
.expect("absolute temp dir");
let allowed_path = cwd.join("allowed").expect("allowed path");
let denied_path = cwd.join("denied").expect("denied path");
let merged_policy = merge_file_system_policy_with_additional_permissions(
&FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath {
kind: FileSystemSpecialPathKind::Root,
subpath: None,
},
},
access: FileSystemAccessMode::Read,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: denied_path.clone(),
},
access: FileSystemAccessMode::None,
},
]),
vec![allowed_path.clone()],
Vec::new(),
);
assert_eq!(
merged_policy.entries.contains(&FileSystemSandboxEntry {
path: FileSystemPath::Path { path: denied_path },
access: FileSystemAccessMode::None,
}),
true
);
assert_eq!(
merged_policy.entries.contains(&FileSystemSandboxEntry {
path: FileSystemPath::Path { path: allowed_path },
access: FileSystemAccessMode::Read,
}),
true
);
}
} }

View File

@@ -15,6 +15,7 @@ use tokio::process::Child;
use tracing::warn; use tracing::warn;
use url::Url; use url::Url;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy; use crate::protocol::SandboxPolicy;
use crate::seatbelt_permissions::MacOsSeatbeltProfileExtensions; use crate::seatbelt_permissions::MacOsSeatbeltProfileExtensions;
use crate::seatbelt_permissions::build_seatbelt_extensions; use crate::seatbelt_permissions::build_seatbelt_extensions;
@@ -51,7 +52,7 @@ pub async fn spawn_command_under_seatbelt(
args, args,
arg0, arg0,
cwd: command_cwd, cwd: command_cwd,
sandbox_policy, network_sandbox_policy: NetworkSandboxPolicy::from(sandbox_policy),
network, network,
stdio_policy, stdio_policy,
env, env,

View File

@@ -6,13 +6,13 @@ use tokio::process::Child;
use tokio::process::Command; use tokio::process::Command;
use tracing::trace; use tracing::trace;
use crate::protocol::SandboxPolicy; use crate::protocol::NetworkSandboxPolicy;
/// Experimental environment variable that will be set to some non-empty value /// Experimental environment variable that will be set to some non-empty value
/// if both of the following are true: /// if both of the following are true:
/// ///
/// 1. The process was spawned by Codex as part of a shell tool call. /// 1. The process was spawned by Codex as part of a shell tool call.
/// 2. SandboxPolicy.has_full_network_access() was false for the tool call. /// 2. NetworkSandboxPolicy is restricted for the tool call.
/// ///
/// We may try to have just one environment variable for all sandboxing /// We may try to have just one environment variable for all sandboxing
/// attributes, so this may change in the future. /// attributes, so this may change in the future.
@@ -33,15 +33,15 @@ pub enum StdioPolicy {
/// ensuring the args and environment variables used to create the `Command` /// ensuring the args and environment variables used to create the `Command`
/// (and `Child`) honor the configuration. /// (and `Child`) honor the configuration.
/// ///
/// For now, we take `SandboxPolicy` as a parameter to spawn_child() because /// For now, we take `NetworkSandboxPolicy` as a parameter to spawn_child()
/// we need to determine whether to set the /// because we need to determine whether to set the
/// `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` environment variable. /// `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` environment variable.
pub(crate) struct SpawnChildRequest<'a> { pub(crate) struct SpawnChildRequest<'a> {
pub program: PathBuf, pub program: PathBuf,
pub args: Vec<String>, pub args: Vec<String>,
pub arg0: Option<&'a str>, pub arg0: Option<&'a str>,
pub cwd: PathBuf, pub cwd: PathBuf,
pub sandbox_policy: &'a SandboxPolicy, pub network_sandbox_policy: NetworkSandboxPolicy,
pub network: Option<&'a NetworkProxy>, pub network: Option<&'a NetworkProxy>,
pub stdio_policy: StdioPolicy, pub stdio_policy: StdioPolicy,
pub env: HashMap<String, String>, pub env: HashMap<String, String>,
@@ -53,14 +53,14 @@ pub(crate) async fn spawn_child_async(request: SpawnChildRequest<'_>) -> std::io
args, args,
arg0, arg0,
cwd, cwd,
sandbox_policy, network_sandbox_policy,
network, network,
stdio_policy, stdio_policy,
mut env, mut env,
} = request; } = request;
trace!( trace!(
"spawn_child_async: {program:?} {args:?} {arg0:?} {cwd:?} {sandbox_policy:?} {stdio_policy:?} {env:?}" "spawn_child_async: {program:?} {args:?} {arg0:?} {cwd:?} {network_sandbox_policy:?} {stdio_policy:?} {env:?}"
); );
let mut cmd = Command::new(&program); let mut cmd = Command::new(&program);
@@ -74,7 +74,7 @@ pub(crate) async fn spawn_child_async(request: SpawnChildRequest<'_>) -> std::io
cmd.env_clear(); cmd.env_clear();
cmd.envs(env); cmd.envs(env);
if !sandbox_policy.has_full_network_access() { if !network_sandbox_policy.is_enabled() {
cmd.env(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR, "1"); cmd.env(CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR, "1");
} }

View File

@@ -22,6 +22,8 @@ use crate::protocol::ExecCommandBeginEvent;
use crate::protocol::ExecCommandEndEvent; use crate::protocol::ExecCommandEndEvent;
use crate::protocol::ExecCommandSource; use crate::protocol::ExecCommandSource;
use crate::protocol::ExecCommandStatus; use crate::protocol::ExecCommandStatus;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy; use crate::protocol::SandboxPolicy;
use crate::protocol::TurnStartedEvent; use crate::protocol::TurnStartedEvent;
use crate::sandboxing::ExecRequest; use crate::sandboxing::ExecRequest;
@@ -167,6 +169,8 @@ pub(crate) async fn execute_user_shell_command(
windows_sandbox_level: turn_context.windows_sandbox_level, windows_sandbox_level: turn_context.windows_sandbox_level,
sandbox_permissions: SandboxPermissions::UseDefault, sandbox_permissions: SandboxPermissions::UseDefault,
sandbox_policy: sandbox_policy.clone(), sandbox_policy: sandbox_policy.clone(),
file_system_sandbox_policy: FileSystemSandboxPolicy::from(&sandbox_policy),
network_sandbox_policy: NetworkSandboxPolicy::from(&sandbox_policy),
justification: None, justification: None,
arg0: None, arg0: None,
}; };

View File

@@ -852,7 +852,8 @@ impl JsReplManager {
.network .network
.is_some(); .is_some();
let sandbox_type = sandbox.select_initial( let sandbox_type = sandbox.select_initial(
&turn.sandbox_policy, &turn.file_system_sandbox_policy,
turn.network_sandbox_policy,
SandboxablePreference::Auto, SandboxablePreference::Auto,
turn.windows_sandbox_level, turn.windows_sandbox_level,
has_managed_network_requirements, has_managed_network_requirements,
@@ -861,6 +862,8 @@ impl JsReplManager {
.transform(crate::sandboxing::SandboxTransformRequest { .transform(crate::sandboxing::SandboxTransformRequest {
spec, spec,
policy: &turn.sandbox_policy, policy: &turn.sandbox_policy,
file_system_policy: &turn.file_system_sandbox_policy,
network_policy: turn.network_sandbox_policy,
sandbox: sandbox_type, sandbox: sandbox_type,
enforce_managed_network: has_managed_network_requirements, enforce_managed_network: has_managed_network_requirements,
network: None, network: None,

View File

@@ -169,7 +169,8 @@ impl ToolOrchestrator {
let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) { let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) {
SandboxOverride::BypassSandboxFirstAttempt => crate::exec::SandboxType::None, SandboxOverride::BypassSandboxFirstAttempt => crate::exec::SandboxType::None,
SandboxOverride::NoOverride => self.sandbox.select_initial( SandboxOverride::NoOverride => self.sandbox.select_initial(
&turn_ctx.sandbox_policy, &turn_ctx.file_system_sandbox_policy,
turn_ctx.network_sandbox_policy,
tool.sandbox_preference(), tool.sandbox_preference(),
turn_ctx.windows_sandbox_level, turn_ctx.windows_sandbox_level,
has_managed_network_requirements, has_managed_network_requirements,
@@ -182,6 +183,8 @@ impl ToolOrchestrator {
let initial_attempt = SandboxAttempt { let initial_attempt = SandboxAttempt {
sandbox: initial_sandbox, sandbox: initial_sandbox,
policy: &turn_ctx.sandbox_policy, policy: &turn_ctx.sandbox_policy,
file_system_policy: &turn_ctx.file_system_sandbox_policy,
network_policy: turn_ctx.network_sandbox_policy,
enforce_managed_network: has_managed_network_requirements, enforce_managed_network: has_managed_network_requirements,
manager: &self.sandbox, manager: &self.sandbox,
sandbox_cwd: &turn_ctx.cwd, sandbox_cwd: &turn_ctx.cwd,
@@ -296,6 +299,8 @@ impl ToolOrchestrator {
let escalated_attempt = SandboxAttempt { let escalated_attempt = SandboxAttempt {
sandbox: crate::exec::SandboxType::None, sandbox: crate::exec::SandboxType::None,
policy: &turn_ctx.sandbox_policy, policy: &turn_ctx.sandbox_policy,
file_system_policy: &turn_ctx.file_system_sandbox_policy,
network_policy: turn_ctx.network_sandbox_policy,
enforce_managed_network: has_managed_network_requirements, enforce_managed_network: has_managed_network_requirements,
manager: &self.sandbox, manager: &self.sandbox,
sandbox_cwd: &turn_ctx.cwd, sandbox_cwd: &turn_ctx.cwd,

View File

@@ -25,7 +25,9 @@ use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::MacOsSeatbeltProfileExtensions; use codex_protocol::models::MacOsSeatbeltProfileExtensions;
use codex_protocol::models::PermissionProfile; use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::FileSystemSandboxPolicy;
use codex_protocol::protocol::NetworkPolicyRuleAction; use codex_protocol::protocol::NetworkPolicyRuleAction;
use codex_protocol::protocol::NetworkSandboxPolicy;
use codex_protocol::protocol::RejectConfig; use codex_protocol::protocol::RejectConfig;
use codex_protocol::protocol::ReviewDecision; use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SandboxPolicy;
@@ -98,6 +100,8 @@ pub(super) async fn try_run_zsh_fork(
windows_sandbox_level, windows_sandbox_level,
sandbox_permissions, sandbox_permissions,
sandbox_policy, sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
justification, justification,
arg0, arg0,
} = sandbox_exec_request; } = sandbox_exec_request;
@@ -113,6 +117,8 @@ pub(super) async fn try_run_zsh_fork(
command, command,
cwd: sandbox_cwd, cwd: sandbox_cwd,
sandbox_policy, sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
sandbox, sandbox,
env: sandbox_env, env: sandbox_env,
network: sandbox_network, network: sandbox_network,
@@ -220,6 +226,8 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
command: exec_request.command.clone(), command: exec_request.command.clone(),
cwd: exec_request.cwd.clone(), cwd: exec_request.cwd.clone(),
sandbox_policy: exec_request.sandbox_policy.clone(), sandbox_policy: exec_request.sandbox_policy.clone(),
file_system_sandbox_policy: exec_request.file_system_sandbox_policy.clone(),
network_sandbox_policy: exec_request.network_sandbox_policy,
sandbox: exec_request.sandbox, sandbox: exec_request.sandbox,
env: exec_request.env.clone(), env: exec_request.env.clone(),
network: exec_request.network.clone(), network: exec_request.network.clone(),
@@ -728,6 +736,8 @@ struct CoreShellCommandExecutor {
command: Vec<String>, command: Vec<String>,
cwd: PathBuf, cwd: PathBuf,
sandbox_policy: SandboxPolicy, sandbox_policy: SandboxPolicy,
file_system_sandbox_policy: FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox: SandboxType, sandbox: SandboxType,
env: HashMap<String, String>, env: HashMap<String, String>,
network: Option<codex_network_proxy::NetworkProxy>, network: Option<codex_network_proxy::NetworkProxy>,
@@ -747,6 +757,8 @@ struct PrepareSandboxedExecParams<'a> {
workdir: &'a AbsolutePathBuf, workdir: &'a AbsolutePathBuf,
env: HashMap<String, String>, env: HashMap<String, String>,
sandbox_policy: &'a SandboxPolicy, sandbox_policy: &'a SandboxPolicy,
file_system_sandbox_policy: &'a FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
additional_permissions: Option<PermissionProfile>, additional_permissions: Option<PermissionProfile>,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: Option<&'a MacOsSeatbeltProfileExtensions>, macos_seatbelt_profile_extensions: Option<&'a MacOsSeatbeltProfileExtensions>,
@@ -782,6 +794,8 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
windows_sandbox_level: self.windows_sandbox_level, windows_sandbox_level: self.windows_sandbox_level,
sandbox_permissions: self.sandbox_permissions, sandbox_permissions: self.sandbox_permissions,
sandbox_policy: self.sandbox_policy.clone(), sandbox_policy: self.sandbox_policy.clone(),
file_system_sandbox_policy: self.file_system_sandbox_policy.clone(),
network_sandbox_policy: self.network_sandbox_policy,
justification: self.justification.clone(), justification: self.justification.clone(),
arg0: self.arg0.clone(), arg0: self.arg0.clone(),
}, },
@@ -828,6 +842,8 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
workdir, workdir,
env, env,
sandbox_policy: &self.sandbox_policy, sandbox_policy: &self.sandbox_policy,
file_system_sandbox_policy: &self.file_system_sandbox_policy,
network_sandbox_policy: self.network_sandbox_policy,
additional_permissions: None, additional_permissions: None,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: self macos_seatbelt_profile_extensions: self
@@ -845,6 +861,8 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
workdir, workdir,
env, env,
sandbox_policy: &self.sandbox_policy, sandbox_policy: &self.sandbox_policy,
file_system_sandbox_policy: &self.file_system_sandbox_policy,
network_sandbox_policy: self.network_sandbox_policy,
additional_permissions: Some(permission_profile), additional_permissions: Some(permission_profile),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: self macos_seatbelt_profile_extensions: self
@@ -854,11 +872,17 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
} }
EscalationExecution::Permissions(EscalationPermissions::Permissions(permissions)) => { EscalationExecution::Permissions(EscalationPermissions::Permissions(permissions)) => {
// Use a fully specified sandbox policy instead of merging into the turn policy. // Use a fully specified sandbox policy instead of merging into the turn policy.
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from(&permissions.sandbox_policy);
let network_sandbox_policy =
NetworkSandboxPolicy::from(&permissions.sandbox_policy);
self.prepare_sandboxed_exec(PrepareSandboxedExecParams { self.prepare_sandboxed_exec(PrepareSandboxedExecParams {
command, command,
workdir, workdir,
env, env,
sandbox_policy: &permissions.sandbox_policy, sandbox_policy: &permissions.sandbox_policy,
file_system_sandbox_policy: &file_system_sandbox_policy,
network_sandbox_policy,
additional_permissions: None, additional_permissions: None,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions: permissions macos_seatbelt_profile_extensions: permissions
@@ -873,6 +897,7 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
} }
impl CoreShellCommandExecutor { impl CoreShellCommandExecutor {
#[allow(clippy::too_many_arguments)]
fn prepare_sandboxed_exec( fn prepare_sandboxed_exec(
&self, &self,
params: PrepareSandboxedExecParams<'_>, params: PrepareSandboxedExecParams<'_>,
@@ -882,6 +907,8 @@ impl CoreShellCommandExecutor {
workdir, workdir,
env, env,
sandbox_policy, sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
additional_permissions, additional_permissions,
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
macos_seatbelt_profile_extensions, macos_seatbelt_profile_extensions,
@@ -891,7 +918,8 @@ impl CoreShellCommandExecutor {
.ok_or_else(|| anyhow::anyhow!("prepared command must not be empty"))?; .ok_or_else(|| anyhow::anyhow!("prepared command must not be empty"))?;
let sandbox_manager = crate::sandboxing::SandboxManager::new(); let sandbox_manager = crate::sandboxing::SandboxManager::new();
let sandbox = sandbox_manager.select_initial( let sandbox = sandbox_manager.select_initial(
sandbox_policy, file_system_sandbox_policy,
network_sandbox_policy,
SandboxablePreference::Auto, SandboxablePreference::Auto,
self.windows_sandbox_level, self.windows_sandbox_level,
self.network.is_some(), self.network.is_some(),
@@ -913,6 +941,8 @@ impl CoreShellCommandExecutor {
justification: self.justification.clone(), justification: self.justification.clone(),
}, },
policy: sandbox_policy, policy: sandbox_policy,
file_system_policy: file_system_sandbox_policy,
network_policy: network_sandbox_policy,
sandbox, sandbox,
enforce_managed_network: self.network.is_some(), enforce_managed_network: self.network.is_some(),
network: self.network.as_ref(), network: self.network.as_ref(),

View File

@@ -478,6 +478,10 @@ async fn prepare_escalated_exec_turn_default_preserves_macos_seatbelt_extensions
network: None, network: None,
sandbox: SandboxType::None, sandbox: SandboxType::None,
sandbox_policy: SandboxPolicy::new_read_only_policy(), sandbox_policy: SandboxPolicy::new_read_only_policy(),
file_system_sandbox_policy: FileSystemSandboxPolicy::from(
&SandboxPolicy::new_read_only_policy(),
),
network_sandbox_policy: NetworkSandboxPolicy::Restricted,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
sandbox_permissions: SandboxPermissions::UseDefault, sandbox_permissions: SandboxPermissions::UseDefault,
justification: None, justification: None,
@@ -528,6 +532,8 @@ async fn prepare_escalated_exec_permissions_preserve_macos_seatbelt_extensions()
network: None, network: None,
sandbox: SandboxType::None, sandbox: SandboxType::None,
sandbox_policy: SandboxPolicy::DangerFullAccess, sandbox_policy: SandboxPolicy::DangerFullAccess,
file_system_sandbox_policy: FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
network_sandbox_policy: NetworkSandboxPolicy::Enabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
sandbox_permissions: SandboxPermissions::UseDefault, sandbox_permissions: SandboxPermissions::UseDefault,
justification: None, justification: None,
@@ -592,13 +598,16 @@ async fn prepare_escalated_exec_permissions_preserve_macos_seatbelt_extensions()
#[tokio::test] #[tokio::test]
async fn prepare_escalated_exec_permission_profile_unions_turn_and_requested_macos_extensions() { async fn prepare_escalated_exec_permission_profile_unions_turn_and_requested_macos_extensions() {
let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap(); let cwd = AbsolutePathBuf::from_absolute_path(std::env::temp_dir()).unwrap();
let sandbox_policy = SandboxPolicy::new_read_only_policy();
let executor = CoreShellCommandExecutor { let executor = CoreShellCommandExecutor {
command: vec!["echo".to_string(), "ok".to_string()], command: vec!["echo".to_string(), "ok".to_string()],
cwd: cwd.to_path_buf(), cwd: cwd.to_path_buf(),
env: HashMap::new(), env: HashMap::new(),
network: None, network: None,
sandbox: SandboxType::None, sandbox: SandboxType::None,
sandbox_policy: SandboxPolicy::new_read_only_policy(), sandbox_policy: sandbox_policy.clone(),
file_system_sandbox_policy: FileSystemSandboxPolicy::from(&sandbox_policy),
network_sandbox_policy: NetworkSandboxPolicy::from(&sandbox_policy),
windows_sandbox_level: WindowsSandboxLevel::Disabled, windows_sandbox_level: WindowsSandboxLevel::Disabled,
sandbox_permissions: SandboxPermissions::UseDefault, sandbox_permissions: SandboxPermissions::UseDefault,
justification: None, justification: None,

View File

@@ -7,6 +7,8 @@
use crate::codex::Session; use crate::codex::Session;
use crate::codex::TurnContext; use crate::codex::TurnContext;
use crate::error::CodexErr; use crate::error::CodexErr;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy; use crate::protocol::SandboxPolicy;
use crate::sandboxing::CommandSpec; use crate::sandboxing::CommandSpec;
use crate::sandboxing::SandboxManager; use crate::sandboxing::SandboxManager;
@@ -318,6 +320,8 @@ pub(crate) trait ToolRuntime<Req, Out>: Approvable<Req> + Sandboxable {
pub(crate) struct SandboxAttempt<'a> { pub(crate) struct SandboxAttempt<'a> {
pub sandbox: crate::exec::SandboxType, pub sandbox: crate::exec::SandboxType,
pub policy: &'a crate::protocol::SandboxPolicy, pub policy: &'a crate::protocol::SandboxPolicy,
pub file_system_policy: &'a FileSystemSandboxPolicy,
pub network_policy: NetworkSandboxPolicy,
pub enforce_managed_network: bool, pub enforce_managed_network: bool,
pub(crate) manager: &'a SandboxManager, pub(crate) manager: &'a SandboxManager,
pub(crate) sandbox_cwd: &'a Path, pub(crate) sandbox_cwd: &'a Path,
@@ -336,6 +340,8 @@ impl<'a> SandboxAttempt<'a> {
.transform(crate::sandboxing::SandboxTransformRequest { .transform(crate::sandboxing::SandboxTransformRequest {
spec, spec,
policy: self.policy, policy: self.policy,
file_system_policy: self.file_system_policy,
network_policy: self.network_policy,
sandbox: self.sandbox, sandbox: self.sandbox,
enforce_managed_network: self.enforce_managed_network, enforce_managed_network: self.enforce_managed_network,
network, network,

View File

@@ -205,6 +205,10 @@ mod tests {
turn.sandbox_policy turn.sandbox_policy
.set(SandboxPolicy::DangerFullAccess) .set(SandboxPolicy::DangerFullAccess)
.expect("test setup should allow updating sandbox policy"); .expect("test setup should allow updating sandbox policy");
turn.file_system_sandbox_policy =
crate::protocol::FileSystemSandboxPolicy::from(turn.sandbox_policy.get());
turn.network_sandbox_policy =
crate::protocol::NetworkSandboxPolicy::from(turn.sandbox_policy.get());
(Arc::new(session), Arc::new(turn)) (Arc::new(session), Arc::new(turn))
} }

View File

@@ -10,6 +10,8 @@ use codex_core::exec::process_exec_tool_call;
use codex_core::sandboxing::SandboxPermissions; use codex_core::sandboxing::SandboxPermissions;
use codex_core::spawn::CODEX_SANDBOX_ENV_VAR; use codex_core::spawn::CODEX_SANDBOX_ENV_VAR;
use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::protocol::FileSystemSandboxPolicy;
use codex_protocol::protocol::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SandboxPolicy;
use tempfile::TempDir; use tempfile::TempDir;
@@ -45,7 +47,17 @@ async fn run_test_cmd(tmp: TempDir, cmd: Vec<&str>) -> Result<ExecToolCallOutput
let policy = SandboxPolicy::new_read_only_policy(); let policy = SandboxPolicy::new_read_only_policy();
process_exec_tool_call(params, &policy, tmp.path(), &None, false, None).await process_exec_tool_call(
params,
&policy,
&FileSystemSandboxPolicy::from(&policy),
NetworkSandboxPolicy::from(&policy),
tmp.path(),
&None,
false,
None,
)
.await
} }
/// Command succeeds with exit code 0 normally /// Command succeeds with exit code 0 normally

View File

@@ -5,6 +5,7 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecCommandEndEvent; use codex_protocol::protocol::ExecCommandEndEvent;
use codex_protocol::protocol::ExecCommandSource; use codex_protocol::protocol::ExecCommandSource;
use codex_protocol::protocol::ExecOutputStream; use codex_protocol::protocol::ExecOutputStream;
use codex_protocol::protocol::NetworkSandboxPolicy;
use codex_protocol::protocol::Op; use codex_protocol::protocol::Op;
use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::TurnAbortReason; use codex_protocol::protocol::TurnAbortReason;
@@ -23,6 +24,7 @@ use core_test_support::test_codex::test_codex;
use core_test_support::wait_for_event; use core_test_support::wait_for_event;
use core_test_support::wait_for_event_match; use core_test_support::wait_for_event_match;
use core_test_support::wait_for_event_with_timeout; use core_test_support::wait_for_event_with_timeout;
use pretty_assertions::assert_eq;
use regex_lite::escape; use regex_lite::escape;
use std::path::PathBuf; use std::path::PathBuf;
use tempfile::TempDir; use tempfile::TempDir;
@@ -328,6 +330,35 @@ async fn user_shell_command_history_is_persisted_and_shared_with_model() -> anyh
Ok(()) Ok(())
} }
#[tokio::test]
async fn user_shell_command_does_not_set_network_sandbox_env_var() -> anyhow::Result<()> {
let server = responses::start_mock_server().await;
let mut builder = core_test_support::test_codex::test_codex().with_config(|config| {
config.permissions.network_sandbox_policy = NetworkSandboxPolicy::Restricted;
});
let test = builder.build(&server).await?;
#[cfg(windows)]
let command = r#"$val = $env:CODEX_SANDBOX_NETWORK_DISABLED; if ([string]::IsNullOrEmpty($val)) { $val = 'not-set' } ; [System.Console]::Write($val)"#.to_string();
#[cfg(not(windows))]
let command =
r#"sh -c "printf '%s' \"${CODEX_SANDBOX_NETWORK_DISABLED:-not-set}\"""#.to_string();
test.codex
.submit(Op::RunUserShellCommand { command })
.await?;
let end_event = wait_for_event_match(&test.codex, |ev| match ev {
EventMsg::ExecCommandEnd(event) => Some(event.clone()),
_ => None,
})
.await;
assert_eq!(end_event.exit_code, 0);
assert_eq!(end_event.stdout.trim(), "not-set");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[cfg(not(target_os = "windows"))] // TODO: unignore on windows #[cfg(not(target_os = "windows"))] // TODO: unignore on windows
async fn user_shell_command_output_is_truncated_in_history() -> anyhow::Result<()> { async fn user_shell_command_output_is_truncated_in_history() -> anyhow::Result<()> {

View File

@@ -9,6 +9,8 @@ use codex_core::exec::process_exec_tool_call;
use codex_core::exec_env::create_env; use codex_core::exec_env::create_env;
use codex_core::sandboxing::SandboxPermissions; use codex_core::sandboxing::SandboxPermissions;
use codex_protocol::config_types::WindowsSandboxLevel; use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::protocol::FileSystemSandboxPolicy;
use codex_protocol::protocol::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
@@ -102,6 +104,8 @@ async fn run_cmd_result_with_writable_roots(
process_exec_tool_call( process_exec_tool_call(
params, params,
&sandbox_policy, &sandbox_policy,
&FileSystemSandboxPolicy::from(&sandbox_policy),
NetworkSandboxPolicy::from(&sandbox_policy),
sandbox_cwd.as_path(), sandbox_cwd.as_path(),
&codex_linux_sandbox_exe, &codex_linux_sandbox_exe,
use_bwrap_sandbox, use_bwrap_sandbox,
@@ -333,6 +337,8 @@ async fn assert_network_blocked(cmd: &[&str]) {
let result = process_exec_tool_call( let result = process_exec_tool_call(
params, params,
&sandbox_policy, &sandbox_policy,
&FileSystemSandboxPolicy::from(&sandbox_policy),
NetworkSandboxPolicy::from(&sandbox_policy),
sandbox_cwd.as_path(), sandbox_cwd.as_path(),
&codex_linux_sandbox_exe, &codex_linux_sandbox_exe,
false, false,