mirror of
https://github.com/openai/codex.git
synced 2026-09-05 15:18:41 +00:00
Wire managed child MITM CA environments
This commit is contained in:
@@ -17,13 +17,15 @@ use codex_core::spawn::CODEX_SANDBOX_ENV_VAR;
|
||||
use codex_core::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
#[cfg(target_os = "linux")]
|
||||
use codex_sandboxing::ensure_legacy_landlock_supports_managed_mitm;
|
||||
use codex_sandboxing::landlock::allow_network_for_proxy;
|
||||
use codex_sandboxing::landlock::create_linux_sandbox_command_args_for_permission_profile;
|
||||
use codex_sandboxing::prepare_managed_network_child;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_sandboxing::seatbelt::CreateSeatbeltCommandArgsParams;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_sandboxing::seatbelt::create_seatbelt_command_args;
|
||||
use codex_sandboxing::with_managed_mitm_ca_readable_root;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
use tokio::process::Child;
|
||||
@@ -216,7 +218,7 @@ async fn run_command_under_sandbox(
|
||||
#[cfg(target_os = "windows")]
|
||||
let workspace_roots = config.effective_workspace_roots();
|
||||
|
||||
let env = create_env(
|
||||
let mut env = create_env(
|
||||
&config.permissions.shell_environment_policy,
|
||||
/*thread_id*/ None,
|
||||
);
|
||||
@@ -261,15 +263,36 @@ async fn run_command_under_sandbox(
|
||||
// Proxy containment depends on whether a proxy is active, not whether its
|
||||
// policy came from managed requirements.
|
||||
let enforce_managed_network = network.is_some();
|
||||
let managed_mitm_ca_trust_bundle_path = match network.as_ref() {
|
||||
Some(network) => network.managed_mitm_ca_trust_bundle_path(),
|
||||
None => None,
|
||||
};
|
||||
let runtime_permission_profile = with_managed_mitm_ca_readable_root(
|
||||
config.permissions.effective_permission_profile(),
|
||||
managed_mitm_ca_trust_bundle_path.as_ref(),
|
||||
#[expect(
|
||||
clippy::redundant_closure_for_method_calls,
|
||||
reason = "the concrete proxy type is not a direct dependency of codex-cli"
|
||||
)]
|
||||
let network_child_env = network.as_ref().map(|network| network.child_env_snapshot());
|
||||
let effective_permission_profile = config.permissions.effective_permission_profile();
|
||||
#[cfg(target_os = "linux")]
|
||||
if matches!(&sandbox_type, SandboxType::Landlock) {
|
||||
let file_system_sandbox_policy = effective_permission_profile.file_system_sandbox_policy();
|
||||
#[expect(
|
||||
clippy::redundant_closure_for_method_calls,
|
||||
reason = "the concrete snapshot type is not a direct dependency of codex-cli"
|
||||
)]
|
||||
let managed_mitm_ca_active = network_child_env
|
||||
.as_ref()
|
||||
.is_some_and(|snapshot| snapshot.has_managed_mitm_ca());
|
||||
ensure_legacy_landlock_supports_managed_mitm(
|
||||
&file_system_sandbox_policy,
|
||||
config.features.use_legacy_landlock(),
|
||||
managed_mitm_ca_active,
|
||||
)?;
|
||||
}
|
||||
let runtime_permission_profile = prepare_managed_network_child(
|
||||
network_child_env.as_ref(),
|
||||
&mut env,
|
||||
cwd.as_path(),
|
||||
effective_permission_profile,
|
||||
sandbox_policy_cwd.as_path(),
|
||||
);
|
||||
cfg!(target_os = "windows") && matches!(sandbox_type, SandboxType::Windows),
|
||||
)?;
|
||||
|
||||
let mut child = match sandbox_type {
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -294,9 +317,6 @@ async fn run_command_under_sandbox(
|
||||
env,
|
||||
|env_map| {
|
||||
env_map.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
|
||||
if let Some(network) = network.as_ref() {
|
||||
network.apply_to_env(env_map);
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?
|
||||
@@ -323,11 +343,7 @@ async fn run_command_under_sandbox(
|
||||
cwd.to_path_buf(),
|
||||
network_sandbox_policy,
|
||||
env,
|
||||
|env_map| {
|
||||
if let Some(network) = network.as_ref() {
|
||||
network.apply_to_env(env_map);
|
||||
}
|
||||
},
|
||||
|_env_map| {},
|
||||
)
|
||||
.await?
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
mod bwrap;
|
||||
pub mod landlock;
|
||||
mod managed_network;
|
||||
mod manager;
|
||||
pub mod policy_transforms;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -11,6 +12,7 @@ mod windows;
|
||||
pub use bwrap::find_system_bwrap_in_path;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use bwrap::system_bwrap_warning;
|
||||
pub use managed_network::prepare_managed_network_child;
|
||||
pub use manager::SandboxCommand;
|
||||
pub use manager::SandboxDirectSpawnTransformRequest;
|
||||
pub use manager::SandboxExecRequest;
|
||||
@@ -20,8 +22,9 @@ pub use manager::SandboxTransformRequest;
|
||||
pub use manager::SandboxType;
|
||||
pub use manager::SandboxablePreference;
|
||||
pub use manager::compatibility_sandbox_policy_for_permission_profile;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use manager::ensure_legacy_landlock_supports_managed_mitm;
|
||||
pub use manager::get_platform_sandbox;
|
||||
pub use manager::with_managed_mitm_ca_readable_root;
|
||||
pub use windows::WindowsSandboxFilesystemOverrides;
|
||||
pub use windows::permission_profile_supports_windows_restricted_token_sandbox;
|
||||
pub use windows::resolve_windows_elevated_filesystem_overrides;
|
||||
@@ -48,6 +51,25 @@ impl From<SandboxTransformError> for CodexErr {
|
||||
SandboxTransformError::MissingLinuxSandboxExecutable => {
|
||||
CodexErr::LandlockSandboxExecutableNotProvided
|
||||
}
|
||||
SandboxTransformError::ManagedMitmCaPathUnderWritableRoot => {
|
||||
CodexErr::UnsupportedOperation(
|
||||
"managed MITM CA isolation requires its proxy directory to be outside sandbox-writable roots"
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
SandboxTransformError::ManagedMitmCustomCaUnsupportedOnWindows => {
|
||||
CodexErr::UnsupportedOperation(
|
||||
"CA directories and command-specific CA overrides with managed MITM are not supported in the Windows sandbox because its read grants persist across commands"
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
SandboxTransformError::LegacyLandlockUnsupportedWithManagedMitm => {
|
||||
CodexErr::UnsupportedOperation(
|
||||
"managed MITM CA isolation requires bubblewrap and is incompatible with legacy Landlock"
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
SandboxTransformError::Wsl1UnsupportedForBubblewrap => {
|
||||
CodexErr::UnsupportedOperation(crate::bwrap::WSL1_BWRAP_WARNING.to_string())
|
||||
|
||||
205
codex-rs/sandboxing/src/managed_network.rs
Normal file
205
codex-rs/sandboxing/src/managed_network.rs
Normal file
@@ -0,0 +1,205 @@
|
||||
use crate::manager::SandboxTransformError;
|
||||
use codex_network_proxy::NetworkProxyChildEnvSnapshot;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxKind;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::ReadDenyMatcher;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
pub(crate) fn with_managed_mitm_ca_proxy_dirs_denied(
|
||||
permission_profile: PermissionProfile,
|
||||
managed_mitm_ca_trust_bundle_paths: &[AbsolutePathBuf],
|
||||
sandbox_policy_cwd: &Path,
|
||||
) -> Result<PermissionProfile, SandboxTransformError> {
|
||||
if managed_mitm_ca_trust_bundle_paths.is_empty() {
|
||||
return Ok(permission_profile);
|
||||
}
|
||||
let (mut file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
|
||||
// Seatbelt and bubblewrap can apply the parent deny plus file carveback per
|
||||
// invocation. Windows read grants persist; other profile kinds stay unchanged.
|
||||
if !cfg!(any(target_os = "linux", target_os = "macos"))
|
||||
|| file_system_sandbox_policy.kind != FileSystemSandboxKind::Restricted
|
||||
{
|
||||
return Ok(permission_profile);
|
||||
}
|
||||
|
||||
// Hide the entire proxy directory before custom CA materialization. This
|
||||
// prevents a later command from asking the unsandboxed host to copy an
|
||||
// earlier command's generated bundle (or the MITM private key) into its
|
||||
// own active bundle.
|
||||
let mut managed_mitm_ca_dirs = managed_mitm_ca_trust_bundle_paths
|
||||
.iter()
|
||||
.filter_map(|path| path.as_path().parent())
|
||||
.filter_map(|path| AbsolutePathBuf::from_absolute_path(path).ok())
|
||||
.collect::<Vec<_>>();
|
||||
managed_mitm_ca_dirs.sort();
|
||||
managed_mitm_ca_dirs.dedup();
|
||||
if managed_mitm_ca_dirs.iter().any(|path| {
|
||||
managed_mitm_ca_dir_overlaps_writable_path(
|
||||
&file_system_sandbox_policy,
|
||||
path.as_path(),
|
||||
sandbox_policy_cwd,
|
||||
)
|
||||
}) {
|
||||
return Err(SandboxTransformError::ManagedMitmCaPathUnderWritableRoot);
|
||||
}
|
||||
for path in managed_mitm_ca_dirs {
|
||||
let entry = FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
};
|
||||
if !file_system_sandbox_policy.entries.contains(&entry) {
|
||||
file_system_sandbox_policy.entries.push(entry);
|
||||
}
|
||||
}
|
||||
Ok(
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
permission_profile.enforcement(),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn managed_mitm_ca_dir_overlaps_writable_path(
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
managed_mitm_ca_dir: &Path,
|
||||
sandbox_policy_cwd: &Path,
|
||||
) -> bool {
|
||||
if file_system_sandbox_policy.has_full_disk_write_access() {
|
||||
return true;
|
||||
}
|
||||
let managed_mitm_ca_dir_canonical = managed_mitm_ca_dir.canonicalize().ok();
|
||||
let has_explicit_writable_overlap = file_system_sandbox_policy
|
||||
.get_writable_roots_with_cwd(sandbox_policy_cwd)
|
||||
.into_iter()
|
||||
.any(|root| {
|
||||
let root = root.root;
|
||||
managed_mitm_ca_dir.starts_with(root.as_path())
|
||||
|| root.as_path().starts_with(managed_mitm_ca_dir)
|
||||
|| managed_mitm_ca_dir_canonical
|
||||
.as_ref()
|
||||
.is_some_and(|managed_dir| {
|
||||
root.as_path().canonicalize().is_ok_and(|root| {
|
||||
managed_dir.starts_with(&root) || root.starts_with(managed_dir)
|
||||
})
|
||||
})
|
||||
});
|
||||
if has_explicit_writable_overlap {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if file_system_sandbox_policy.include_platform_defaults() {
|
||||
return ["/tmp", "/private/tmp", "/var/tmp", "/private/var/tmp"]
|
||||
.into_iter()
|
||||
.map(Path::new)
|
||||
.any(|root| {
|
||||
managed_mitm_ca_dir.starts_with(root)
|
||||
|| managed_mitm_ca_dir_canonical
|
||||
.as_ref()
|
||||
.is_some_and(|managed_dir| {
|
||||
root.canonicalize()
|
||||
.is_ok_and(|root| managed_dir.starts_with(root))
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn with_managed_mitm_ca_readable_roots(
|
||||
permission_profile: PermissionProfile,
|
||||
managed_mitm_ca_trust_bundle_paths: &[AbsolutePathBuf],
|
||||
sandbox_policy_cwd: &Path,
|
||||
) -> PermissionProfile {
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let file_system_sandbox_policy = file_system_sandbox_policy
|
||||
.with_additional_readable_roots(sandbox_policy_cwd, managed_mitm_ca_trust_bundle_paths);
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
permission_profile.enforcement(),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn prepare_managed_network_child(
|
||||
network: Option<&NetworkProxyChildEnvSnapshot>,
|
||||
env: &mut HashMap<String, String>,
|
||||
command_cwd: &Path,
|
||||
permission_profile: PermissionProfile,
|
||||
sandbox_policy_cwd: &Path,
|
||||
persistent_windows_sandbox: bool,
|
||||
) -> Result<PermissionProfile, SandboxTransformError> {
|
||||
let managed_mitm_ca_trust_bundle_paths = network
|
||||
.and_then(NetworkProxyChildEnvSnapshot::managed_mitm_ca_trust_bundle_path)
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
let permission_profile = with_managed_mitm_ca_proxy_dirs_denied(
|
||||
permission_profile,
|
||||
&managed_mitm_ca_trust_bundle_paths,
|
||||
sandbox_policy_cwd,
|
||||
)?;
|
||||
if persistent_windows_sandbox
|
||||
&& network.is_some_and(|network| network.requires_child_specific_mitm_ca_bundle(env))
|
||||
{
|
||||
return Err(SandboxTransformError::ManagedMitmCustomCaUnsupportedOnWindows);
|
||||
}
|
||||
let active_mitm_ca_trust_bundle_paths = network.map_or_else(Vec::new, |network| {
|
||||
if persistent_windows_sandbox {
|
||||
return network.prepare_persistent_sandbox_child_env(env);
|
||||
}
|
||||
let file_system_sandbox_policy = permission_profile.file_system_sandbox_policy();
|
||||
let read_deny_glob_matcher =
|
||||
read_deny_glob_matcher(&file_system_sandbox_policy, sandbox_policy_cwd);
|
||||
network.prepare_child_env(env, command_cwd, |path| {
|
||||
can_read_path_with_policy(
|
||||
&file_system_sandbox_policy,
|
||||
read_deny_glob_matcher.as_ref(),
|
||||
path,
|
||||
sandbox_policy_cwd,
|
||||
)
|
||||
})
|
||||
});
|
||||
Ok(with_managed_mitm_ca_readable_roots(
|
||||
permission_profile,
|
||||
&active_mitm_ca_trust_bundle_paths,
|
||||
sandbox_policy_cwd,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn read_deny_glob_matcher(
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
cwd: &Path,
|
||||
) -> Option<ReadDenyMatcher> {
|
||||
// Exact deny roots participate in normal path-specificity resolution, so
|
||||
// a narrower explicit read entry can validly reopen one CA file below a
|
||||
// denied parent. Globs are enforced separately and must still fail closed.
|
||||
let mut deny_glob_policy = file_system_sandbox_policy.clone();
|
||||
deny_glob_policy.entries.retain(|entry| {
|
||||
entry.access == FileSystemAccessMode::Deny
|
||||
&& matches!(entry.path, FileSystemPath::GlobPattern { .. })
|
||||
});
|
||||
ReadDenyMatcher::new(&deny_glob_policy, cwd)
|
||||
}
|
||||
|
||||
pub(crate) fn can_read_path_with_policy(
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
read_deny_glob_matcher: Option<&ReadDenyMatcher>,
|
||||
path: &Path,
|
||||
cwd: &Path,
|
||||
) -> bool {
|
||||
file_system_sandbox_policy.can_read_path_with_cwd(path, cwd)
|
||||
&& path.canonicalize().is_ok_and(|canonical_path| {
|
||||
file_system_sandbox_policy.can_read_path_with_cwd(&canonical_path, cwd)
|
||||
})
|
||||
&& !read_deny_glob_matcher.is_some_and(|matcher| matcher.is_read_denied(path))
|
||||
}
|
||||
@@ -5,6 +5,15 @@ use crate::bwrap::is_wsl1;
|
||||
use crate::landlock::CODEX_LINUX_SANDBOX_ARG0;
|
||||
use crate::landlock::allow_network_for_proxy;
|
||||
use crate::landlock::create_linux_sandbox_command_args_for_permission_profile;
|
||||
#[cfg(test)]
|
||||
use crate::managed_network::can_read_path_with_policy;
|
||||
use crate::managed_network::prepare_managed_network_child;
|
||||
#[cfg(test)]
|
||||
use crate::managed_network::read_deny_glob_matcher;
|
||||
#[cfg(test)]
|
||||
use crate::managed_network::with_managed_mitm_ca_proxy_dirs_denied;
|
||||
#[cfg(all(test, any(target_os = "linux", target_os = "macos")))]
|
||||
use crate::managed_network::with_managed_mitm_ca_readable_roots;
|
||||
use crate::policy_transforms::effective_permission_profile;
|
||||
use crate::policy_transforms::should_require_platform_sandbox;
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -14,9 +23,12 @@ use crate::resolve_windows_restricted_token_filesystem_overrides;
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::windows_sandbox_uses_elevated_backend;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_network_proxy::NetworkProxyChildEnvSnapshot;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
#[cfg(target_os = "linux")]
|
||||
use codex_protocol::permissions::FileSystemSandboxKind;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
@@ -72,27 +84,6 @@ pub fn get_platform_sandbox(windows_sandbox_enabled: bool) -> Option<SandboxType
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_managed_mitm_ca_readable_root(
|
||||
permission_profile: PermissionProfile,
|
||||
managed_mitm_ca_trust_bundle_path: Option<&AbsolutePathBuf>,
|
||||
sandbox_policy_cwd: &Path,
|
||||
) -> PermissionProfile {
|
||||
let Some(managed_mitm_ca_trust_bundle_path) = managed_mitm_ca_trust_bundle_path else {
|
||||
return permission_profile;
|
||||
};
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let file_system_sandbox_policy = file_system_sandbox_policy.with_additional_readable_roots(
|
||||
sandbox_policy_cwd,
|
||||
std::slice::from_ref(managed_mitm_ca_trust_bundle_path),
|
||||
);
|
||||
PermissionProfile::from_runtime_permissions_with_enforcement(
|
||||
permission_profile.enforcement(),
|
||||
&file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SandboxCommand {
|
||||
pub program: OsString,
|
||||
@@ -165,8 +156,10 @@ impl PendingSandboxedExecRequest {
|
||||
fn new(
|
||||
command_cwd: &PathUri,
|
||||
sandbox_policy_cwd: &PathUri,
|
||||
env: &mut HashMap<String, String>,
|
||||
effective_permission_profile: PermissionProfile,
|
||||
managed_mitm_ca_trust_bundle_path: Option<&AbsolutePathBuf>,
|
||||
network: Option<&NetworkProxyChildEnvSnapshot>,
|
||||
persistent_windows_sandbox: bool,
|
||||
) -> Result<Self, SandboxTransformError> {
|
||||
// TODO(anp): Move PathUri conversion into the platform sandbox implementations.
|
||||
let native_command_cwd = command_cwd.to_abs_path().map_err(|source| {
|
||||
@@ -181,11 +174,14 @@ impl PendingSandboxedExecRequest {
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
let effective_permission_profile = with_managed_mitm_ca_readable_root(
|
||||
let effective_permission_profile = prepare_managed_network_child(
|
||||
network,
|
||||
env,
|
||||
native_command_cwd.as_path(),
|
||||
effective_permission_profile,
|
||||
managed_mitm_ca_trust_bundle_path,
|
||||
native_sandbox_policy_cwd.as_path(),
|
||||
);
|
||||
persistent_windows_sandbox,
|
||||
)?;
|
||||
let (effective_file_system_policy, effective_network_policy) =
|
||||
effective_permission_profile.to_runtime_permissions();
|
||||
Ok(Self {
|
||||
@@ -209,6 +205,10 @@ pub enum SandboxTransformError {
|
||||
source: io::Error,
|
||||
},
|
||||
MissingLinuxSandboxExecutable,
|
||||
ManagedMitmCaPathUnderWritableRoot,
|
||||
ManagedMitmCustomCaUnsupportedOnWindows,
|
||||
#[cfg(target_os = "linux")]
|
||||
LegacyLandlockUnsupportedWithManagedMitm,
|
||||
#[cfg(target_os = "linux")]
|
||||
Wsl1UnsupportedForBubblewrap,
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
@@ -233,6 +233,19 @@ impl std::fmt::Display for SandboxTransformError {
|
||||
Self::MissingLinuxSandboxExecutable => {
|
||||
write!(f, "missing codex-linux-sandbox executable path")
|
||||
}
|
||||
Self::ManagedMitmCaPathUnderWritableRoot => write!(
|
||||
f,
|
||||
"managed MITM CA isolation requires its proxy directory to be outside sandbox-writable roots"
|
||||
),
|
||||
Self::ManagedMitmCustomCaUnsupportedOnWindows => write!(
|
||||
f,
|
||||
"CA directories and command-specific CA overrides with managed MITM are not supported in the Windows sandbox because its read grants persist across commands"
|
||||
),
|
||||
#[cfg(target_os = "linux")]
|
||||
Self::LegacyLandlockUnsupportedWithManagedMitm => write!(
|
||||
f,
|
||||
"managed MITM CA isolation requires bubblewrap and is incompatible with legacy Landlock"
|
||||
),
|
||||
#[cfg(target_os = "linux")]
|
||||
Self::Wsl1UnsupportedForBubblewrap => write!(f, "{WSL1_BWRAP_WARNING}"),
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
@@ -250,9 +263,13 @@ impl std::error::Error for SandboxTransformError {
|
||||
match self {
|
||||
Self::InvalidCommandCwd { source, .. }
|
||||
| Self::InvalidSandboxPolicyCwd { source, .. } => Some(source),
|
||||
Self::MissingLinuxSandboxExecutable => None,
|
||||
Self::MissingLinuxSandboxExecutable
|
||||
| Self::ManagedMitmCaPathUnderWritableRoot
|
||||
| Self::ManagedMitmCustomCaUnsupportedOnWindows => None,
|
||||
#[cfg(target_os = "linux")]
|
||||
Self::Wsl1UnsupportedForBubblewrap => None,
|
||||
Self::LegacyLandlockUnsupportedWithManagedMitm | Self::Wsl1UnsupportedForBubblewrap => {
|
||||
None
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
Self::SeatbeltUnavailable => None,
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -316,15 +333,16 @@ impl SandboxManager {
|
||||
windows_sandbox_private_desktop,
|
||||
} = request;
|
||||
let additional_permissions = command.additional_permissions.take();
|
||||
let managed_mitm_ca_trust_bundle_path =
|
||||
network.and_then(NetworkProxy::managed_mitm_ca_trust_bundle_path);
|
||||
let network_child_env = network.map(NetworkProxy::child_env_snapshot);
|
||||
let base_effective_permission_profile =
|
||||
effective_permission_profile(permissions, additional_permissions.as_ref());
|
||||
let pending_sandboxed_request = PendingSandboxedExecRequest::new(
|
||||
&command.cwd,
|
||||
sandbox_policy_cwd,
|
||||
&mut command.env,
|
||||
base_effective_permission_profile.clone(),
|
||||
managed_mitm_ca_trust_bundle_path.as_ref(),
|
||||
network_child_env.as_ref(),
|
||||
cfg!(target_os = "windows") && sandbox == SandboxType::WindowsRestrictedToken,
|
||||
);
|
||||
let (base_file_system_policy, base_network_policy) =
|
||||
base_effective_permission_profile.to_runtime_permissions();
|
||||
@@ -367,6 +385,9 @@ impl SandboxManager {
|
||||
&pending.effective_file_system_policy,
|
||||
use_legacy_landlock,
|
||||
allow_proxy_network,
|
||||
network_child_env
|
||||
.as_ref()
|
||||
.is_some_and(NetworkProxyChildEnvSnapshot::has_managed_mitm_ca),
|
||||
is_wsl1(),
|
||||
)?;
|
||||
let mut args = create_linux_sandbox_command_args_for_permission_profile(
|
||||
@@ -636,10 +657,16 @@ fn ensure_linux_bubblewrap_is_supported(
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
use_legacy_landlock: bool,
|
||||
allow_network_for_proxy: bool,
|
||||
managed_mitm_ca_active: bool,
|
||||
is_wsl1: bool,
|
||||
) -> Result<(), SandboxTransformError> {
|
||||
let requires_bubblewrap = allow_network_for_proxy
|
||||
|| (!use_legacy_landlock && !file_system_sandbox_policy.has_full_disk_write_access());
|
||||
ensure_legacy_landlock_supports_managed_mitm(
|
||||
file_system_sandbox_policy,
|
||||
use_legacy_landlock,
|
||||
managed_mitm_ca_active,
|
||||
)?;
|
||||
let requires_bubblewrap = !use_legacy_landlock
|
||||
&& (!file_system_sandbox_policy.has_full_disk_write_access() || allow_network_for_proxy);
|
||||
if is_wsl1 && requires_bubblewrap {
|
||||
return Err(SandboxTransformError::Wsl1UnsupportedForBubblewrap);
|
||||
}
|
||||
@@ -647,6 +674,22 @@ fn ensure_linux_bubblewrap_is_supported(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn ensure_legacy_landlock_supports_managed_mitm(
|
||||
file_system_sandbox_policy: &FileSystemSandboxPolicy,
|
||||
use_legacy_landlock: bool,
|
||||
managed_mitm_ca_active: bool,
|
||||
) -> Result<(), SandboxTransformError> {
|
||||
if use_legacy_landlock
|
||||
&& managed_mitm_ca_active
|
||||
&& file_system_sandbox_policy.kind == FileSystemSandboxKind::Restricted
|
||||
{
|
||||
return Err(SandboxTransformError::LegacyLandlockUnsupportedWithManagedMitm);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn os_argv_to_strings(argv: Vec<OsString>) -> Vec<String> {
|
||||
argv.into_iter()
|
||||
.map(os_string_to_command_component)
|
||||
|
||||
@@ -5,8 +5,20 @@ use super::SandboxManager;
|
||||
use super::SandboxTransformRequest;
|
||||
use super::SandboxType;
|
||||
use super::SandboxablePreference;
|
||||
use super::can_read_path_with_policy;
|
||||
use super::get_platform_sandbox;
|
||||
use super::with_managed_mitm_ca_readable_root;
|
||||
use super::read_deny_glob_matcher;
|
||||
use super::with_managed_mitm_ca_proxy_dirs_denied;
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use super::with_managed_mitm_ca_readable_roots;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_network_proxy::NetworkProxyConstraints;
|
||||
#[cfg(target_os = "windows")]
|
||||
use codex_network_proxy::build_config_state;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::AdditionalPermissionProfile;
|
||||
use codex_protocol::models::FileSystemPermissions;
|
||||
@@ -18,11 +30,15 @@ use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
use codex_protocol::permissions::FileSystemSpecialPath;
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use codex_protocol::permissions::ReadDenyMatcher;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use dunce::canonicalize;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::sync::Arc;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
@@ -258,45 +274,300 @@ fn transform_additional_permissions_preserves_denied_entries() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_mitm_ca_bundle_becomes_readable_for_restricted_sandbox() {
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn managed_mitm_ca_bundle_is_only_readable_carveback_in_proxy_dir() {
|
||||
let cwd = TempDir::new().expect("create cwd");
|
||||
let cwd =
|
||||
AbsolutePathBuf::from_absolute_path(canonicalize(cwd.path()).expect("canonicalize cwd"))
|
||||
.expect("absolute cwd");
|
||||
let managed_bundle_dir = TempDir::new().expect("create managed bundle dir");
|
||||
let managed_bundle_path =
|
||||
AbsolutePathBuf::from_absolute_path(managed_bundle_dir.path().join("ca-bundle.pem"))
|
||||
.expect("absolute managed bundle path");
|
||||
let managed_bundle_dir = AbsolutePathBuf::from_absolute_path(
|
||||
canonicalize(managed_bundle_dir.path()).expect("canonicalize managed bundle dir"),
|
||||
)
|
||||
.expect("absolute managed bundle dir");
|
||||
let managed_bundle_path = managed_bundle_dir.join("ca-bundle-active.pem");
|
||||
let previous_bundle_path = managed_bundle_dir.join("ca-bundle-previous.pem");
|
||||
let managed_ca_key_path = managed_bundle_dir.join("ca.key");
|
||||
for path in [
|
||||
&managed_bundle_path,
|
||||
&previous_bundle_path,
|
||||
&managed_ca_key_path,
|
||||
] {
|
||||
std::fs::write(path.as_path(), "fixture").expect("write managed CA fixture");
|
||||
}
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: cwd.clone() },
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
|
||||
let permission_profile = with_managed_mitm_ca_readable_root(
|
||||
let permission_profile = with_managed_mitm_ca_proxy_dirs_denied(
|
||||
permission_profile,
|
||||
Some(&managed_bundle_path),
|
||||
std::slice::from_ref(&managed_bundle_path),
|
||||
cwd.as_path(),
|
||||
)
|
||||
.expect("managed bundle directory should be outside writable roots");
|
||||
let (file_system_sandbox_policy, _) = permission_profile.to_runtime_permissions();
|
||||
let read_deny_matcher =
|
||||
ReadDenyMatcher::new(&file_system_sandbox_policy, cwd.as_path()).expect("deny matcher");
|
||||
for path in [
|
||||
&managed_bundle_path,
|
||||
&previous_bundle_path,
|
||||
&managed_ca_key_path,
|
||||
] {
|
||||
assert!(!can_read_path_with_policy(
|
||||
&file_system_sandbox_policy,
|
||||
Some(&read_deny_matcher),
|
||||
path.as_path(),
|
||||
cwd.as_path(),
|
||||
));
|
||||
}
|
||||
|
||||
let permission_profile = with_managed_mitm_ca_readable_roots(
|
||||
permission_profile,
|
||||
std::slice::from_ref(&managed_bundle_path),
|
||||
cwd.as_path(),
|
||||
);
|
||||
let (file_system_sandbox_policy, _) = permission_profile.to_runtime_permissions();
|
||||
let read_deny_glob_matcher = read_deny_glob_matcher(&file_system_sandbox_policy, cwd.as_path());
|
||||
|
||||
assert_eq!(
|
||||
file_system_sandbox_policy,
|
||||
FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: cwd },
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: managed_bundle_path,
|
||||
path: managed_bundle_dir,
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: managed_bundle_path.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
])
|
||||
);
|
||||
assert!(can_read_path_with_policy(
|
||||
&file_system_sandbox_policy,
|
||||
read_deny_glob_matcher.as_ref(),
|
||||
managed_bundle_path.as_path(),
|
||||
cwd.as_path(),
|
||||
));
|
||||
assert!(
|
||||
!file_system_sandbox_policy
|
||||
.can_read_path_with_cwd(previous_bundle_path.as_path(), cwd.as_path(),)
|
||||
);
|
||||
assert!(
|
||||
!file_system_sandbox_policy
|
||||
.can_read_path_with_cwd(managed_ca_key_path.as_path(), cwd.as_path(),)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn managed_mitm_ca_materialization_checks_canonical_target_policy() {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
let root = TempDir::new().expect("create policy root");
|
||||
let root = AbsolutePathBuf::from_absolute_path(
|
||||
canonicalize(root.path()).expect("canonicalize policy root"),
|
||||
)
|
||||
.expect("absolute policy root");
|
||||
let denied_dir = root.join("secrets");
|
||||
std::fs::create_dir(denied_dir.as_path()).expect("create denied dir");
|
||||
let denied_ca = denied_dir.join("ca.pem");
|
||||
std::fs::write(denied_ca.as_path(), "secret CA").expect("write denied CA");
|
||||
let readable_alias = root.join("readable-ca.pem");
|
||||
symlink(denied_ca.as_path(), readable_alias.as_path()).expect("create readable alias");
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: root.clone() },
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: denied_dir },
|
||||
access: FileSystemAccessMode::Deny,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: readable_alias.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
]);
|
||||
|
||||
assert!(
|
||||
file_system_sandbox_policy.can_read_path_with_cwd(readable_alias.as_path(), root.as_path()),
|
||||
"the lexical alias is explicitly readable"
|
||||
);
|
||||
assert!(!can_read_path_with_policy(
|
||||
&file_system_sandbox_policy,
|
||||
/*read_deny_glob_matcher*/ None,
|
||||
readable_alias.as_path(),
|
||||
root.as_path(),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_mitm_ca_proxy_dir_deny_preserves_profiles_without_restricted_filesystem() {
|
||||
let managed_bundle_dir = TempDir::new().expect("create managed bundle dir");
|
||||
let managed_bundle_path =
|
||||
AbsolutePathBuf::from_absolute_path(managed_bundle_dir.path().join("ca-bundle.pem"))
|
||||
.expect("absolute managed bundle path");
|
||||
|
||||
for permission_profile in [
|
||||
PermissionProfile::Disabled,
|
||||
PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
),
|
||||
PermissionProfile::External {
|
||||
network: NetworkSandboxPolicy::Restricted,
|
||||
},
|
||||
] {
|
||||
assert_eq!(
|
||||
with_managed_mitm_ca_proxy_dirs_denied(
|
||||
permission_profile.clone(),
|
||||
std::slice::from_ref(&managed_bundle_path),
|
||||
managed_bundle_dir.path(),
|
||||
)
|
||||
.expect("profile should remain supported"),
|
||||
permission_profile,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn managed_mitm_ca_proxy_dir_rejects_writable_overlap() {
|
||||
let writable_root = TempDir::new().expect("create writable root");
|
||||
let proxy_dir = writable_root.path().join("proxy");
|
||||
std::fs::create_dir(&proxy_dir).expect("create proxy dir");
|
||||
let managed_bundle_path =
|
||||
AbsolutePathBuf::from_absolute_path(proxy_dir.join("ca-bundle-active.pem"))
|
||||
.expect("absolute managed bundle path");
|
||||
let writable_key = AbsolutePathBuf::from_absolute_path(proxy_dir.join("ca.key"))
|
||||
.expect("absolute writable key path");
|
||||
std::fs::write(writable_key.as_path(), "secret key").expect("write managed key fixture");
|
||||
let writable_root = AbsolutePathBuf::from_absolute_path(
|
||||
canonicalize(writable_root.path()).expect("canonicalize writable root"),
|
||||
)
|
||||
.expect("absolute writable root");
|
||||
let policies = [
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: writable_root.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]),
|
||||
FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path { path: writable_key },
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
]),
|
||||
FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
}]),
|
||||
];
|
||||
|
||||
for policy in policies {
|
||||
let permission_profile =
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted);
|
||||
assert!(matches!(
|
||||
with_managed_mitm_ca_proxy_dirs_denied(
|
||||
permission_profile,
|
||||
std::slice::from_ref(&managed_bundle_path),
|
||||
writable_root.as_path(),
|
||||
),
|
||||
Err(super::SandboxTransformError::ManagedMitmCaPathUnderWritableRoot)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn managed_mitm_ca_proxy_dir_rejects_platform_default_writable_ancestor() {
|
||||
let proxy_dir = tempfile::tempdir_in("/private/tmp").expect("create proxy dir");
|
||||
let managed_bundle_path =
|
||||
AbsolutePathBuf::from_absolute_path(proxy_dir.path().join("ca-bundle-active.pem"))
|
||||
.expect("absolute managed bundle path");
|
||||
let permission_profile = PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}]),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
with_managed_mitm_ca_proxy_dirs_denied(
|
||||
permission_profile,
|
||||
std::slice::from_ref(&managed_bundle_path),
|
||||
proxy_dir.path(),
|
||||
),
|
||||
Err(super::SandboxTransformError::ManagedMitmCaPathUnderWritableRoot)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn managed_mitm_ca_materialization_rejects_glob_denied_paths_from_command_subdir() {
|
||||
let sandbox_policy_cwd = TempDir::new().expect("create cwd");
|
||||
let sandbox_policy_cwd = AbsolutePathBuf::from_absolute_path(
|
||||
canonicalize(sandbox_policy_cwd.path()).expect("canonicalize cwd"),
|
||||
)
|
||||
.expect("absolute cwd");
|
||||
let command_cwd = sandbox_policy_cwd.join("subdir");
|
||||
std::fs::create_dir(command_cwd.as_path()).expect("create command cwd");
|
||||
let ca_bundle_path = command_cwd.join("../secrets/blocked.pem");
|
||||
std::fs::create_dir(sandbox_policy_cwd.join("secrets").as_path()).expect("create secrets");
|
||||
std::fs::write(ca_bundle_path.as_path(), "secret").expect("write blocked CA bundle");
|
||||
let file_system_sandbox_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: sandbox_policy_cwd.clone(),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::GlobPattern {
|
||||
pattern: "secrets/**".to_string(),
|
||||
},
|
||||
access: FileSystemAccessMode::Deny,
|
||||
},
|
||||
]);
|
||||
let read_deny_glob_matcher =
|
||||
read_deny_glob_matcher(&file_system_sandbox_policy, sandbox_policy_cwd.as_path())
|
||||
.expect("deny glob matcher");
|
||||
|
||||
assert!(!can_read_path_with_policy(
|
||||
&file_system_sandbox_policy,
|
||||
Some(&read_deny_glob_matcher),
|
||||
ca_bundle_path.as_path(),
|
||||
sandbox_policy_cwd.as_path(),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -345,6 +616,7 @@ fn wsl1_rejects_linux_bubblewrap_path() {
|
||||
&restricted_policy,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*allow_network_for_proxy*/ false,
|
||||
/*managed_mitm_ca_active*/ false,
|
||||
/*is_wsl1*/ true,
|
||||
),
|
||||
Err(super::SandboxTransformError::Wsl1UnsupportedForBubblewrap)
|
||||
@@ -354,15 +626,7 @@ fn wsl1_rejects_linux_bubblewrap_path() {
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*allow_network_for_proxy*/ true,
|
||||
/*is_wsl1*/ true,
|
||||
),
|
||||
Err(super::SandboxTransformError::Wsl1UnsupportedForBubblewrap)
|
||||
));
|
||||
assert!(matches!(
|
||||
super::ensure_linux_bubblewrap_is_supported(
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
/*use_legacy_landlock*/ true,
|
||||
/*allow_network_for_proxy*/ true,
|
||||
/*managed_mitm_ca_active*/ false,
|
||||
/*is_wsl1*/ true,
|
||||
),
|
||||
Err(super::SandboxTransformError::Wsl1UnsupportedForBubblewrap)
|
||||
@@ -377,6 +641,7 @@ fn wsl1_allows_non_bubblewrap_linux_paths() {
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*allow_network_for_proxy*/ false,
|
||||
/*managed_mitm_ca_active*/ false,
|
||||
/*is_wsl1*/ true,
|
||||
)
|
||||
.is_ok()
|
||||
@@ -393,12 +658,65 @@ fn wsl1_allows_non_bubblewrap_linux_paths() {
|
||||
&restricted_policy,
|
||||
/*use_legacy_landlock*/ true,
|
||||
/*allow_network_for_proxy*/ false,
|
||||
/*managed_mitm_ca_active*/ false,
|
||||
/*is_wsl1*/ true,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn legacy_landlock_rejects_managed_mitm_ca_isolation_for_restricted_profiles() {
|
||||
let restricted_policy = FileSystemSandboxPolicy::restricted(vec![FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
}]);
|
||||
|
||||
assert!(matches!(
|
||||
super::ensure_linux_bubblewrap_is_supported(
|
||||
&restricted_policy,
|
||||
/*use_legacy_landlock*/ true,
|
||||
/*allow_network_for_proxy*/ false,
|
||||
/*managed_mitm_ca_active*/ true,
|
||||
/*is_wsl1*/ false,
|
||||
),
|
||||
Err(super::SandboxTransformError::LegacyLandlockUnsupportedWithManagedMitm)
|
||||
));
|
||||
assert!(
|
||||
super::ensure_linux_bubblewrap_is_supported(
|
||||
&restricted_policy,
|
||||
/*use_legacy_landlock*/ false,
|
||||
/*allow_network_for_proxy*/ false,
|
||||
/*managed_mitm_ca_active*/ true,
|
||||
/*is_wsl1*/ false,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
super::ensure_linux_bubblewrap_is_supported(
|
||||
&restricted_policy,
|
||||
/*use_legacy_landlock*/ true,
|
||||
/*allow_network_for_proxy*/ false,
|
||||
/*managed_mitm_ca_active*/ false,
|
||||
/*is_wsl1*/ false,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(
|
||||
super::ensure_linux_bubblewrap_is_supported(
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
/*use_legacy_landlock*/ true,
|
||||
/*allow_network_for_proxy*/ false,
|
||||
/*managed_mitm_ca_active*/ true,
|
||||
/*is_wsl1*/ false,
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn transform_linux_seccomp_preserves_helper_path_in_arg0_when_available() {
|
||||
@@ -454,6 +772,56 @@ fn transform_for_direct_spawn_windows_preserves_only_wrapper_setup_identity() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[tokio::test]
|
||||
async fn windows_restricted_transform_rejects_command_specific_ca() {
|
||||
let cwd = TempDir::new().expect("create cwd");
|
||||
let cwd = AbsolutePathBuf::from_absolute_path(cwd.path()).expect("absolute cwd");
|
||||
let cwd_uri = PathUri::from_abs_path(&cwd);
|
||||
let mut config = NetworkProxyConfig::default();
|
||||
config.network.mitm = true;
|
||||
let state = Arc::new(
|
||||
build_config_state(config, NetworkProxyConstraints::default()).expect("build proxy state"),
|
||||
);
|
||||
let network = NetworkProxy::builder()
|
||||
.state(state)
|
||||
.managed_by_codex(/*managed_by_codex*/ false)
|
||||
.build()
|
||||
.await
|
||||
.expect("build proxy");
|
||||
let permissions = PermissionProfile::read_only();
|
||||
|
||||
let err = SandboxManager::new()
|
||||
.transform(SandboxTransformRequest {
|
||||
command: SandboxCommand {
|
||||
program: "cmd.exe".into(),
|
||||
args: vec!["/c".to_string(), "exit 0".to_string()],
|
||||
cwd: cwd_uri.clone(),
|
||||
env: HashMap::from([(
|
||||
"REQUESTS_CA_BUNDLE".to_string(),
|
||||
r"C:\command-ca.pem".to_string(),
|
||||
)]),
|
||||
additional_permissions: None,
|
||||
},
|
||||
permissions: &permissions,
|
||||
sandbox: SandboxType::WindowsRestrictedToken,
|
||||
enforce_managed_network: true,
|
||||
environment_id: None,
|
||||
network: Some(&network),
|
||||
sandbox_policy_cwd: &cwd_uri,
|
||||
codex_linux_sandbox_exe: None,
|
||||
use_legacy_landlock: false,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Elevated,
|
||||
windows_sandbox_private_desktop: false,
|
||||
})
|
||||
.expect_err("command-specific CA should be rejected");
|
||||
|
||||
assert!(matches!(
|
||||
err,
|
||||
super::SandboxTransformError::ManagedMitmCustomCaUnsupportedOnWindows
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn transform_for_direct_spawn_windows_materializes_inner_helper() {
|
||||
|
||||
Reference in New Issue
Block a user