Merge 3d8f475bce into sapling-pr-archive-bolinfest

This commit is contained in:
Michael Bolin
2026-03-04 00:32:52 -08:00
committed by GitHub
16 changed files with 317 additions and 40 deletions
@@ -1506,9 +1506,19 @@ impl CodexMessageProcessor {
};
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) {
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) => {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
@@ -1519,7 +1529,11 @@ impl CodexMessageProcessor {
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();
@@ -1534,6 +1548,8 @@ impl CodexMessageProcessor {
match codex_core::exec::process_exec_tool_call(
exec_params,
&effective_policy,
&effective_file_system_sandbox_policy,
effective_network_sandbox_policy,
sandbox_cwd.as_path(),
&codex_linux_sandbox_exe,
use_linux_sandbox_bwrap,
+30
View File
@@ -222,10 +222,12 @@ use crate::protocol::ErrorEvent;
use crate::protocol::Event;
use crate::protocol::EventMsg;
use crate::protocol::ExecApprovalRequestEvent;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::McpServerRefreshConfig;
use crate::protocol::ModelRerouteEvent;
use crate::protocol::ModelRerouteReason;
use crate::protocol::NetworkApprovalContext;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::Op;
use crate::protocol::PlanDeltaEvent;
use crate::protocol::RateLimitSnapshot;
@@ -482,6 +484,8 @@ impl Codex {
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_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),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -676,6 +680,8 @@ pub(crate) struct TurnContext {
pub(crate) personality: Option<Personality>,
pub(crate) approval_policy: Constrained<AskForApproval>,
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) windows_sandbox_level: WindowsSandboxLevel,
pub(crate) shell_environment_policy: ShellEnvironmentPolicy,
@@ -764,6 +770,8 @@ impl TurnContext {
personality: self.personality,
approval_policy: self.approval_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(),
windows_sandbox_level: self.windows_sandbox_level,
shell_environment_policy: self.shell_environment_policy.clone(),
@@ -867,6 +875,8 @@ pub(crate) struct SessionConfiguration {
approval_policy: Constrained<AskForApproval>,
/// How to sandbox commands executed in the system
sandbox_policy: Constrained<SandboxPolicy>,
file_system_sandbox_policy: FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
windows_sandbox_level: WindowsSandboxLevel,
/// Working directory that should be treated as the *root* of the
@@ -933,6 +943,10 @@ impl SessionConfiguration {
}
if let Some(sandbox_policy) = updates.sandbox_policy.clone() {
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 {
next_configuration.windows_sandbox_level = windows_sandbox_level;
@@ -1145,6 +1159,8 @@ impl Session {
personality: session_configuration.personality,
approval_policy: session_configuration.approval_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,
windows_sandbox_level: session_configuration.windows_sandbox_level,
shell_environment_policy: per_turn_config.permissions.shell_environment_policy.clone(),
@@ -4805,6 +4821,8 @@ async fn spawn_review_thread(
personality: parent_turn_context.personality,
approval_policy: parent_turn_context.approval_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(),
windows_sandbox_level: parent_turn_context.windows_sandbox_level,
shell_environment_policy: parent_turn_context.shell_environment_policy.clone(),
@@ -7832,6 +7850,8 @@ mod tests {
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_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),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -7927,6 +7947,8 @@ mod tests {
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_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),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -8247,6 +8269,8 @@ mod tests {
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_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),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -8306,6 +8330,8 @@ mod tests {
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_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),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -8398,6 +8424,8 @@ mod tests {
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_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),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
@@ -8647,6 +8675,8 @@ mod tests {
compact_prompt: config.compact_prompt.clone(),
approval_policy: config.permissions.approval_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),
cwd: config.cwd.clone(),
codex_home: config.codex_home.clone(),
+30 -10
View File
@@ -24,6 +24,9 @@ use crate::protocol::Event;
use crate::protocol::EventMsg;
use crate::protocol::ExecCommandOutputDeltaEvent;
use crate::protocol::ExecOutputStream;
use crate::protocol::FileSystemSandboxKind;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy;
use crate::sandboxing::CommandSpec;
use crate::sandboxing::ExecRequest;
@@ -149,9 +152,12 @@ pub struct StdoutStream {
pub tx_event: Sender<Event>,
}
#[allow(clippy::too_many_arguments)]
pub async fn process_exec_tool_call(
params: ExecParams,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox_cwd: &Path,
codex_linux_sandbox_exe: &Option<PathBuf>,
use_linux_sandbox_bwrap: bool,
@@ -159,8 +165,8 @@ pub async fn process_exec_tool_call(
) -> Result<ExecToolCallOutput> {
let windows_sandbox_level = params.windows_sandbox_level;
let enforce_managed_network = params.network.is_some();
let sandbox_type = match &sandbox_policy {
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
let sandbox_type = match file_system_sandbox_policy.kind {
FileSystemSandboxKind::Unrestricted | FileSystemSandboxKind::ExternalSandbox => {
if enforce_managed_network {
get_platform_sandbox(
windows_sandbox_level
@@ -215,6 +221,8 @@ pub async fn process_exec_tool_call(
.transform(crate::sandboxing::SandboxTransformRequest {
spec,
policy: sandbox_policy,
file_system_policy: file_system_sandbox_policy,
network_policy: network_sandbox_policy,
sandbox: sandbox_type,
enforce_managed_network,
network: network.as_ref(),
@@ -233,7 +241,6 @@ pub async fn process_exec_tool_call(
pub(crate) async fn execute_exec_env(
env: ExecRequest,
sandbox_policy: &SandboxPolicy,
stdout_stream: Option<StdoutStream>,
) -> Result<ExecToolCallOutput> {
let ExecRequest {
@@ -245,7 +252,9 @@ pub(crate) async fn execute_exec_env(
sandbox,
windows_sandbox_level,
sandbox_permissions,
sandbox_policy: _sandbox_policy_from_env,
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
justification,
arg0,
} = env;
@@ -263,7 +272,15 @@ pub(crate) async fn execute_exec_env(
};
let start = Instant::now();
let raw_output_result = exec(params, sandbox, sandbox_policy, stdout_stream).await;
let raw_output_result = exec(
params,
sandbox,
&sandbox_policy,
&file_system_sandbox_policy,
network_sandbox_policy,
stdout_stream,
)
.await;
let duration = start.elapsed();
finalize_exec_result(raw_output_result, sandbox, duration)
}
@@ -692,14 +709,13 @@ async fn exec(
params: ExecParams,
sandbox: SandboxType,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
stdout_stream: Option<StdoutStream>,
) -> Result<RawExecToolCallOutput> {
#[cfg(target_os = "windows")]
if sandbox == SandboxType::WindowsRestrictedToken
&& !matches!(
sandbox_policy,
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. }
)
&& file_system_sandbox_policy.kind == FileSystemSandboxKind::Restricted
{
return exec_windows_sandbox(params, sandbox_policy).await;
}
@@ -729,7 +745,7 @@ async fn exec(
args: args.into(),
arg0: arg0_ref,
cwd,
sandbox_policy,
network_sandbox_policy,
// The environment already has attempt-scoped proxy settings from
// apply_to_env_for_attempt above. Passing network here would reapply
// non-attempt proxy vars and drop attempt correlation metadata.
@@ -1135,6 +1151,8 @@ mod tests {
params,
SandboxType::None,
&SandboxPolicy::new_read_only_policy(),
&FileSystemSandboxPolicy::from(&SandboxPolicy::new_read_only_policy()),
NetworkSandboxPolicy::Restricted,
None,
)
.await?;
@@ -1190,6 +1208,8 @@ mod tests {
let result = process_exec_tool_call(
params,
&SandboxPolicy::DangerFullAccess,
&FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
NetworkSandboxPolicy::Enabled,
cwd.as_path(),
&None,
false,
+2 -1
View File
@@ -1,3 +1,4 @@
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy;
use crate::spawn::SpawnChildRequest;
use crate::spawn::StdioPolicy;
@@ -44,7 +45,7 @@ where
args,
arg0,
cwd: command_cwd,
sandbox_policy,
network_sandbox_policy: NetworkSandboxPolicy::from(sandbox_policy),
network,
stdio_policy,
env,
+125 -12
View File
@@ -13,6 +13,9 @@ use crate::exec::StdoutStream;
use crate::exec::execute_exec_env;
use crate::landlock::allow_network_for_proxy;
use crate::landlock::create_linux_sandbox_command_args;
use crate::protocol::FileSystemSandboxKind;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy;
#[cfg(target_os = "macos")]
use crate::seatbelt::MACOS_PATH_TO_SEATBELT_EXECUTABLE;
@@ -60,6 +63,8 @@ pub struct ExecRequest {
pub windows_sandbox_level: WindowsSandboxLevel,
pub sandbox_permissions: SandboxPermissions,
pub sandbox_policy: SandboxPolicy,
pub file_system_sandbox_policy: FileSystemSandboxPolicy,
pub network_sandbox_policy: NetworkSandboxPolicy,
pub justification: Option<String>,
pub arg0: Option<String>,
}
@@ -70,6 +75,8 @@ pub struct ExecRequest {
pub(crate) struct SandboxTransformRequest<'a> {
pub spec: CommandSpec,
pub policy: &'a SandboxPolicy,
pub file_system_policy: &'a FileSystemSandboxPolicy,
pub network_policy: NetworkSandboxPolicy,
pub sandbox: SandboxType,
pub enforce_managed_network: bool,
// TODO(viyatb): Evaluate switching this to Option<Arc<NetworkProxy>>
@@ -270,7 +277,7 @@ impl SandboxManager {
pub(crate) fn select_initial(
&self,
policy: &SandboxPolicy,
file_system_policy: &FileSystemSandboxPolicy,
pref: SandboxablePreference,
windows_sandbox_level: WindowsSandboxLevel,
has_managed_network_requirements: bool,
@@ -285,8 +292,8 @@ impl SandboxManager {
)
.unwrap_or(SandboxType::None)
}
SandboxablePreference::Auto => match policy {
SandboxPolicy::DangerFullAccess | SandboxPolicy::ExternalSandbox { .. } => {
SandboxablePreference::Auto => match file_system_policy.kind {
FileSystemSandboxKind::Unrestricted | FileSystemSandboxKind::ExternalSandbox => {
if has_managed_network_requirements {
crate::safety::get_platform_sandbox(
windows_sandbox_level != WindowsSandboxLevel::Disabled,
@@ -311,6 +318,8 @@ impl SandboxManager {
let SandboxTransformRequest {
mut spec,
policy,
file_system_policy,
network_policy,
sandbox,
enforce_managed_network,
network,
@@ -321,14 +330,45 @@ impl SandboxManager {
use_linux_sandbox_bwrap,
windows_sandbox_level,
} = request;
let effective_policy =
if let Some(additional_permissions) = spec.additional_permissions.take() {
sandbox_policy_with_additional_permissions(policy, &additional_permissions)?
let effective_policy = if let Some(additional_permissions) =
spec.additional_permissions.take()
{
let (extra_reads, extra_writes) = additional_permission_roots(&additional_permissions);
let effective_policy =
sandbox_policy_with_additional_permissions(policy, &additional_permissions)?;
let file_system_sandbox_policy = if extra_reads.is_empty() && extra_writes.is_empty() {
file_system_policy.clone()
} else {
policy.clone()
match file_system_policy.kind {
FileSystemSandboxKind::Restricted => {
FileSystemSandboxPolicy::from(&effective_policy)
}
FileSystemSandboxKind::Unrestricted
| FileSystemSandboxKind::ExternalSandbox => file_system_policy.clone(),
}
};
let network_sandbox_policy = if additional_permissions
.network
.as_ref()
.and_then(|network| network.enabled)
.unwrap_or(false)
{
NetworkSandboxPolicy::Enabled
} else {
network_policy
};
(
effective_policy,
file_system_sandbox_policy,
network_sandbox_policy,
)
} else {
(policy.clone(), file_system_policy.clone(), network_policy)
};
let (effective_policy, effective_file_system_policy, effective_network_policy) =
effective_policy;
let mut env = spec.env;
if !effective_policy.has_full_network_access() {
if !effective_network_policy.is_enabled() {
env.insert(
CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR.to_string(),
"1".to_string(),
@@ -402,6 +442,8 @@ impl SandboxManager {
windows_sandbox_level,
sandbox_permissions: spec.sandbox_permissions,
sandbox_policy: effective_policy,
file_system_sandbox_policy: effective_file_system_policy,
network_sandbox_policy: effective_network_policy,
justification: spec.justification,
arg0: arg0_override,
})
@@ -416,8 +458,7 @@ pub async fn execute_env(
env: ExecRequest,
stdout_stream: Option<StdoutStream>,
) -> crate::error::Result<ExecToolCallOutput> {
let effective_policy = env.sandbox_policy.clone();
execute_exec_env(env, &effective_policy, stdout_stream).await
execute_exec_env(env, stdout_stream).await
}
#[cfg(test)]
@@ -426,6 +467,13 @@ mod tests {
use super::normalize_additional_permissions;
use super::sandbox_policy_with_additional_permissions;
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::NetworkSandboxPolicy;
use crate::protocol::ReadOnlyAccess;
use crate::protocol::SandboxPolicy;
use crate::tools::sandboxing::SandboxablePreference;
@@ -435,13 +483,14 @@ mod tests {
use codex_utils_absolute_path::AbsolutePathBuf;
use dunce::canonicalize;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use tempfile::TempDir;
#[test]
fn danger_full_access_defaults_to_no_sandbox_without_network_requirements() {
let manager = SandboxManager::new();
let sandbox = manager.select_initial(
&SandboxPolicy::DangerFullAccess,
&FileSystemSandboxPolicy::unrestricted(),
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
false,
@@ -454,7 +503,7 @@ mod tests {
let manager = SandboxManager::new();
let expected = crate::safety::get_platform_sandbox(false).unwrap_or(SandboxType::None);
let sandbox = manager.select_initial(
&SandboxPolicy::DangerFullAccess,
&FileSystemSandboxPolicy::unrestricted(),
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
true,
@@ -462,6 +511,70 @@ mod tests {
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,
}]),
SandboxablePreference::Auto,
WindowsSandboxLevel::Disabled,
false,
);
assert_eq!(sandbox, expected);
}
#[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]
fn normalize_additional_permissions_preserves_network() {
let temp_dir = TempDir::new().expect("create temp dir");
+2 -1
View File
@@ -15,6 +15,7 @@ use tokio::process::Child;
use tracing::warn;
use url::Url;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy;
use crate::seatbelt_permissions::MacOsSeatbeltProfileExtensions;
use crate::seatbelt_permissions::build_seatbelt_extensions;
@@ -51,7 +52,7 @@ pub async fn spawn_command_under_seatbelt(
args,
arg0,
cwd: command_cwd,
sandbox_policy,
network_sandbox_policy: NetworkSandboxPolicy::from(sandbox_policy),
network,
stdio_policy,
env,
+8 -8
View File
@@ -6,13 +6,13 @@ use tokio::process::Child;
use tokio::process::Command;
use tracing::trace;
use crate::protocol::SandboxPolicy;
use crate::protocol::NetworkSandboxPolicy;
/// Experimental environment variable that will be set to some non-empty value
/// if both of the following are true:
///
/// 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
/// 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`
/// (and `Child`) honor the configuration.
///
/// For now, we take `SandboxPolicy` as a parameter to spawn_child() because
/// we need to determine whether to set the
/// For now, we take `NetworkSandboxPolicy` as a parameter to spawn_child()
/// because we need to determine whether to set the
/// `CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR` environment variable.
pub(crate) struct SpawnChildRequest<'a> {
pub program: PathBuf,
pub args: Vec<String>,
pub arg0: Option<&'a str>,
pub cwd: PathBuf,
pub sandbox_policy: &'a SandboxPolicy,
pub network_sandbox_policy: NetworkSandboxPolicy,
pub network: Option<&'a NetworkProxy>,
pub stdio_policy: StdioPolicy,
pub env: HashMap<String, String>,
@@ -53,14 +53,14 @@ pub(crate) async fn spawn_child_async(request: SpawnChildRequest<'_>) -> std::io
args,
arg0,
cwd,
sandbox_policy,
network_sandbox_policy,
network,
stdio_policy,
mut env,
} = request;
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);
@@ -74,7 +74,7 @@ pub(crate) async fn spawn_child_async(request: SpawnChildRequest<'_>) -> std::io
cmd.env_clear();
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");
}
+5 -1
View File
@@ -22,6 +22,8 @@ use crate::protocol::ExecCommandBeginEvent;
use crate::protocol::ExecCommandEndEvent;
use crate::protocol::ExecCommandSource;
use crate::protocol::ExecCommandStatus;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy;
use crate::protocol::TurnStartedEvent;
use crate::sandboxing::ExecRequest;
@@ -163,6 +165,8 @@ pub(crate) async fn execute_user_shell_command(
windows_sandbox_level: turn_context.windows_sandbox_level,
sandbox_permissions: SandboxPermissions::UseDefault,
sandbox_policy: sandbox_policy.clone(),
file_system_sandbox_policy: FileSystemSandboxPolicy::from(&sandbox_policy),
network_sandbox_policy: NetworkSandboxPolicy::from(&sandbox_policy),
justification: None,
arg0: None,
};
@@ -173,7 +177,7 @@ pub(crate) async fn execute_user_shell_command(
tx_event: session.get_tx_event(),
});
let exec_result = execute_exec_env(exec_env, &sandbox_policy, stdout_stream)
let exec_result = execute_exec_env(exec_env, stdout_stream)
.or_cancel(&cancellation_token)
.await;
+3 -1
View File
@@ -852,7 +852,7 @@ impl JsReplManager {
.network
.is_some();
let sandbox_type = sandbox.select_initial(
&turn.sandbox_policy,
&turn.file_system_sandbox_policy,
SandboxablePreference::Auto,
turn.windows_sandbox_level,
has_managed_network_requirements,
@@ -861,6 +861,8 @@ impl JsReplManager {
.transform(crate::sandboxing::SandboxTransformRequest {
spec,
policy: &turn.sandbox_policy,
file_system_policy: &turn.file_system_sandbox_policy,
network_policy: turn.network_sandbox_policy,
sandbox: sandbox_type,
enforce_managed_network: has_managed_network_requirements,
network: None,
+5 -1
View File
@@ -169,7 +169,7 @@ impl ToolOrchestrator {
let initial_sandbox = match tool.sandbox_mode_for_first_attempt(req) {
SandboxOverride::BypassSandboxFirstAttempt => crate::exec::SandboxType::None,
SandboxOverride::NoOverride => self.sandbox.select_initial(
&turn_ctx.sandbox_policy,
&turn_ctx.file_system_sandbox_policy,
tool.sandbox_preference(),
turn_ctx.windows_sandbox_level,
has_managed_network_requirements,
@@ -182,6 +182,8 @@ impl ToolOrchestrator {
let initial_attempt = SandboxAttempt {
sandbox: initial_sandbox,
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,
manager: &self.sandbox,
sandbox_cwd: &turn_ctx.cwd,
@@ -296,6 +298,8 @@ impl ToolOrchestrator {
let escalated_attempt = SandboxAttempt {
sandbox: crate::exec::SandboxType::None,
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,
manager: &self.sandbox,
sandbox_cwd: &turn_ctx.cwd,
@@ -26,7 +26,9 @@ use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::MacOsSeatbeltProfileExtensions;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::FileSystemSandboxPolicy;
use codex_protocol::protocol::NetworkPolicyRuleAction;
use codex_protocol::protocol::NetworkSandboxPolicy;
use codex_protocol::protocol::RejectConfig;
use codex_protocol::protocol::ReviewDecision;
use codex_protocol::protocol::SandboxPolicy;
@@ -98,6 +100,8 @@ pub(super) async fn try_run_zsh_fork(
windows_sandbox_level,
sandbox_permissions,
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
justification,
arg0,
} = sandbox_exec_request;
@@ -110,6 +114,8 @@ pub(super) async fn try_run_zsh_fork(
command,
cwd: sandbox_cwd,
sandbox_policy,
file_system_sandbox_policy,
network_sandbox_policy,
sandbox,
env: sandbox_env,
network: sandbox_network,
@@ -721,6 +727,8 @@ struct CoreShellCommandExecutor {
command: Vec<String>,
cwd: PathBuf,
sandbox_policy: SandboxPolicy,
file_system_sandbox_policy: FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
sandbox: SandboxType,
env: HashMap<String, String>,
network: Option<codex_network_proxy::NetworkProxy>,
@@ -761,6 +769,8 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
windows_sandbox_level: self.windows_sandbox_level,
sandbox_permissions: self.sandbox_permissions,
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(),
arg0: self.arg0.clone(),
},
@@ -805,6 +815,8 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
workdir,
env,
&self.sandbox_policy,
&self.file_system_sandbox_policy,
self.network_sandbox_policy,
None,
self.macos_seatbelt_profile_extensions.as_ref(),
)?,
@@ -815,15 +827,23 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
workdir,
env,
&self.sandbox_policy,
&self.file_system_sandbox_policy,
self.network_sandbox_policy,
Some(permission_profile),
None,
)?,
EscalationExecution::Permissions(EscalationPermissions::Permissions(permissions)) => {
let file_system_sandbox_policy =
FileSystemSandboxPolicy::from(&permissions.sandbox_policy);
let network_sandbox_policy =
NetworkSandboxPolicy::from(&permissions.sandbox_policy);
self.prepare_sandboxed_exec(
command,
workdir,
env,
&permissions.sandbox_policy,
&file_system_sandbox_policy,
network_sandbox_policy,
None,
permissions.macos_seatbelt_profile_extensions.as_ref(),
)?
@@ -835,12 +855,15 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
}
impl CoreShellCommandExecutor {
#[allow(clippy::too_many_arguments)]
fn prepare_sandboxed_exec(
&self,
command: Vec<String>,
workdir: &AbsolutePathBuf,
env: HashMap<String, String>,
sandbox_policy: &SandboxPolicy,
file_system_sandbox_policy: &FileSystemSandboxPolicy,
network_sandbox_policy: NetworkSandboxPolicy,
additional_permissions: Option<PermissionProfile>,
#[cfg(target_os = "macos")] macos_seatbelt_profile_extensions: Option<
&MacOsSeatbeltProfileExtensions,
@@ -854,7 +877,7 @@ impl CoreShellCommandExecutor {
.ok_or_else(|| anyhow::anyhow!("prepared command must not be empty"))?;
let sandbox_manager = crate::sandboxing::SandboxManager::new();
let sandbox = sandbox_manager.select_initial(
sandbox_policy,
file_system_sandbox_policy,
SandboxablePreference::Auto,
self.windows_sandbox_level,
self.network.is_some(),
@@ -876,6 +899,8 @@ impl CoreShellCommandExecutor {
justification: self.justification.clone(),
},
policy: sandbox_policy,
file_system_policy: file_system_sandbox_policy,
network_policy: network_sandbox_policy,
sandbox,
enforce_managed_network: self.network.is_some(),
network: self.network.as_ref(),
@@ -426,6 +426,10 @@ async fn prepare_escalated_exec_turn_default_preserves_macos_seatbelt_extensions
network: None,
sandbox: SandboxType::None,
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,
sandbox_permissions: SandboxPermissions::UseDefault,
justification: None,
@@ -476,6 +480,8 @@ async fn prepare_escalated_exec_permissions_preserve_macos_seatbelt_extensions()
network: None,
sandbox: SandboxType::None,
sandbox_policy: SandboxPolicy::DangerFullAccess,
file_system_sandbox_policy: FileSystemSandboxPolicy::from(&SandboxPolicy::DangerFullAccess),
network_sandbox_policy: NetworkSandboxPolicy::Enabled,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
sandbox_permissions: SandboxPermissions::UseDefault,
justification: None,
+6
View File
@@ -7,6 +7,8 @@
use crate::codex::Session;
use crate::codex::TurnContext;
use crate::error::CodexErr;
use crate::protocol::FileSystemSandboxPolicy;
use crate::protocol::NetworkSandboxPolicy;
use crate::protocol::SandboxPolicy;
use crate::sandboxing::CommandSpec;
use crate::sandboxing::SandboxManager;
@@ -318,6 +320,8 @@ pub(crate) trait ToolRuntime<Req, Out>: Approvable<Req> + Sandboxable {
pub(crate) struct SandboxAttempt<'a> {
pub sandbox: crate::exec::SandboxType,
pub policy: &'a crate::protocol::SandboxPolicy,
pub file_system_policy: &'a FileSystemSandboxPolicy,
pub network_policy: NetworkSandboxPolicy,
pub enforce_managed_network: bool,
pub(crate) manager: &'a SandboxManager,
pub(crate) sandbox_cwd: &'a Path,
@@ -336,6 +340,8 @@ impl<'a> SandboxAttempt<'a> {
.transform(crate::sandboxing::SandboxTransformRequest {
spec,
policy: self.policy,
file_system_policy: self.file_system_policy,
network_policy: self.network_policy,
sandbox: self.sandbox,
enforce_managed_network: self.enforce_managed_network,
network,
+13 -1
View File
@@ -10,6 +10,8 @@ use codex_core::exec::process_exec_tool_call;
use codex_core::sandboxing::SandboxPermissions;
use codex_core::spawn::CODEX_SANDBOX_ENV_VAR;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::protocol::FileSystemSandboxPolicy;
use codex_protocol::protocol::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
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();
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
@@ -5,6 +5,7 @@ use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ExecCommandEndEvent;
use codex_protocol::protocol::ExecCommandSource;
use codex_protocol::protocol::ExecOutputStream;
use codex_protocol::protocol::NetworkSandboxPolicy;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::SandboxPolicy;
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_match;
use core_test_support::wait_for_event_with_timeout;
use pretty_assertions::assert_eq;
use regex_lite::escape;
use std::path::PathBuf;
use tempfile::TempDir;
@@ -328,6 +330,35 @@ async fn user_shell_command_history_is_persisted_and_shared_with_model() -> anyh
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)]
#[cfg(not(target_os = "windows"))] // TODO: unignore on windows
async fn user_shell_command_output_is_truncated_in_history() -> anyhow::Result<()> {
@@ -9,6 +9,8 @@ use codex_core::exec::process_exec_tool_call;
use codex_core::exec_env::create_env;
use codex_core::sandboxing::SandboxPermissions;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::protocol::FileSystemSandboxPolicy;
use codex_protocol::protocol::NetworkSandboxPolicy;
use codex_protocol::protocol::SandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
@@ -102,6 +104,8 @@ async fn run_cmd_result_with_writable_roots(
process_exec_tool_call(
params,
&sandbox_policy,
&FileSystemSandboxPolicy::from(&sandbox_policy),
NetworkSandboxPolicy::from(&sandbox_policy),
sandbox_cwd.as_path(),
&codex_linux_sandbox_exe,
use_bwrap_sandbox,
@@ -333,6 +337,8 @@ async fn assert_network_blocked(cmd: &[&str]) {
let result = process_exec_tool_call(
params,
&sandbox_policy,
&FileSystemSandboxPolicy::from(&sandbox_policy),
NetworkSandboxPolicy::from(&sandbox_policy),
sandbox_cwd.as_path(),
&codex_linux_sandbox_exe,
false,