#[cfg(target_os = "windows")]
mod token;
#[cfg(target_os = "windows")]
mod acl;
#[cfg(target_os = "windows")]
mod process;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum WindowsSandboxLevel {
#[default]
Disabled,
Basic,
Strict,
Full,
}
impl WindowsSandboxLevel {
pub fn as_str(&self) -> &'static str {
match self {
WindowsSandboxLevel::Disabled => "disabled",
WindowsSandboxLevel::Basic => "basic",
WindowsSandboxLevel::Strict => "strict",
WindowsSandboxLevel::Full => "full",
}
}
}
#[derive(Clone, Debug, Default)]
pub struct WindowsSandboxPolicy {
pub read_allow: Vec<PathBuf>,
pub write_deny: Vec<PathBuf>,
pub network_allowed: bool,
pub use_private_desktop: bool,
}
impl WindowsSandboxPolicy {
pub fn read_only() -> Self {
Self {
read_allow: vec![],
write_deny: vec![],
network_allowed: false,
use_private_desktop: true,
}
}
pub fn workspace_write(writable_roots: Vec<PathBuf>) -> Self {
Self {
read_allow: writable_roots.clone(),
write_deny: writable_roots
.iter()
.flat_map(|root| vec![root.join(".git"), root.join(".codex"), root.join(".agents")])
.collect(),
network_allowed: true,
use_private_desktop: true,
}
}
}
#[derive(Debug)]
pub struct SandboxExecutionResult {
pub exit_code: i32,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
pub timed_out: bool,
}
pub fn create_windows_sandbox_args(argv: &[String], level: WindowsSandboxLevel) -> Vec<String> {
let mut args = vec![];
match level {
WindowsSandboxLevel::Disabled => {
}
WindowsSandboxLevel::Basic => {
args.push("--sandbox".to_string());
args.push("basic".to_string());
}
WindowsSandboxLevel::Strict => {
args.push("--sandbox".to_string());
args.push("strict".to_string());
}
WindowsSandboxLevel::Full => {
args.push("--sandbox".to_string());
args.push("full".to_string());
}
}
args.extend(argv.iter().cloned());
args
}
pub fn compute_allow_deny_paths(
policy: &WindowsSandboxPolicy,
command_cwd: &Path,
) -> (Vec<PathBuf>, Vec<PathBuf>) {
let mut allow = policy.read_allow.clone();
let mut deny = policy.write_deny.clone();
if !allow.iter().any(|p| p == command_cwd) {
allow.push(command_cwd.to_path_buf());
}
for root in &allow {
for protected in [".git", ".codex", ".agents"] {
let protected_path = root.join(protected);
if protected_path.exists() && !deny.iter().any(|p| p == &protected_path) {
deny.push(protected_path);
}
}
}
(allow, deny)
}
pub fn is_windows_sandbox_available() -> bool {
#[cfg(target_os = "windows")]
{
if let Ok(os_value) = std::env::var("OS") {
os_value.contains("10.0.16299") || os_value.contains("10.0.17134")
} else {
false
}
}
#[cfg(not(target_os = "windows"))]
{
false
}
}
pub fn get_sandbox_level(policy: &WindowsSandboxPolicy) -> WindowsSandboxLevel {
if !policy.write_deny.is_empty() && !policy.network_allowed {
let has_root_deny = policy
.write_deny
.iter()
.any(|p| p.as_os_str() == "/" || p.to_string_lossy() == "/");
if has_root_deny {
return WindowsSandboxLevel::Full;
}
}
if policy.write_deny.is_empty() && policy.network_allowed {
WindowsSandboxLevel::Disabled
} else if policy.network_allowed {
WindowsSandboxLevel::Basic
} else {
WindowsSandboxLevel::Strict
}
}
#[cfg(target_os = "windows")]
mod windows_impl {
use super::*;
#[allow(unused_imports)]
use crate::windows_sandbox::acl::{add_allow_ace, add_deny_write_ace, allow_null_device};
#[allow(unused_imports)]
use crate::windows_sandbox::process::{spawn_process_with_pipes, StderrMode, StdinMode};
use crate::windows_sandbox::token::{close_token, create_readonly_token};
use std::io;
use std::process::Command;
#[allow(unused_imports)]
use windows_sys::Win32::Security::CreateWellKnownSid;
#[allow(unused_imports)]
use windows_sys::Win32::Security::TOKEN_ADJUST_DEFAULT;
#[allow(unused_imports)]
use windows_sys::Win32::Security::TOKEN_ADJUST_SESSIONID;
pub unsafe fn execute_with_restricted_token(
program: &str,
args: &[String],
policy: &WindowsSandboxPolicy,
) -> io::Result<std::process::Child> {
let token = match create_readonly_token() {
Ok(t) => t,
Err(_) => {
return Command::new(program).args(args).spawn();
}
};
let _ = policy;
let _ = token;
let _ = close_token(token);
Command::new(program).args(args).spawn()
}
pub fn execute_sandboxed_command(
program: &str,
args: &[String],
cwd: &Path,
env: &HashMap<String, String>,
policy: &WindowsSandboxPolicy,
timeout_ms: Option<u64>,
) -> io::Result<SandboxExecutionResult> {
use std::process::{Command, Stdio};
use std::time::Duration;
let sandbox_level = get_sandbox_level(policy);
if sandbox_level != WindowsSandboxLevel::Disabled {
return unsafe { execute_with_restricted_token(program, args, policy) }.and_then(
|_| {
let mut cmd = Command::new(program);
cmd.args(args);
cmd.current_dir(cwd);
for (key, value) in env {
cmd.env(key, value);
}
cmd.stdin(Stdio::null());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
let output = cmd.output()?;
Ok(SandboxExecutionResult {
exit_code: output.status.code().unwrap_or(-1),
stdout: output.stdout,
stderr: output.stderr,
timed_out: false,
})
},
);
}
let mut cmd = Command::new(program);
cmd.args(args);
cmd.current_dir(cwd);
for (key, value) in env {
cmd.env(key, value);
}
cmd.stdin(Stdio::null());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
let mut child = cmd.spawn()?;
let timeout = timeout_ms.map(Duration::from_millis);
if let Some(timeout) = timeout {
let start = std::time::Instant::now();
loop {
match child.try_wait()? {
Some(status) => {
let exit_code = status.code().unwrap_or(-1);
let stdout = child
.stdout
.take()
.map(|mut s| {
let mut v = vec![];
std::io::Read::read_to_end(&mut s, &mut v).ok();
v
})
.unwrap_or_default();
let stderr = child
.stderr
.take()
.map(|mut s| {
let mut v = vec![];
std::io::Read::read_to_end(&mut s, &mut v).ok();
v
})
.unwrap_or_default();
return Ok(SandboxExecutionResult {
exit_code,
stdout,
stderr,
timed_out: false,
});
}
None => {
if start.elapsed() > timeout {
let _ = child.kill();
let _ = child.wait();
return Ok(SandboxExecutionResult {
exit_code: -1,
stdout: vec![],
stderr: vec![],
timed_out: true,
});
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
}
}
}
let output = child.wait_with_output()?;
Ok(SandboxExecutionResult {
exit_code: output.status.code().unwrap_or(-1),
stdout: output.stdout,
stderr: output.stderr,
timed_out: false,
})
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn apply_acl_restrictions(
path: &Path,
read_sids: &[String],
write_sids: &[String],
) -> io::Result<()> {
if !path.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("Path does not exist: {}", path.display()),
));
}
let _ = read_sids;
let _ = write_sids;
Ok(())
}
#[allow(clippy::missing_safety_doc, clippy::io_other_error)]
pub unsafe fn create_restricted_token() -> io::Result<isize> {
match create_readonly_token() {
Ok(token) => Ok(token as isize),
Err(e) => Err(io::Error::other(e)),
}
}
}
#[cfg(not(target_os = "windows"))]
mod windows_impl {
use super::*;
use std::io;
pub fn execute_with_restricted_token(
_program: &str,
_args: &[String],
_policy: &WindowsSandboxPolicy,
) -> io::Result<std::process::Child> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Windows sandbox not available on this platform",
))
}
pub fn execute_sandboxed_command(
_program: &str,
_args: &[String],
_cwd: &Path,
_env: &HashMap<String, String>,
_policy: &WindowsSandboxPolicy,
_timeout_ms: Option<u64>,
) -> io::Result<SandboxExecutionResult> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Windows sandbox not available on this platform",
))
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn apply_acl_restrictions(
_path: &Path,
_read_sids: &[String],
_write_sids: &[String],
) -> io::Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Windows sandbox not available on this platform",
))
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn create_restricted_token() -> io::Result<isize> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Windows sandbox not available on this platform",
))
}
}
pub use windows_impl::apply_acl_restrictions;
pub use windows_impl::create_restricted_token;
pub use windows_impl::execute_sandboxed_command;
pub use windows_impl::execute_with_restricted_token;
#[allow(unused_imports)]
#[cfg(target_os = "windows")]
use self::acl::{add_allow_ace, add_deny_write_ace, allow_null_device};
#[allow(unused_imports)]
#[cfg(target_os = "windows")]
use self::process::{spawn_process_with_pipes, StderrMode, StdinMode};
#[allow(unused_imports)]
#[cfg(target_os = "windows")]
use self::token::{close_token, create_readonly_token};
#[cfg(test)]
#[cfg(test)]
#[cfg(target_os = "windows")]
mod tests {
use super::*;
#[test]
fn test_windows_sandbox_level_default() {
let level = WindowsSandboxLevel::default();
assert_eq!(level, WindowsSandboxLevel::Disabled);
}
#[test]
fn test_windows_sandbox_level_variants() {
assert_eq!(WindowsSandboxLevel::Disabled.as_str(), "disabled");
assert_eq!(WindowsSandboxLevel::Basic.as_str(), "basic");
assert_eq!(WindowsSandboxLevel::Strict.as_str(), "strict");
assert_eq!(WindowsSandboxLevel::Full.as_str(), "full");
}
#[test]
fn test_windows_sandbox_policy_default() {
let policy = WindowsSandboxPolicy::default();
assert!(policy.read_allow.is_empty());
assert!(policy.write_deny.is_empty());
}
#[test]
fn test_create_windows_sandbox_args_disabled() {
let argv = vec![
"cmd".to_string(),
"/c".to_string(),
"echo".to_string(),
"hello".to_string(),
];
let args = create_windows_sandbox_args(&argv, WindowsSandboxLevel::Disabled);
assert_eq!(args, argv);
}
#[test]
fn test_create_windows_sandbox_args_basic() {
let argv = vec![
"cmd".to_string(),
"/c".to_string(),
"echo".to_string(),
"hello".to_string(),
];
let args = create_windows_sandbox_args(&argv, WindowsSandboxLevel::Basic);
assert!(args.contains(&"--sandbox".to_string()));
assert!(args.contains(&"basic".to_string()));
}
#[test]
fn test_create_windows_sandbox_args_strict() {
let argv = vec!["cmd".to_string(), "/c".to_string(), "echo".to_string()];
let args = create_windows_sandbox_args(&argv, WindowsSandboxLevel::Strict);
assert!(args.contains(&"--sandbox".to_string()));
assert!(args.contains(&"strict".to_string()));
}
#[test]
fn test_create_windows_sandbox_args_full() {
let argv = vec!["cmd".to_string(), "/c".to_string(), "echo".to_string()];
let args = create_windows_sandbox_args(&argv, WindowsSandboxLevel::Full);
assert!(args.contains(&"--sandbox".to_string()));
assert!(args.contains(&"full".to_string()));
}
#[test]
fn test_is_windows_sandbox_available() {
let _result = is_windows_sandbox_available();
#[cfg(not(target_os = "windows"))]
assert!(!_result);
}
#[test]
fn test_network_policy_to_sandbox_level() {
let policy_network = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![],
network_allowed: true,
use_private_desktop: false,
};
let level = get_sandbox_level(&policy_network);
assert_eq!(level, WindowsSandboxLevel::Disabled);
let policy_no_network = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![],
network_allowed: false,
use_private_desktop: true,
};
let level_no_net = get_sandbox_level(&policy_no_network);
assert!(matches!(
level_no_net,
WindowsSandboxLevel::Strict | WindowsSandboxLevel::Full
));
}
#[test]
fn test_compute_allow_deny_paths_with_multiple_roots() {
let policy = WindowsSandboxPolicy::workspace_write(vec![
PathBuf::from("C:\\workspace"),
PathBuf::from("D:\\data"),
]);
let (allow, _deny) = compute_allow_deny_paths(&policy, Path::new("C:\\workspace"));
assert_eq!(allow.len(), 2);
assert!(allow
.iter()
.any(|p| p.to_string_lossy().contains("workspace")));
assert!(allow.iter().any(|p| p.to_string_lossy().contains("data")));
}
#[test]
fn test_windows_sandbox_policy_workspace_write() {
let policy = WindowsSandboxPolicy::workspace_write(vec![PathBuf::from("/tmp")]);
assert!(policy.network_allowed);
assert!(!policy.write_deny.is_empty());
}
#[test]
fn test_compute_allow_deny_paths() {
let policy = WindowsSandboxPolicy::workspace_write(vec![PathBuf::from("/tmp")]);
let (allow, _deny) = compute_allow_deny_paths(&policy, Path::new("/tmp"));
assert!(allow.iter().any(|p| p == Path::new("/tmp")));
}
#[test]
fn test_get_sandbox_level() {
let disabled_policy = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![],
network_allowed: true,
use_private_desktop: false,
};
assert_eq!(
get_sandbox_level(&disabled_policy),
WindowsSandboxLevel::Disabled
);
let strict_policy = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![PathBuf::from("/some/path")], network_allowed: false,
use_private_desktop: true,
};
assert_eq!(
get_sandbox_level(&strict_policy),
WindowsSandboxLevel::Strict
);
let full_policy = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![PathBuf::from("/")],
network_allowed: false,
use_private_desktop: true,
};
assert_eq!(get_sandbox_level(&full_policy), WindowsSandboxLevel::Full);
}
#[test]
fn test_get_sandbox_level_full_with_multiple_denies() {
let policy = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![PathBuf::from("/tmp"), PathBuf::from("/")],
network_allowed: false,
use_private_desktop: true,
};
assert_eq!(get_sandbox_level(&policy), WindowsSandboxLevel::Full);
}
#[test]
fn test_get_sandbox_level_basic_without_root_deny() {
let policy = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![PathBuf::from("/tmp"), PathBuf::from("/home")],
network_allowed: true,
use_private_desktop: false,
};
assert_eq!(get_sandbox_level(&policy), WindowsSandboxLevel::Basic);
}
#[test]
fn test_policy_to_sandbox_level_mapping() {
let policy_disabled = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![],
network_allowed: true,
use_private_desktop: false,
};
assert_eq!(
get_sandbox_level(&policy_disabled),
WindowsSandboxLevel::Disabled
);
let policy_basic = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![PathBuf::from("/tmp")],
network_allowed: true,
use_private_desktop: false,
};
assert_eq!(get_sandbox_level(&policy_basic), WindowsSandboxLevel::Basic);
let policy_strict = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![],
network_allowed: false,
use_private_desktop: true,
};
assert_eq!(
get_sandbox_level(&policy_strict),
WindowsSandboxLevel::Strict
);
let policy_full = WindowsSandboxPolicy {
read_allow: vec![],
write_deny: vec![PathBuf::from("/")],
network_allowed: false,
use_private_desktop: true,
};
assert_eq!(get_sandbox_level(&policy_full), WindowsSandboxLevel::Full);
}
#[test]
fn test_policy_read_only() {
let policy = WindowsSandboxPolicy::read_only();
assert!(!policy.network_allowed);
assert!(policy.use_private_desktop);
}
#[test]
fn test_policy_workspace_write() {
let writable_roots = vec![PathBuf::from("/workspace"), PathBuf::from("/home")];
let policy = WindowsSandboxPolicy::workspace_write(writable_roots.clone());
assert!(policy.network_allowed);
assert!(policy.use_private_desktop);
assert_eq!(policy.read_allow.len(), 2);
for root in &writable_roots {
assert!(policy.write_deny.iter().any(|p| p.starts_with(root)));
}
}
}