windows-sandbox: pass workspace roots to runner

This commit is contained in:
Michael Bolin
2026-05-27 13:18:01 -07:00
parent 7a26497836
commit f00a87deaa
32 changed files with 452 additions and 211 deletions
+20 -4
View File
@@ -388,7 +388,13 @@ pub fn build_exec_request(
.map(|request| {
let windows_sandbox_policy_cwd = AbsolutePathBuf::try_from(sandbox_cwd.to_path_buf())
.unwrap_or_else(|_| request.cwd.clone());
ExecRequest::from_sandbox_exec_request(request, options, windows_sandbox_policy_cwd)
let windows_sandbox_workspace_roots = vec![windows_sandbox_policy_cwd.clone()];
ExecRequest::from_sandbox_exec_request(
request,
options,
windows_sandbox_policy_cwd,
windows_sandbox_workspace_roots,
)
})
.map_err(CodexErr::from)?;
let use_windows_elevated_backend = windows_sandbox_uses_elevated_backend(
@@ -434,6 +440,7 @@ pub(crate) async fn execute_exec_request(
capture_policy,
sandbox,
windows_sandbox_policy_cwd,
windows_sandbox_workspace_roots,
windows_sandbox_level,
windows_sandbox_private_desktop,
permission_profile,
@@ -466,6 +473,7 @@ pub(crate) async fn execute_exec_request(
sandbox,
&permission_profile,
&windows_sandbox_policy_cwd,
&windows_sandbox_workspace_roots,
windows_sandbox_filesystem_overrides.as_ref(),
)
.await;
@@ -482,6 +490,8 @@ async fn get_raw_output_result(
#[cfg_attr(not(windows), allow(unused_variables))] sandbox: SandboxType,
#[cfg_attr(not(windows), allow(unused_variables))] permission_profile: &PermissionProfile,
#[cfg_attr(not(windows), allow(unused_variables))] windows_sandbox_policy_cwd: &AbsolutePathBuf,
#[cfg_attr(not(windows), allow(unused_variables))]
windows_sandbox_workspace_roots: &[AbsolutePathBuf],
#[cfg_attr(not(windows), allow(unused_variables))] windows_sandbox_filesystem_overrides: Option<
&WindowsSandboxFilesystemOverrides,
>,
@@ -492,6 +502,7 @@ async fn get_raw_output_result(
params,
permission_profile,
windows_sandbox_policy_cwd,
windows_sandbox_workspace_roots,
windows_sandbox_filesystem_overrides,
)
.await;
@@ -571,6 +582,7 @@ async fn exec_windows_sandbox(
params: ExecParams,
permission_profile: &PermissionProfile,
windows_sandbox_policy_cwd: &AbsolutePathBuf,
windows_sandbox_workspace_roots: &[AbsolutePathBuf],
windows_sandbox_filesystem_overrides: Option<&WindowsSandboxFilesystemOverrides>,
) -> Result<RawExecToolCallOutput> {
use crate::config::find_codex_home;
@@ -600,7 +612,11 @@ async fn exec_windows_sandbox(
None
};
let sandbox_cwd = windows_sandbox_policy_cwd.clone();
let workspace_roots = if windows_sandbox_workspace_roots.is_empty() {
vec![windows_sandbox_policy_cwd.clone()]
} else {
windows_sandbox_workspace_roots.to_vec()
};
let permission_profile = permission_profile.clone();
let codex_home = find_codex_home().map_err(|err| {
CodexErr::Io(io::Error::other(format!(
@@ -628,7 +644,7 @@ async fn exec_windows_sandbox(
run_windows_sandbox_capture_for_permission_profile_elevated(
codex_windows_sandbox::ElevatedSandboxProfileCaptureRequest {
permission_profile: &permission_profile,
permission_profile_cwd: &sandbox_cwd,
workspace_roots: workspace_roots.as_slice(),
codex_home: codex_home.as_ref(),
command,
cwd: &cwd,
@@ -647,7 +663,7 @@ async fn exec_windows_sandbox(
} else {
run_windows_sandbox_capture_with_filesystem_overrides(
&permission_profile,
&sandbox_cwd,
workspace_roots.as_slice(),
codex_home.as_ref(),
command,
&cwd,
+5
View File
@@ -52,6 +52,7 @@ pub struct ExecRequest {
pub capture_policy: ExecCapturePolicy,
pub sandbox: SandboxType,
pub windows_sandbox_policy_cwd: AbsolutePathBuf,
pub windows_sandbox_workspace_roots: Vec<AbsolutePathBuf>,
pub windows_sandbox_level: WindowsSandboxLevel,
pub windows_sandbox_private_desktop: bool,
pub permission_profile: PermissionProfile,
@@ -77,6 +78,7 @@ impl ExecRequest {
arg0: Option<String>,
) -> Self {
let windows_sandbox_policy_cwd = cwd.clone();
let windows_sandbox_workspace_roots = vec![cwd.clone()];
let (file_system_sandbox_policy, network_sandbox_policy) =
permission_profile.to_runtime_permissions();
Self {
@@ -89,6 +91,7 @@ impl ExecRequest {
capture_policy,
sandbox,
windows_sandbox_policy_cwd,
windows_sandbox_workspace_roots,
windows_sandbox_level,
windows_sandbox_private_desktop,
permission_profile,
@@ -112,6 +115,7 @@ impl ExecRequest {
request: SandboxExecRequest,
options: ExecOptions,
windows_sandbox_policy_cwd: AbsolutePathBuf,
windows_sandbox_workspace_roots: Vec<AbsolutePathBuf>,
) -> Self {
let SandboxExecRequest {
command,
@@ -150,6 +154,7 @@ impl ExecRequest {
capture_policy,
sandbox,
windows_sandbox_policy_cwd,
windows_sandbox_workspace_roots,
windows_sandbox_level,
windows_sandbox_private_desktop,
permission_profile,
+1
View File
@@ -193,6 +193,7 @@ pub(crate) async fn execute_user_shell_command(
capture_policy: ExecCapturePolicy::ShellTool,
sandbox: SandboxType::None,
windows_sandbox_policy_cwd: cwd.clone(),
windows_sandbox_workspace_roots: turn_context.config.effective_workspace_roots(),
windows_sandbox_level: turn_context.windows_sandbox_level,
windows_sandbox_private_desktop: turn_context
.config
+4
View File
@@ -85,6 +85,7 @@ impl ToolOrchestrator {
enforce_managed_network: attempt.enforce_managed_network,
manager: attempt.manager,
sandbox_cwd: attempt.sandbox_cwd,
workspace_roots: attempt.workspace_roots,
codex_linux_sandbox_exe: attempt.codex_linux_sandbox_exe,
use_legacy_landlock: attempt.use_legacy_landlock,
windows_sandbox_level: attempt.windows_sandbox_level,
@@ -236,12 +237,14 @@ impl ToolOrchestrator {
let use_legacy_landlock = turn_ctx.features.use_legacy_landlock();
#[allow(deprecated)]
let sandbox_cwd = tool.sandbox_cwd(req).unwrap_or(&turn_ctx.cwd);
let workspace_roots = turn_ctx.config.effective_workspace_roots();
let initial_attempt = SandboxAttempt {
sandbox: initial_sandbox,
permissions: &turn_ctx.permission_profile,
enforce_managed_network: managed_network_active,
manager: &self.sandbox,
sandbox_cwd,
workspace_roots: workspace_roots.as_slice(),
codex_linux_sandbox_exe: turn_ctx.codex_linux_sandbox_exe.as_ref(),
use_legacy_landlock,
windows_sandbox_level: turn_ctx.windows_sandbox_level,
@@ -360,6 +363,7 @@ impl ToolOrchestrator {
enforce_managed_network: managed_network_active,
manager: &self.sandbox,
sandbox_cwd,
workspace_roots: workspace_roots.as_slice(),
codex_linux_sandbox_exe: None,
use_legacy_landlock,
windows_sandbox_level: turn_ctx.windows_sandbox_level,
@@ -212,6 +212,7 @@ async fn file_system_sandbox_context_uses_active_attempt() {
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: &path,
workspace_roots: std::slice::from_ref(&path),
codex_linux_sandbox_exe: None,
use_legacy_landlock: true,
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
@@ -265,6 +266,7 @@ async fn no_sandbox_attempt_has_no_file_system_context() {
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: &path,
workspace_roots: std::slice::from_ref(&path),
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
@@ -111,6 +111,7 @@ async fn explicit_escalation_prepares_exec_without_managed_network() -> anyhow::
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: &cwd,
workspace_roots: std::slice::from_ref(&cwd),
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: WindowsSandboxLevel::Disabled,
@@ -140,6 +140,7 @@ pub(super) async fn try_run_zsh_fork(
capture_policy: _capture_policy,
sandbox,
windows_sandbox_policy_cwd: sandbox_policy_cwd,
windows_sandbox_workspace_roots,
windows_sandbox_level,
windows_sandbox_private_desktop: _windows_sandbox_private_desktop,
permission_profile,
@@ -168,6 +169,7 @@ pub(super) async fn try_run_zsh_fork(
windows_sandbox_level,
arg0,
sandbox_policy_cwd,
windows_sandbox_workspace_roots,
codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),
use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),
};
@@ -269,6 +271,7 @@ pub(crate) async fn prepare_unified_exec_zsh_fork(
windows_sandbox_level: exec_request.windows_sandbox_level,
arg0: exec_request.arg0.clone(),
sandbox_policy_cwd: exec_request.windows_sandbox_policy_cwd.clone(),
windows_sandbox_workspace_roots: exec_request.windows_sandbox_workspace_roots.clone(),
codex_linux_sandbox_exe: ctx.turn.codex_linux_sandbox_exe.clone(),
use_legacy_landlock: ctx.turn.features.use_legacy_landlock(),
};
@@ -753,6 +756,7 @@ struct CoreShellCommandExecutor {
windows_sandbox_level: WindowsSandboxLevel,
arg0: Option<String>,
sandbox_policy_cwd: AbsolutePathBuf,
windows_sandbox_workspace_roots: Vec<AbsolutePathBuf>,
codex_linux_sandbox_exe: Option<PathBuf>,
use_legacy_landlock: bool,
}
@@ -795,6 +799,7 @@ impl ShellCommandExecutor for CoreShellCommandExecutor {
capture_policy: ExecCapturePolicy::ShellTool,
sandbox: self.sandbox,
windows_sandbox_policy_cwd: self.sandbox_policy_cwd.clone(),
windows_sandbox_workspace_roots: self.windows_sandbox_workspace_roots.clone(),
windows_sandbox_level: self.windows_sandbox_level,
windows_sandbox_private_desktop: false,
permission_profile: self.permission_profile.clone(),
@@ -932,6 +937,7 @@ impl CoreShellCommandExecutor {
exec_request,
options,
self.sandbox_policy_cwd.clone(),
self.windows_sandbox_workspace_roots.clone(),
);
if let Some(network) = exec_request.network.as_ref() {
network.apply_to_env(&mut exec_request.env);
+2
View File
@@ -383,6 +383,7 @@ pub(crate) struct SandboxAttempt<'a> {
pub enforce_managed_network: bool,
pub(crate) manager: &'a SandboxManager,
pub(crate) sandbox_cwd: &'a AbsolutePathBuf,
pub(crate) workspace_roots: &'a [AbsolutePathBuf],
pub codex_linux_sandbox_exe: Option<&'a std::path::PathBuf>,
pub use_legacy_landlock: bool,
pub windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
@@ -422,6 +423,7 @@ impl<'a> SandboxAttempt<'a> {
request,
options,
windows_sandbox_policy_cwd,
self.workspace_roots.to_vec(),
)
})
}
@@ -897,7 +897,7 @@ impl UnifiedExecProcessManager {
codex_protocol::config_types::WindowsSandboxLevel::Elevated => {
codex_windows_sandbox::spawn_windows_sandbox_session_elevated_for_permission_profile(
&request.permission_profile,
request.windows_sandbox_policy_cwd.as_path(),
request.windows_sandbox_workspace_roots.as_slice(),
codex_home.as_ref(),
request.command.clone(),
request.cwd.as_path(),
@@ -918,7 +918,7 @@ impl UnifiedExecProcessManager {
| codex_protocol::config_types::WindowsSandboxLevel::Disabled => {
codex_windows_sandbox::spawn_windows_sandbox_session_legacy(
&request.permission_profile,
request.windows_sandbox_policy_cwd.as_path(),
request.windows_sandbox_workspace_roots.as_slice(),
codex_home.as_ref(),
request.command.clone(),
request.cwd.as_path(),
@@ -100,7 +100,8 @@ fn exec_server_params_use_env_policy_overlay_contract() {
expiration: crate::exec::ExecExpiration::DefaultTimeout,
capture_policy: crate::exec::ExecCapturePolicy::ShellTool,
sandbox: codex_sandboxing::SandboxType::None,
windows_sandbox_policy_cwd: cwd,
windows_sandbox_policy_cwd: cwd.clone(),
windows_sandbox_workspace_roots: vec![cwd],
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled,
windows_sandbox_private_desktop: false,
permission_profile,
+15 -14
View File
@@ -9,6 +9,7 @@ use codex_login::default_client::originator;
use codex_otel::sanitize_metric_tag_value;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::models::PermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::path::Path;
@@ -146,15 +147,15 @@ pub fn elevated_setup_failure_metric_name(_err: &anyhow::Error) -> &'static str
#[cfg(target_os = "windows")]
pub fn run_elevated_setup(
permission_profile: &PermissionProfile,
permission_profile_cwd: &Path,
workspace_roots: &[AbsolutePathBuf],
command_cwd: &Path,
env_map: &HashMap<String, String>,
codex_home: &Path,
) -> anyhow::Result<()> {
let permissions =
codex_windows_sandbox::ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_cwd(
codex_windows_sandbox::ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots(
permission_profile,
permission_profile_cwd,
workspace_roots,
)?;
codex_windows_sandbox::run_elevated_setup(
codex_windows_sandbox::SandboxSetupRequest {
@@ -171,7 +172,7 @@ pub fn run_elevated_setup(
#[cfg(not(target_os = "windows"))]
pub fn run_elevated_setup(
_permission_profile: &PermissionProfile,
_permission_profile_cwd: &Path,
_workspace_roots: &[AbsolutePathBuf],
_command_cwd: &Path,
_env_map: &HashMap<String, String>,
_codex_home: &Path,
@@ -182,14 +183,14 @@ pub fn run_elevated_setup(
#[cfg(target_os = "windows")]
pub fn run_legacy_setup_preflight(
permission_profile: &PermissionProfile,
permission_profile_cwd: &Path,
workspace_roots: &[AbsolutePathBuf],
command_cwd: &Path,
env_map: &HashMap<String, String>,
codex_home: &Path,
) -> anyhow::Result<()> {
codex_windows_sandbox::run_windows_sandbox_legacy_preflight(
permission_profile,
permission_profile_cwd,
workspace_roots,
codex_home,
command_cwd,
env_map,
@@ -199,7 +200,7 @@ pub fn run_legacy_setup_preflight(
#[cfg(target_os = "windows")]
pub fn run_setup_refresh_with_extra_read_roots(
permission_profile: &PermissionProfile,
permission_profile_cwd: &Path,
workspace_roots: &[AbsolutePathBuf],
command_cwd: &Path,
env_map: &HashMap<String, String>,
codex_home: &Path,
@@ -207,7 +208,7 @@ pub fn run_setup_refresh_with_extra_read_roots(
) -> anyhow::Result<()> {
codex_windows_sandbox::run_setup_refresh_with_extra_read_roots(
permission_profile,
permission_profile_cwd,
workspace_roots,
command_cwd,
env_map,
codex_home,
@@ -219,7 +220,7 @@ pub fn run_setup_refresh_with_extra_read_roots(
#[cfg(not(target_os = "windows"))]
pub fn run_legacy_setup_preflight(
_permission_profile: &PermissionProfile,
_permission_profile_cwd: &Path,
_workspace_roots: &[AbsolutePathBuf],
_command_cwd: &Path,
_env_map: &HashMap<String, String>,
_codex_home: &Path,
@@ -230,7 +231,7 @@ pub fn run_legacy_setup_preflight(
#[cfg(not(target_os = "windows"))]
pub fn run_setup_refresh_with_extra_read_roots(
_permission_profile: &PermissionProfile,
_permission_profile_cwd: &Path,
_workspace_roots: &[AbsolutePathBuf],
_command_cwd: &Path,
_env_map: &HashMap<String, String>,
_codex_home: &Path,
@@ -249,7 +250,7 @@ pub enum WindowsSandboxSetupMode {
pub struct WindowsSandboxSetupRequest {
pub mode: WindowsSandboxSetupMode,
pub permission_profile: PermissionProfile,
pub permission_profile_cwd: PathBuf,
pub workspace_roots: Vec<AbsolutePathBuf>,
pub command_cwd: PathBuf,
pub env_map: HashMap<String, String>,
pub codex_home: PathBuf,
@@ -287,7 +288,7 @@ async fn run_windows_sandbox_setup_and_persist(
) -> anyhow::Result<()> {
let mode = request.mode;
let permission_profile = request.permission_profile;
let permission_profile_cwd = request.permission_profile_cwd;
let workspace_roots = request.workspace_roots;
let command_cwd = request.command_cwd;
let env_map = request.env_map;
let codex_home = request.codex_home;
@@ -299,7 +300,7 @@ async fn run_windows_sandbox_setup_and_persist(
if !sandbox_setup_is_complete(setup_codex_home.as_path()) {
run_elevated_setup(
&permission_profile,
permission_profile_cwd.as_path(),
workspace_roots.as_slice(),
command_cwd.as_path(),
&env_map,
setup_codex_home.as_path(),
@@ -309,7 +310,7 @@ async fn run_windows_sandbox_setup_and_persist(
WindowsSandboxSetupMode::Unelevated => {
run_legacy_setup_preflight(
&permission_profile,
permission_profile_cwd.as_path(),
workspace_roots.as_slice(),
command_cwd.as_path(),
&env_map,
setup_codex_home.as_path(),
@@ -1,13 +1,14 @@
use crate::windows_sandbox::run_setup_refresh_with_extra_read_roots;
use anyhow::Result;
use codex_protocol::models::PermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
pub fn grant_read_root_non_elevated(
permission_profile: &PermissionProfile,
permission_profile_cwd: &Path,
workspace_roots: &[AbsolutePathBuf],
command_cwd: &Path,
env_map: &HashMap<String, String>,
codex_home: &Path,
@@ -26,7 +27,7 @@ pub fn grant_read_root_non_elevated(
let canonical_root = dunce::canonicalize(read_root)?;
run_setup_refresh_with_extra_read_roots(
permission_profile,
permission_profile_cwd,
workspace_roots,
command_cwd,
env_map,
codex_home,
@@ -1,19 +1,21 @@
use super::grant_read_root_non_elevated;
use codex_protocol::models::PermissionProfile;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
use std::path::Path;
use tempfile::TempDir;
fn permission_profile() -> PermissionProfile {
PermissionProfile::workspace_write()
fn workspace_roots_for(root: &Path) -> Vec<AbsolutePathBuf> {
vec![AbsolutePathBuf::from_absolute_path(root).expect("absolute workspace root")]
}
#[test]
fn rejects_relative_path() {
let tmp = TempDir::new().expect("tempdir");
let workspace_roots = workspace_roots_for(tmp.path());
let err = grant_read_root_non_elevated(
&permission_profile(),
tmp.path(),
&PermissionProfile::workspace_write(),
workspace_roots.as_slice(),
tmp.path(),
&HashMap::new(),
tmp.path(),
@@ -27,9 +29,10 @@ fn rejects_relative_path() {
fn rejects_missing_path() {
let tmp = TempDir::new().expect("tempdir");
let missing = tmp.path().join("does-not-exist");
let workspace_roots = workspace_roots_for(tmp.path());
let err = grant_read_root_non_elevated(
&permission_profile(),
tmp.path(),
&PermissionProfile::workspace_write(),
workspace_roots.as_slice(),
tmp.path(),
&HashMap::new(),
tmp.path(),
@@ -44,9 +47,10 @@ fn rejects_file_path() {
let tmp = TempDir::new().expect("tempdir");
let file_path = tmp.path().join("file.txt");
std::fs::write(&file_path, "hello").expect("write file");
let workspace_roots = workspace_roots_for(tmp.path());
let err = grant_read_root_non_elevated(
&permission_profile(),
tmp.path(),
&PermissionProfile::workspace_write(),
workspace_roots.as_slice(),
tmp.path(),
&HashMap::new(),
tmp.path(),