Add hidden Windows sandbox wrapper entrypoint (#28358)

## Why

This is the second PR in the Windows fs-helper sandbox stack. The
fs-helper path needs a Windows sandbox launcher that has the same
argv-shaped contract as macOS `sandbox-exec` and `codex-linux-sandbox`,
but this PR only introduces that hidden launcher. It does not route
fs-helper through it yet.

The hidden launcher still needs to be policy-complete before later
direct-spawn callers use it. In particular, it has to carry the same
Windows sandbox policy details that the existing spawn paths already
understand: proxy enforcement, read/write root overrides, and
deny-read/deny-write overrides.

## What Changed

- Added the hidden `codex.exe --run-as-windows-sandbox` arg1 dispatch
path.
- Added `windows-sandbox-rs/src/wrapper.rs`, which parses the wrapper
argv, launches the requested command through the shared Windows sandbox
session runner from PR1, and forwards stdio.
- Added `create_windows_sandbox_command_args_for_permission_profile()`
so later direct-spawn callers can build the wrapper argv consistently.
- Made the wrapper argv round-trip the full Windows sandbox policy
surface it needs later: workspace roots, environment, permission
profile, sandbox level, private desktop, proxy enforcement, read/write
root overrides, and deny-read/deny-write overrides.
- Carried `proxy_enforced` through the shared Windows session request so
proxy-managed executions continue to use the offline/elevated sandbox
identity.
- Added wrapper argument round-trip coverage for the full policy fields.

## Verification

- `just test -p codex-windows-sandbox windows_wrapper_args_round_trip`
- `just test -p codex-arg0`
- `just test -p codex-core exec::tests::windows_`
- `just fix -p codex-windows-sandbox -p codex-core -p codex-cli`

Local note: the full `just fmt` command still fails on this workstation
in non-Rust formatter setup (`uv` cache access denied and missing
`dotslash`/buildifier), but the Rust formatter phase completed.
This commit is contained in:
iceweasel-oai
2026-06-15 14:30:32 -07:00
committed by GitHub
Unverified
parent e7a9988d1a
commit fbbe7706d6
11 changed files with 491 additions and 39 deletions
+1
View File
@@ -2183,6 +2183,7 @@ dependencies = [
"codex-shell-escalation",
"codex-utils-absolute-path",
"codex-utils-home-dir",
"codex-windows-sandbox",
"dotenvy",
"pretty_assertions",
"tempfile",
+3
View File
@@ -26,5 +26,8 @@ dotenvy = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread"] }
[target.'cfg(windows)'.dependencies]
codex-windows-sandbox = { workspace = true }
[dev-dependencies]
pretty_assertions = { workspace = true }
+6
View File
@@ -9,6 +9,8 @@ use codex_exec_server::CODEX_FS_HELPER_ARG1;
use codex_install_context::InstallContext;
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
use codex_utils_home_dir::find_codex_home;
#[cfg(target_os = "windows")]
use codex_windows_sandbox::CODEX_WINDOWS_SANDBOX_ARG1;
#[cfg(unix)]
use std::os::unix::fs::symlink;
use tempfile::TempDir;
@@ -99,6 +101,10 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
if argv1 == CODEX_FS_HELPER_ARG1 {
codex_exec_server::run_fs_helper_main();
}
#[cfg(target_os = "windows")]
if argv1 == CODEX_WINDOWS_SANDBOX_ARG1 {
codex_windows_sandbox::run_windows_sandbox_wrapper_main();
}
if argv1 == CODEX_CORE_APPLY_PATCH_ARG1 {
let patch_arg = args.next().and_then(|s| s.to_str().map(str::to_owned));
let exit_code = match patch_arg {
+1
View File
@@ -382,6 +382,7 @@ async fn run_command_under_windows_session(
cwd: cwd.as_path(),
env_map: env,
windows_sandbox_level: WindowsSandboxLevel::from_config(config),
proxy_enforced: false,
timeout_ms: None,
read_roots_override: None,
read_roots_include_platform_defaults: false,
@@ -935,6 +935,7 @@ impl UnifiedExecProcessManager {
request.command.clone(),
request.cwd.as_path(),
request.env.clone(),
request.network.is_some(),
None,
elevated_read_roots_override.as_deref(),
elevated_read_roots_include_platform_defaults,
+8
View File
@@ -109,6 +109,8 @@ mod stdio_bridge;
#[cfg(target_os = "windows")]
mod unified_exec;
#[cfg(target_os = "windows")]
mod wrapper;
#[cfg(target_os = "windows")]
pub(crate) use elevated::ipc_framed;
@@ -318,6 +320,12 @@ pub use winutil::string_from_sid_bytes;
pub use winutil::to_wide;
#[cfg(target_os = "windows")]
pub use workspace_acl::is_command_cwd_root;
#[cfg(target_os = "windows")]
pub use wrapper::CODEX_WINDOWS_SANDBOX_ARG1;
#[cfg(target_os = "windows")]
pub use wrapper::create_windows_sandbox_command_args_for_permission_profile;
#[cfg(target_os = "windows")]
pub use wrapper::run_windows_sandbox_wrapper_main;
#[cfg(not(target_os = "windows"))]
pub use stub::CaptureResult;
@@ -356,6 +356,7 @@ pub(crate) fn prepare_elevated_spawn_context_for_permissions(
write_roots_override: Option<&[PathBuf]>,
deny_read_paths_override: &[PathBuf],
deny_write_paths_override: &[PathBuf],
proxy_enforced: bool,
) -> Result<ElevatedSpawnContext> {
normalize_null_device_env(env_map);
ensure_non_interactive_pager(env_map);
@@ -410,7 +411,7 @@ pub(crate) fn prepare_elevated_spawn_context_for_permissions(
} else {
deny_write_paths_override
},
/*proxy_enforced*/ false,
proxy_enforced,
)?;
let caps = load_or_create_cap_sids(codex_home)?;
let (psid_to_use, cap_sids) = if uses_write_capabilities {
@@ -55,6 +55,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated_for_permission_profil
command: Vec<String>,
cwd: &Path,
mut env_map: HashMap<String, String>,
proxy_enforced: bool,
timeout_ms: Option<u64>,
read_roots_override: Option<&[PathBuf]>,
read_roots_include_platform_defaults: bool,
@@ -89,6 +90,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated_for_permission_profil
write_roots_override,
&deny_read_paths_override,
&deny_write_paths_override,
proxy_enforced,
)?;
let spawn_request = SpawnRequest {
@@ -30,6 +30,7 @@ pub struct WindowsSandboxSessionRequest<'a> {
pub cwd: &'a Path,
pub env_map: HashMap<String, String>,
pub windows_sandbox_level: WindowsSandboxLevel,
pub proxy_enforced: bool,
pub timeout_ms: Option<u64>,
pub read_roots_override: Option<&'a [PathBuf]>,
pub read_roots_include_platform_defaults: bool,
@@ -44,44 +45,44 @@ pub struct WindowsSandboxSessionRequest<'a> {
pub async fn spawn_windows_sandbox_session_for_level(
request: WindowsSandboxSessionRequest<'_>,
) -> Result<SpawnedProcess> {
match request.windows_sandbox_level {
WindowsSandboxLevel::Elevated => {
spawn_windows_sandbox_session_elevated_for_permission_profile(
request.permission_profile,
request.workspace_roots,
request.codex_home,
request.command,
request.cwd,
request.env_map,
request.timeout_ms,
request.read_roots_override,
request.read_roots_include_platform_defaults,
request.write_roots_override,
request.deny_read_paths_override,
request.deny_write_paths_override,
request.tty,
request.stdin_open,
request.use_private_desktop,
)
.await
}
WindowsSandboxLevel::RestrictedToken | WindowsSandboxLevel::Disabled => {
spawn_windows_sandbox_session_legacy(
request.permission_profile,
request.workspace_roots,
request.codex_home,
request.command,
request.cwd,
request.env_map,
request.timeout_ms,
request.deny_read_paths_override,
request.deny_write_paths_override,
request.tty,
request.stdin_open,
request.use_private_desktop,
)
.await
}
if request.proxy_enforced
|| matches!(request.windows_sandbox_level, WindowsSandboxLevel::Elevated)
{
spawn_windows_sandbox_session_elevated_for_permission_profile(
request.permission_profile,
request.workspace_roots,
request.codex_home,
request.command,
request.cwd,
request.env_map,
request.proxy_enforced,
request.timeout_ms,
request.read_roots_override,
request.read_roots_include_platform_defaults,
request.write_roots_override,
request.deny_read_paths_override,
request.deny_write_paths_override,
request.tty,
request.stdin_open,
request.use_private_desktop,
)
.await
} else {
spawn_windows_sandbox_session_legacy(
request.permission_profile,
request.workspace_roots,
request.codex_home,
request.command,
request.cwd,
request.env_map,
request.timeout_ms,
request.deny_read_paths_override,
request.deny_write_paths_override,
request.tty,
request.stdin_open,
request.use_private_desktop,
)
.await
}
}
@@ -125,6 +126,7 @@ pub async fn spawn_windows_sandbox_session_elevated_for_permission_profile(
command: Vec<String>,
cwd: &Path,
env_map: HashMap<String, String>,
proxy_enforced: bool,
timeout_ms: Option<u64>,
read_roots_override: Option<&[PathBuf]>,
read_roots_include_platform_defaults: bool,
@@ -142,6 +144,7 @@ pub async fn spawn_windows_sandbox_session_elevated_for_permission_profile(
command,
cwd,
env_map,
proxy_enforced,
timeout_ms,
read_roots_override,
read_roots_include_platform_defaults,
+320
View File
@@ -0,0 +1,320 @@
//! Internal `codex.exe --run-as-windows-sandbox` wrapper.
//!
//! This gives direct-spawn callers an argv-shaped Windows sandbox launcher,
//! analogous to the macOS seatbelt and Linux sandbox wrapper paths. The wrapper
//! parses sandbox metadata from argv, launches the requested inner command in a
//! Windows sandbox session, and forwards stdio to that inner command.
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use anyhow::Context;
use anyhow::Result;
use anyhow::anyhow;
use anyhow::bail;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
pub const CODEX_WINDOWS_SANDBOX_ARG1: &str = "--run-as-windows-sandbox";
const COMMAND_CWD_FLAG: &str = "--command-cwd";
const CODEX_HOME_FLAG: &str = "--codex-home";
const DENY_READ_PATHS_JSON_FLAG: &str = "--deny-read-paths-json";
const DENY_WRITE_PATHS_JSON_FLAG: &str = "--deny-write-paths-json";
const ENV_JSON_FLAG: &str = "--env-json";
const PERMISSION_PROFILE_FLAG: &str = "--permission-profile";
const PRIVATE_DESKTOP_FLAG: &str = "--windows-sandbox-private-desktop";
const PROXY_ENFORCED_FLAG: &str = "--proxy-enforced";
const READ_ROOTS_INCLUDE_PLATFORM_DEFAULTS_FLAG: &str = "--read-roots-include-platform-defaults";
const READ_ROOTS_JSON_FLAG: &str = "--read-roots-json";
const SANDBOX_LEVEL_FLAG: &str = "--windows-sandbox-level";
const WRITE_ROOTS_JSON_FLAG: &str = "--write-roots-json";
const WORKSPACE_ROOT_FLAG: &str = "--workspace-root";
#[allow(clippy::too_many_arguments)]
pub fn create_windows_sandbox_command_args_for_permission_profile(
command: Vec<String>,
command_cwd: &AbsolutePathBuf,
workspace_roots: &[AbsolutePathBuf],
env_map: &HashMap<String, String>,
permission_profile: &PermissionProfile,
windows_sandbox_level: WindowsSandboxLevel,
windows_sandbox_private_desktop: bool,
proxy_enforced: bool,
read_roots_override: Option<&[PathBuf]>,
read_roots_include_platform_defaults: bool,
write_roots_override: Option<&[PathBuf]>,
deny_read_paths_override: &[AbsolutePathBuf],
deny_write_paths_override: &[AbsolutePathBuf],
codex_home: &Path,
) -> Vec<String> {
let permission_profile_json = serde_json::to_string(permission_profile)
.unwrap_or_else(|err| panic!("failed to serialize permission profile: {err}"));
let env_json = serde_json::to_string(env_map)
.unwrap_or_else(|err| panic!("failed to serialize env: {err}"));
let mut args = vec![
CODEX_WINDOWS_SANDBOX_ARG1.to_string(),
CODEX_HOME_FLAG.to_string(),
codex_home.to_string_lossy().into_owned(),
COMMAND_CWD_FLAG.to_string(),
command_cwd.as_path().to_string_lossy().into_owned(),
PERMISSION_PROFILE_FLAG.to_string(),
permission_profile_json,
ENV_JSON_FLAG.to_string(),
env_json,
SANDBOX_LEVEL_FLAG.to_string(),
windows_sandbox_level.to_string(),
];
let workspace_roots = if workspace_roots.is_empty() {
std::slice::from_ref(command_cwd)
} else {
workspace_roots
};
for root in workspace_roots {
args.push(WORKSPACE_ROOT_FLAG.to_string());
args.push(root.as_path().to_string_lossy().into_owned());
}
if windows_sandbox_private_desktop {
args.push(PRIVATE_DESKTOP_FLAG.to_string());
}
if proxy_enforced {
args.push(PROXY_ENFORCED_FLAG.to_string());
}
if let Some(read_roots_override) = read_roots_override {
push_json_arg(&mut args, READ_ROOTS_JSON_FLAG, &read_roots_override);
}
if read_roots_include_platform_defaults {
args.push(READ_ROOTS_INCLUDE_PLATFORM_DEFAULTS_FLAG.to_string());
}
if let Some(write_roots_override) = write_roots_override {
push_json_arg(&mut args, WRITE_ROOTS_JSON_FLAG, &write_roots_override);
}
if !deny_read_paths_override.is_empty() {
push_json_arg(
&mut args,
DENY_READ_PATHS_JSON_FLAG,
&deny_read_paths_override,
);
}
if !deny_write_paths_override.is_empty() {
push_json_arg(
&mut args,
DENY_WRITE_PATHS_JSON_FLAG,
&deny_write_paths_override,
);
}
args.push("--".to_string());
args.extend(command);
args
}
fn push_json_arg<T: serde::Serialize>(args: &mut Vec<String>, flag: &str, value: &T) {
args.push(flag.to_string());
args.push(
serde_json::to_string(value)
.unwrap_or_else(|err| panic!("failed to serialize {flag}: {err}")),
);
}
pub fn run_windows_sandbox_wrapper_main() -> ! {
let args = std::env::args().skip(2).collect::<Vec<_>>();
let runtime = match tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
{
Ok(runtime) => runtime,
Err(err) => {
eprintln!("windows sandbox failed to build runtime: {err}");
std::process::exit(1);
}
};
let exit_code = match runtime.block_on(run_windows_sandbox_wrapper_args(args)) {
Ok(exit_code) => exit_code,
Err(err) => {
eprintln!("windows sandbox failed: {err:#}");
1
}
};
std::process::exit(exit_code);
}
async fn run_windows_sandbox_wrapper_args(args: Vec<String>) -> Result<i32> {
let request = parse_windows_sandbox_wrapper_args(args)?;
run_windows_sandbox_wrapper_request(request).await
}
struct WindowsSandboxWrapperRequest {
codex_home: PathBuf,
command_cwd: AbsolutePathBuf,
workspace_roots: Vec<AbsolutePathBuf>,
env_map: HashMap<String, String>,
permission_profile: PermissionProfile,
windows_sandbox_level: WindowsSandboxLevel,
windows_sandbox_private_desktop: bool,
proxy_enforced: bool,
read_roots_override: Option<Vec<PathBuf>>,
read_roots_include_platform_defaults: bool,
write_roots_override: Option<Vec<PathBuf>>,
deny_read_paths_override: Vec<AbsolutePathBuf>,
deny_write_paths_override: Vec<AbsolutePathBuf>,
command: Vec<String>,
}
async fn run_windows_sandbox_wrapper_request(request: WindowsSandboxWrapperRequest) -> Result<i32> {
if request.command.is_empty() {
bail!("missing sandboxed command in windows sandbox wrapper request");
}
let spawned =
crate::spawn_windows_sandbox_session_for_level(crate::WindowsSandboxSessionRequest {
permission_profile: &request.permission_profile,
workspace_roots: request.workspace_roots.as_slice(),
codex_home: request.codex_home.as_path(),
command: request.command,
cwd: request.command_cwd.as_path(),
env_map: request.env_map,
windows_sandbox_level: request.windows_sandbox_level,
proxy_enforced: request.proxy_enforced,
timeout_ms: None,
read_roots_override: request.read_roots_override.as_deref(),
read_roots_include_platform_defaults: request.read_roots_include_platform_defaults,
write_roots_override: request.write_roots_override.as_deref(),
deny_read_paths_override: request.deny_read_paths_override.as_slice(),
deny_write_paths_override: request.deny_write_paths_override.as_slice(),
tty: false,
stdin_open: true,
use_private_desktop: request.windows_sandbox_private_desktop,
})
.await?;
Ok(crate::forward_sandbox_session_stdio(spawned).await)
}
fn parse_windows_sandbox_wrapper_args(args: Vec<String>) -> Result<WindowsSandboxWrapperRequest> {
let mut args = args.into_iter();
let mut codex_home = None;
let mut command_cwd = None;
let mut workspace_roots = Vec::new();
let mut env_map = None;
let mut permission_profile = None;
let mut windows_sandbox_level = None;
let mut windows_sandbox_private_desktop = false;
let mut proxy_enforced = false;
let mut read_roots_override = None;
let mut read_roots_include_platform_defaults = false;
let mut write_roots_override = None;
let mut deny_read_paths_override = Vec::new();
let mut deny_write_paths_override = Vec::new();
let mut command = None;
while let Some(arg) = args.next() {
match arg.as_str() {
CODEX_HOME_FLAG => codex_home = Some(PathBuf::from(next_flag_value(&mut args, &arg)?)),
COMMAND_CWD_FLAG => {
command_cwd = Some(absolute_path_arg(next_flag_value(&mut args, &arg)?, &arg)?);
}
WORKSPACE_ROOT_FLAG => {
workspace_roots.push(absolute_path_arg(next_flag_value(&mut args, &arg)?, &arg)?);
}
ENV_JSON_FLAG => {
let value = next_flag_value(&mut args, &arg)?;
env_map = Some(serde_json::from_str(&value).context("failed to parse env json")?);
}
DENY_READ_PATHS_JSON_FLAG => {
deny_read_paths_override =
json_flag_value(next_flag_value(&mut args, &arg)?, &arg)?;
}
DENY_WRITE_PATHS_JSON_FLAG => {
deny_write_paths_override =
json_flag_value(next_flag_value(&mut args, &arg)?, &arg)?;
}
PERMISSION_PROFILE_FLAG => {
let value = next_flag_value(&mut args, &arg)?;
permission_profile = Some(
serde_json::from_str(&value).context("failed to parse permission profile")?,
);
}
SANDBOX_LEVEL_FLAG => {
let value = next_flag_value(&mut args, &arg)?;
windows_sandbox_level = Some(parse_windows_sandbox_level(&value)?);
}
PRIVATE_DESKTOP_FLAG => windows_sandbox_private_desktop = true,
PROXY_ENFORCED_FLAG => proxy_enforced = true,
READ_ROOTS_INCLUDE_PLATFORM_DEFAULTS_FLAG => {
read_roots_include_platform_defaults = true;
}
READ_ROOTS_JSON_FLAG => {
read_roots_override =
Some(json_flag_value(next_flag_value(&mut args, &arg)?, &arg)?);
}
WRITE_ROOTS_JSON_FLAG => {
write_roots_override =
Some(json_flag_value(next_flag_value(&mut args, &arg)?, &arg)?);
}
"--" => {
command = Some(args.collect::<Vec<_>>());
break;
}
_ => bail!("unexpected windows sandbox wrapper argument: {arg}"),
}
}
let codex_home = codex_home.ok_or_else(|| anyhow!("missing required {CODEX_HOME_FLAG}"))?;
if !codex_home.is_absolute() {
bail!(
"{CODEX_HOME_FLAG} must be absolute: {}",
codex_home.display()
);
}
let command_cwd = command_cwd.ok_or_else(|| anyhow!("missing required {COMMAND_CWD_FLAG}"))?;
if workspace_roots.is_empty() {
workspace_roots.push(command_cwd.clone());
}
Ok(WindowsSandboxWrapperRequest {
codex_home,
command_cwd,
workspace_roots,
env_map: env_map.ok_or_else(|| anyhow!("missing required {ENV_JSON_FLAG}"))?,
permission_profile: permission_profile
.ok_or_else(|| anyhow!("missing required {PERMISSION_PROFILE_FLAG}"))?,
windows_sandbox_level: windows_sandbox_level
.ok_or_else(|| anyhow!("missing required {SANDBOX_LEVEL_FLAG}"))?,
windows_sandbox_private_desktop,
proxy_enforced,
read_roots_override,
read_roots_include_platform_defaults,
write_roots_override,
deny_read_paths_override,
deny_write_paths_override,
command: command.ok_or_else(|| anyhow!("missing sandboxed command separator --"))?,
})
}
fn next_flag_value(args: &mut impl Iterator<Item = String>, flag: &str) -> Result<String> {
args.next()
.ok_or_else(|| anyhow!("missing value for {flag}"))
}
fn absolute_path_arg(value: String, flag: &str) -> Result<AbsolutePathBuf> {
let path = PathBuf::from(value);
AbsolutePathBuf::from_absolute_path(path.as_path())
.with_context(|| format!("{flag} must be absolute: {}", path.display()))
}
fn json_flag_value<T: serde::de::DeserializeOwned>(value: String, flag: &str) -> Result<T> {
serde_json::from_str(&value).with_context(|| format!("failed to parse {flag}"))
}
fn parse_windows_sandbox_level(value: &str) -> Result<WindowsSandboxLevel> {
match value {
"disabled" => Ok(WindowsSandboxLevel::Disabled),
"restricted-token" => Ok(WindowsSandboxLevel::RestrictedToken),
"elevated" => Ok(WindowsSandboxLevel::Elevated),
_ => bail!("invalid windows sandbox level: {value}"),
}
}
#[cfg(test)]
#[path = "wrapper_tests.rs"]
mod tests;
@@ -0,0 +1,106 @@
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq;
use super::CODEX_HOME_FLAG;
use super::CODEX_WINDOWS_SANDBOX_ARG1;
use super::COMMAND_CWD_FLAG;
use super::DENY_READ_PATHS_JSON_FLAG;
use super::DENY_WRITE_PATHS_JSON_FLAG;
use super::ENV_JSON_FLAG;
use super::PERMISSION_PROFILE_FLAG;
use super::PRIVATE_DESKTOP_FLAG;
use super::PROXY_ENFORCED_FLAG;
use super::READ_ROOTS_INCLUDE_PLATFORM_DEFAULTS_FLAG;
use super::READ_ROOTS_JSON_FLAG;
use super::SANDBOX_LEVEL_FLAG;
use super::WORKSPACE_ROOT_FLAG;
use super::WRITE_ROOTS_JSON_FLAG;
use super::create_windows_sandbox_command_args_for_permission_profile;
use super::parse_windows_sandbox_wrapper_args;
#[test]
fn windows_wrapper_args_round_trip() {
let command_cwd = AbsolutePathBuf::from_absolute_path(Path::new(r"C:\workspace"))
.expect("absolute command cwd");
let workspace_roots = vec![
command_cwd.clone(),
AbsolutePathBuf::from_absolute_path(Path::new(r"D:\other-workspace"))
.expect("absolute workspace root"),
];
let env = HashMap::from([("Path".to_string(), r"C:\Windows\System32".to_string())]);
let permission_profile = PermissionProfile::External {
network: NetworkSandboxPolicy::Restricted,
};
let read_roots_override = vec![PathBuf::from(r"C:\read")];
let write_roots_override = vec![PathBuf::from(r"C:\write")];
let deny_read_paths_override = vec![
AbsolutePathBuf::from_absolute_path(Path::new(r"C:\blocked-read"))
.expect("absolute deny-read"),
];
let deny_write_paths_override = vec![
AbsolutePathBuf::from_absolute_path(Path::new(r"C:\blocked-write"))
.expect("absolute deny-write"),
];
let args = create_windows_sandbox_command_args_for_permission_profile(
vec![
"codex.exe".to_string(),
"--codex-run-as-fs-helper".to_string(),
],
&command_cwd,
workspace_roots.as_slice(),
&env,
&permission_profile,
WindowsSandboxLevel::Elevated,
/*windows_sandbox_private_desktop*/ true,
/*proxy_enforced*/ true,
Some(read_roots_override.as_slice()),
/*read_roots_include_platform_defaults*/ true,
Some(write_roots_override.as_slice()),
deny_read_paths_override.as_slice(),
deny_write_paths_override.as_slice(),
Path::new(r"C:\Users\me\.codex"),
);
assert_eq!(args[0], CODEX_WINDOWS_SANDBOX_ARG1);
assert!(args.contains(&CODEX_HOME_FLAG.to_string()));
assert!(args.contains(&COMMAND_CWD_FLAG.to_string()));
assert!(args.contains(&WORKSPACE_ROOT_FLAG.to_string()));
assert!(args.contains(&PERMISSION_PROFILE_FLAG.to_string()));
assert!(args.contains(&ENV_JSON_FLAG.to_string()));
assert!(args.contains(&SANDBOX_LEVEL_FLAG.to_string()));
assert!(args.contains(&PRIVATE_DESKTOP_FLAG.to_string()));
assert!(args.contains(&PROXY_ENFORCED_FLAG.to_string()));
assert!(args.contains(&READ_ROOTS_JSON_FLAG.to_string()));
assert!(args.contains(&READ_ROOTS_INCLUDE_PLATFORM_DEFAULTS_FLAG.to_string()));
assert!(args.contains(&WRITE_ROOTS_JSON_FLAG.to_string()));
assert!(args.contains(&DENY_READ_PATHS_JSON_FLAG.to_string()));
assert!(args.contains(&DENY_WRITE_PATHS_JSON_FLAG.to_string()));
let parsed =
parse_windows_sandbox_wrapper_args(args[1..].to_vec()).expect("parse wrapper args");
assert_eq!(
parsed.command,
vec!["codex.exe", "--codex-run-as-fs-helper"]
);
assert_eq!(parsed.command_cwd, command_cwd);
assert_eq!(parsed.workspace_roots, workspace_roots);
assert_eq!(parsed.env_map, env);
assert_eq!(parsed.permission_profile, permission_profile);
assert_eq!(parsed.windows_sandbox_level, WindowsSandboxLevel::Elevated);
assert_eq!(parsed.windows_sandbox_private_desktop, true);
assert_eq!(parsed.proxy_enforced, true);
assert_eq!(parsed.read_roots_override, Some(read_roots_override));
assert_eq!(parsed.read_roots_include_platform_defaults, true);
assert_eq!(parsed.write_roots_override, Some(write_roots_override));
assert_eq!(parsed.deny_read_paths_override, deny_read_paths_override);
assert_eq!(parsed.deny_write_paths_override, deny_write_paths_override);
}