Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 9 additions & 31 deletions codex-rs/core/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@ pub(crate) use codex_sandboxing::is_likely_sandbox_denied;
#[cfg(test)]
use codex_sandboxing::permission_profile_supports_windows_restricted_token_sandbox;
use codex_sandboxing::record_filesystem_sandbox_violation;
#[cfg(test)]
use codex_sandboxing::resolve_windows_elevated_filesystem_overrides;
#[cfg(test)]
use codex_sandboxing::resolve_windows_restricted_token_filesystem_overrides;
#[cfg(test)]
use codex_sandboxing::unsupported_windows_restricted_token_sandbox_reason;
#[cfg(any(test, target_os = "windows"))]
use codex_sandboxing::windows_sandbox_uses_elevated_backend;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
Expand Down Expand Up @@ -379,7 +382,7 @@ pub fn build_exec_request(
expiration,
capture_policy,
};
let mut exec_req = manager
let request = manager
.transform(SandboxTransformRequest {
command,
permissions: permission_profile,
Expand All @@ -393,38 +396,13 @@ pub fn build_exec_request(
windows_sandbox_level,
windows_sandbox_private_desktop,
})
.map(|request| {
let windows_sandbox_workspace_roots = if windows_sandbox_workspace_roots.is_empty() {
vec![sandbox_cwd.clone()]
} else {
windows_sandbox_workspace_roots.to_vec()
};
ExecRequest::from_sandbox_exec_request(
request,
options,
windows_sandbox_workspace_roots,
)
})
.map_err(CodexErr::from)?;
let use_windows_elevated_backend =
windows_sandbox_uses_elevated_backend(exec_req.windows_sandbox_level);
exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend {
resolve_windows_elevated_filesystem_overrides(
exec_req.sandbox,
&exec_req.permission_profile,
sandbox_cwd,
use_windows_elevated_backend,
)
let windows_sandbox_workspace_roots = if windows_sandbox_workspace_roots.is_empty() {
vec![sandbox_cwd.clone()]
} else {
resolve_windows_restricted_token_filesystem_overrides(
exec_req.sandbox,
&exec_req.permission_profile,
sandbox_cwd,
exec_req.windows_sandbox_level,
)
}
.map_err(CodexErr::UnsupportedOperation)?;
Ok(exec_req)
windows_sandbox_workspace_roots.to_vec()
};
ExecRequest::from_sandbox_exec_request(request, options, windows_sandbox_workspace_roots)
}

pub(crate) async fn execute_exec_request(
Expand Down
43 changes: 39 additions & 4 deletions codex-rs/core/src/sandboxing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,19 @@ use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_network_proxy::NetworkProxy;
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::error::CodexErr;
use codex_protocol::exec_output::ExecToolCallOutput;
use codex_protocol::models::PermissionProfile;
pub use codex_protocol::models::SandboxPermissions;
use codex_sandboxing::SandboxExecRequest;
use codex_sandboxing::SandboxType;
use codex_sandboxing::WindowsSandboxFilesystemOverrides;
use codex_sandboxing::resolve_windows_elevated_filesystem_overrides;
use codex_sandboxing::resolve_windows_restricted_token_filesystem_overrides;
use codex_sandboxing::windows_sandbox_uses_elevated_backend;
use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_path_uri::PathUri;
use codex_utils_string::truncate_middle_with_token_budget;
use std::collections::HashMap;

#[derive(Debug)]
Expand Down Expand Up @@ -112,7 +117,7 @@ impl ExecRequest {
request: SandboxExecRequest,
options: ExecOptions,
windows_sandbox_workspace_roots: Vec<AbsolutePathBuf>,
) -> Self {
) -> Result<Self, CodexErr> {
let SandboxExecRequest {
command,
cwd,
Expand All @@ -131,6 +136,36 @@ impl ExecRequest {
expiration,
capture_policy,
} = options;
let windows_sandbox_filesystem_overrides = if sandbox == SandboxType::WindowsRestrictedToken
{
let sandbox_policy_cwd = windows_sandbox_policy_cwd
.to_abs_path()
.map_err(|err| CodexErr::InvalidRequest(format!("invalid sandbox cwd: {err}")))?;
let use_windows_elevated_backend =
windows_sandbox_uses_elevated_backend(windows_sandbox_level);
if use_windows_elevated_backend {
resolve_windows_elevated_filesystem_overrides(
sandbox,
&permission_profile,
&sandbox_policy_cwd,
use_windows_elevated_backend,
)
} else {
resolve_windows_restricted_token_filesystem_overrides(
sandbox,
&permission_profile,
&sandbox_policy_cwd,
windows_sandbox_level,
)
}
.map_err(|error| {
CodexErr::UnsupportedOperation(
truncate_middle_with_token_budget(&error, /*max_tokens*/ 900).0,
)
})?
} else {
None
};
let network_sandbox_policy = permission_profile.network_sandbox_policy();
if !network_sandbox_policy.is_enabled() {
env.insert(
Expand All @@ -142,7 +177,7 @@ impl ExecRequest {
if sandbox == SandboxType::MacosSeatbelt {
env.insert(CODEX_SANDBOX_ENV_VAR.to_string(), "seatbelt".to_string());
}
Self {
Ok(Self {
command,
cwd,
env,
Expand All @@ -157,13 +192,13 @@ impl ExecRequest {
windows_sandbox_level,
windows_sandbox_private_desktop,
permission_profile,
windows_sandbox_filesystem_overrides: None,
windows_sandbox_filesystem_overrides,
arg0,
exec_server_sandbox: None,
exec_server_enforce_managed_network: false,
exec_server_managed_network: None,
exec_server_network_proxy: None,
}
})
}
}

Expand Down
2 changes: 1 addition & 1 deletion codex-rs/core/src/tools/runtimes/shell/unix_escalation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@ impl CoreShellCommandExecutor {
exec_request,
options,
self.windows_sandbox_workspace_roots.clone(),
);
)?;
if let Some(network) = exec_request.network.as_ref() {
network
.apply_to_env_for_optional_environment(
Expand Down
13 changes: 6 additions & 7 deletions codex-rs/core/src/tools/sandboxing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,11 +453,7 @@ impl<'a> SandboxAttempt<'a> {
.iter()
.map(PathUri::to_abs_path)
.collect::<std::io::Result<Vec<_>>>()?;
Ok(crate::sandboxing::ExecRequest::from_sandbox_exec_request(
request,
options,
workspace_roots,
))
crate::sandboxing::ExecRequest::from_sandbox_exec_request(request, options, workspace_roots)
}

pub fn env_for_exec_server(
Expand Down Expand Up @@ -487,8 +483,11 @@ impl<'a> SandboxAttempt<'a> {
windows_sandbox_private_desktop: self.windows_sandbox_private_desktop,
})
.map_err(CodexErr::from)?;
let mut exec_request =
crate::sandboxing::ExecRequest::from_sandbox_exec_request(request, options, Vec::new());
let mut exec_request = crate::sandboxing::ExecRequest::from_sandbox_exec_request(
request,
options,
Vec::new(),
)?;
exec_request.exec_server_managed_network = managed_network;
if self.sandbox_requested {
exec_request.exec_server_sandbox = Some(FileSystemSandboxContext {
Expand Down
103 changes: 103 additions & 0 deletions codex-rs/core/src/tools/sandboxing_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use codex_network_proxy::ManagedNetworkSandboxContext;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
use codex_protocol::permissions::FileSystemSpecialPath;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::GranularApprovalConfig;
use codex_sandboxing::SandboxCommand;
use codex_sandboxing::SandboxManager;
Expand Down Expand Up @@ -202,6 +204,107 @@ fn deny_read_blocks_explicit_escalation_and_policy_bypass() {
);
}

#[test]
fn windows_sandbox_env_preserves_denied_reads_or_rejects_unsupported_backend() {
let temp_dir = tempfile::TempDir::new().expect("create sandbox workspace");
let cwd = AbsolutePathBuf::from_absolute_path(
dunce::canonicalize(temp_dir.path()).expect("canonicalize sandbox workspace"),
)
.expect("absolute sandbox workspace");
let denied_path = cwd.join("blocked");
std::fs::create_dir_all(denied_path.as_path()).expect("create denied directory");
let denied_path = AbsolutePathBuf::from_absolute_path(
dunce::canonicalize(denied_path.as_path()).expect("canonicalize denied directory"),
)
.expect("absolute denied directory");
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::Root,
},
access: FileSystemAccessMode::Read,
missing_path_behavior: None,
},
FileSystemSandboxEntry {
path: FileSystemPath::Special {
value: FileSystemSpecialPath::project_roots(/*subpath*/ None),
},
access: FileSystemAccessMode::Write,
missing_path_behavior: None,
},
FileSystemSandboxEntry {
path: FileSystemPath::Path {
path: denied_path.clone(),
},
access: FileSystemAccessMode::Deny,
missing_path_behavior: None,
},
]);
let permissions = codex_protocol::models::PermissionProfile::from_runtime_permissions(
&file_system_policy,
NetworkSandboxPolicy::Restricted,
);
let cwd_uri = PathUri::from_abs_path(&cwd);
let manager = SandboxManager::new();
let mut attempt = SandboxAttempt {
sandbox: SandboxType::WindowsRestrictedToken,
sandbox_requested: true,
permissions: &permissions,
exec_server_permissions: &permissions,
enforce_managed_network: false,
manager: &manager,
sandbox_cwd: &cwd_uri,
workspace_roots: std::slice::from_ref(&cwd_uri),
codex_linux_sandbox_exe: None,
use_legacy_landlock: false,
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Elevated,
windows_sandbox_private_desktop: false,
network_denial_cancellation_token: None,
network_proxy: None,
};
let command = || SandboxCommand {
program: "cmd.exe".into(),
args: vec!["/C".to_string(), "echo sandboxed".to_string()],
cwd: cwd_uri.clone(),
env: HashMap::new(),
managed_network: None,
additional_permissions: None,
};
let options = || crate::sandboxing::ExecOptions {
expiration: crate::exec::ExecExpiration::DefaultTimeout,
capture_policy: crate::exec::ExecCapturePolicy::ShellTool,
};

let request = attempt
.env_for(
command(),
options(),
/*network*/ None,
/*environment_id*/ None,
)
.expect("prepare elevated Windows sandbox request");
let overrides = request
.windows_sandbox_filesystem_overrides
.expect("elevated Windows sandbox should preserve deny-read overrides");
assert_eq!(overrides.additional_deny_read_paths, vec![denied_path]);
assert_eq!(request.windows_sandbox_workspace_roots, vec![cwd]);

attempt.windows_sandbox_level =
codex_protocol::config_types::WindowsSandboxLevel::RestrictedToken;
let error = attempt
.env_for(
command(),
options(),
/*network*/ None,
/*environment_id*/ None,
)
.expect_err("restricted-token Windows sandbox cannot enforce deny-read restrictions");
assert_eq!(
error.to_string(),
"unsupported operation: windows unelevated restricted-token sandbox cannot enforce deny-read restrictions directly; refusing to run unsandboxed"
);
}

#[test]
fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
let cwd: AbsolutePathBuf = std::env::current_dir()
Expand Down
Loading
Loading