mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
Separate executor sandbox selection from Windows sandbox levels (#45730)
## Why MXC is a sandbox implementation, not a restricted-token sandbox level. Executor requests need to represent that choice separately from `WindowsSandboxLevel`. ## What changed - Introduce `WindowsSandboxSelection` for executor sandbox contexts and remove `Mxc` from `WindowsSandboxLevel`. - Preserve the `windowsSandboxLevel` wire field and its serialized values for compatibility. - Share sandbox selection between executor process launches and filesystem helpers, and use the new selection in capability discovery and skill reads. - Disable Windows sandbox selection for executor paths that do not use Windows path conventions. ## Testing Extend coverage for MXC wire serialization, Windows skill-read sandbox checks, and capability discovery with distinct permissions. Exercise remote filesystem write restrictions with both restricted-token and MXC sandboxes, including rejection when native MXC is unavailable. GitOrigin-RevId: 266211377bcb138a0dc75861e9ff2225fa37a53d
This commit is contained in:
@@ -4931,7 +4931,7 @@ impl ThreadRequestProcessor {
|
||||
serde_json::json!("unelevated"),
|
||||
);
|
||||
}
|
||||
WindowsSandboxLevel::Disabled | WindowsSandboxLevel::Mxc => {}
|
||||
WindowsSandboxLevel::Disabled => {}
|
||||
}
|
||||
}
|
||||
let request_overrides = if cli_overrides.is_empty() {
|
||||
|
||||
@@ -303,9 +303,7 @@ fn determine_windows_sandbox_readiness_from_state(
|
||||
) -> WindowsSandboxReadinessResponse {
|
||||
let status = match windows_sandbox_level {
|
||||
WindowsSandboxLevel::Disabled => WindowsSandboxReadiness::NotConfigured,
|
||||
WindowsSandboxLevel::RestrictedToken | WindowsSandboxLevel::Mxc => {
|
||||
WindowsSandboxReadiness::Ready
|
||||
}
|
||||
WindowsSandboxLevel::RestrictedToken => WindowsSandboxReadiness::Ready,
|
||||
WindowsSandboxLevel::Elevated => {
|
||||
if sandbox_setup_is_complete {
|
||||
WindowsSandboxReadiness::Ready
|
||||
|
||||
@@ -25,7 +25,7 @@ pub fn prepare_windows_sandbox_config(
|
||||
let selected_mode = configured_mode.or(match feature_level {
|
||||
WindowsSandboxLevel::Elevated => Some(WindowsSandboxModeToml::Elevated),
|
||||
WindowsSandboxLevel::RestrictedToken => Some(WindowsSandboxModeToml::Unelevated),
|
||||
WindowsSandboxLevel::Disabled | WindowsSandboxLevel::Mxc => None,
|
||||
WindowsSandboxLevel::Disabled => None,
|
||||
});
|
||||
apply_requirement_constrained_value("windows.sandbox", selected_mode, constraint, warnings)?;
|
||||
let effective_mode = *constraint.get();
|
||||
|
||||
@@ -79,7 +79,6 @@ fn permission_profile_sandbox_tag(
|
||||
}
|
||||
if cfg!(target_os = "windows") {
|
||||
match windows_sandbox_level {
|
||||
WindowsSandboxLevel::Mxc => return "windows_mxc",
|
||||
WindowsSandboxLevel::Elevated => return "windows_elevated",
|
||||
WindowsSandboxLevel::Disabled | WindowsSandboxLevel::RestrictedToken => {}
|
||||
}
|
||||
|
||||
@@ -8970,7 +8970,11 @@ async fn capability_discovery_uses_environment_permission_profile() {
|
||||
workspace_roots: environment.workspace_roots().to_vec(),
|
||||
user_home_dir: environment.user_home_dir.clone(),
|
||||
temporary_directories: environment.temporary_directories.clone(),
|
||||
windows_sandbox_level: WindowsSandboxLevel::Elevated,
|
||||
windows_sandbox_selection: if cfg!(windows) {
|
||||
codex_file_system::WindowsSandboxSelection::Elevated
|
||||
} else {
|
||||
codex_file_system::WindowsSandboxSelection::Disabled
|
||||
},
|
||||
windows_sandbox_private_desktop: false,
|
||||
windows_sandbox_proxy_settings_mode: None,
|
||||
use_legacy_landlock: true,
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::exec_policy::AllowPrefixRules;
|
||||
use crate::shell_snapshot::ShellSnapshot;
|
||||
use crate::shell_snapshot::ShellSnapshotFile;
|
||||
use crate::shell_snapshot::ShellSnapshotSandbox;
|
||||
use crate::tools::sandboxing::executor_windows_sandbox_level;
|
||||
use crate::tools::sandboxing::executor_windows_sandbox_selection;
|
||||
use arc_swap::ArcSwap;
|
||||
use codex_core_plugins::PluginCommandAttribution;
|
||||
use codex_core_plugins::ResolvedPluginMetricsOperation;
|
||||
@@ -227,7 +227,7 @@ impl TurnEnvironment {
|
||||
workspace_roots: self.workspace_roots().to_vec(),
|
||||
user_home_dir: self.user_home_dir.clone(),
|
||||
temporary_directories: self.temporary_directories.clone(),
|
||||
windows_sandbox_level: executor_windows_sandbox_level(
|
||||
windows_sandbox_selection: executor_windows_sandbox_selection(
|
||||
config.windows_sandbox_level,
|
||||
self.cwd(),
|
||||
),
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::tools::sandboxing::Sandboxable;
|
||||
use crate::tools::sandboxing::ToolCtx;
|
||||
use crate::tools::sandboxing::ToolError;
|
||||
use crate::tools::sandboxing::ToolRuntime;
|
||||
use crate::tools::sandboxing::executor_windows_sandbox_level;
|
||||
use crate::tools::sandboxing::executor_windows_sandbox_selection;
|
||||
use codex_apply_patch::AppliedPatchDelta;
|
||||
use codex_apply_patch::ApplyPatchAction;
|
||||
use codex_apply_patch::ApplyPatchOptions;
|
||||
@@ -102,7 +102,7 @@ impl ApplyPatchRuntime {
|
||||
workspace_roots: attempt.workspace_roots.to_vec(),
|
||||
user_home_dir: req.turn_environment.user_home_dir.clone(),
|
||||
temporary_directories: None,
|
||||
windows_sandbox_level: executor_windows_sandbox_level(
|
||||
windows_sandbox_selection: executor_windows_sandbox_selection(
|
||||
attempt.windows_sandbox_level,
|
||||
attempt.sandbox_cwd,
|
||||
),
|
||||
|
||||
@@ -276,8 +276,12 @@ async fn file_system_sandbox_context_preserves_executor_workspace_permissions()
|
||||
Some(codex_utils_path_uri::PathUri::from_abs_path(&path))
|
||||
);
|
||||
assert_eq!(
|
||||
sandbox.windows_sandbox_level,
|
||||
WindowsSandboxLevel::RestrictedToken
|
||||
sandbox.windows_sandbox_selection,
|
||||
if cfg!(windows) {
|
||||
codex_file_system::WindowsSandboxSelection::RestrictedToken
|
||||
} else {
|
||||
codex_file_system::WindowsSandboxSelection::Disabled
|
||||
}
|
||||
);
|
||||
assert_eq!(sandbox.windows_sandbox_private_desktop, true);
|
||||
assert_eq!(sandbox.use_legacy_landlock, true);
|
||||
@@ -349,7 +353,7 @@ async fn file_system_sandbox_context_respects_sandbox_request() {
|
||||
workspace_roots: vec![cwd],
|
||||
user_home_dir: Some(user_home_dir),
|
||||
temporary_directories: None,
|
||||
windows_sandbox_level: WindowsSandboxLevel::RestrictedToken,
|
||||
windows_sandbox_selection: codex_file_system::WindowsSandboxSelection::RestrictedToken,
|
||||
windows_sandbox_private_desktop: false,
|
||||
windows_sandbox_proxy_settings_mode: None,
|
||||
use_legacy_landlock: false,
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::state::SessionServices;
|
||||
use crate::tools::hook_names::HookToolName;
|
||||
use crate::tools::network_approval::NetworkApprovalSpec;
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
use codex_file_system::WindowsSandboxSelection;
|
||||
use codex_network_proxy::NetworkProxy;
|
||||
use codex_protocol::approvals::ExecPolicyAmendment;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
@@ -417,6 +418,17 @@ pub(crate) fn executor_windows_sandbox_level(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn executor_windows_sandbox_selection(
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
cwd: &PathUri,
|
||||
) -> WindowsSandboxSelection {
|
||||
if cwd.infer_path_convention() == Some(PathConvention::Windows) {
|
||||
executor_windows_sandbox_level(windows_sandbox_level, cwd).into()
|
||||
} else {
|
||||
WindowsSandboxSelection::Disabled
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> SandboxAttempt<'a> {
|
||||
/// Whether this attempt bypasses sandboxing required by its ambient policy.
|
||||
/// Use the requested policy, not the controller's wrapper, for remote executors.
|
||||
@@ -513,7 +525,7 @@ impl<'a> SandboxAttempt<'a> {
|
||||
workspace_roots: self.workspace_roots.to_vec(),
|
||||
user_home_dir: None,
|
||||
temporary_directories: None,
|
||||
windows_sandbox_level: executor_windows_sandbox_level(
|
||||
windows_sandbox_selection: executor_windows_sandbox_selection(
|
||||
self.windows_sandbox_level,
|
||||
self.sandbox_cwd,
|
||||
),
|
||||
|
||||
@@ -373,10 +373,10 @@ fn exec_server_env_keeps_command_native_and_carries_sandbox_context() {
|
||||
workspace_roots: vec![cwd_uri.clone()],
|
||||
user_home_dir: None,
|
||||
temporary_directories: None,
|
||||
windows_sandbox_level: if cfg!(windows) {
|
||||
codex_protocol::config_types::WindowsSandboxLevel::RestrictedToken
|
||||
windows_sandbox_selection: if cfg!(windows) {
|
||||
codex_file_system::WindowsSandboxSelection::RestrictedToken
|
||||
} else {
|
||||
codex_protocol::config_types::WindowsSandboxLevel::Disabled
|
||||
codex_file_system::WindowsSandboxSelection::Disabled
|
||||
},
|
||||
windows_sandbox_private_desktop: false,
|
||||
windows_sandbox_proxy_settings_mode: None,
|
||||
|
||||
@@ -27,7 +27,7 @@ use crate::exec_env::inject_session_env;
|
||||
use crate::session::session::Session;
|
||||
use crate::session::step_context::StepContext;
|
||||
use crate::shell::ShellType;
|
||||
use crate::tools::sandboxing::executor_windows_sandbox_level;
|
||||
use crate::tools::sandboxing::executor_windows_sandbox_selection;
|
||||
|
||||
impl Session {
|
||||
pub(crate) fn prewarm_shell_snapshots(
|
||||
@@ -97,7 +97,7 @@ impl Session {
|
||||
workspace_roots: environment.workspace_roots().to_vec(),
|
||||
user_home_dir: None,
|
||||
temporary_directories: None,
|
||||
windows_sandbox_level: executor_windows_sandbox_level(
|
||||
windows_sandbox_selection: executor_windows_sandbox_selection(
|
||||
environment.config().windows_sandbox_level,
|
||||
environment.cwd(),
|
||||
),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Reviews terminal input against retained launch permissions and current policy.
|
||||
//! Permission snapshots stay host-owned; neither an earlier approval nor a policy
|
||||
//! change alters the sandbox of an already-running process. Native launches keep
|
||||
//! their configured Windows sandbox level; executor launches use executor defaults.
|
||||
//! their configured Windows sandbox selection; executor launches use executor defaults.
|
||||
|
||||
use super::ProcessEntry;
|
||||
use super::UnifiedExecContext;
|
||||
@@ -56,7 +56,7 @@ impl TerminalPolicy {
|
||||
if matches!(sandbox_source, TerminalSandboxSource::Native) {
|
||||
// The filesystem helper applies executor defaults, but native process
|
||||
// launches honor Disabled. Preserve it so later enablement is detected.
|
||||
sandbox.windows_sandbox_level = environment.config().windows_sandbox_level;
|
||||
sandbox.windows_sandbox_selection = environment.config().windows_sandbox_level.into();
|
||||
}
|
||||
Self {
|
||||
sandbox,
|
||||
|
||||
@@ -920,6 +920,7 @@ mod tests {
|
||||
use super::ProcessSandboxType;
|
||||
use super::ShellInfo;
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
use codex_file_system::WindowsSandboxSelection;
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::NetworkProxyAuditMetadata;
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
@@ -1274,9 +1275,11 @@ mod tests {
|
||||
let mut sandbox =
|
||||
FileSystemSandboxContext::from_permission_profile_with_cwd(permissions, cwd.clone());
|
||||
sandbox.user_home_dir = Some(cwd.clone());
|
||||
sandbox.windows_sandbox_selection = WindowsSandboxSelection::Mxc;
|
||||
|
||||
let serialized = serde_json::to_value(&sandbox).expect("serialize sandbox");
|
||||
|
||||
assert_eq!(serialized["windowsSandboxLevel"], "mxc");
|
||||
assert_eq!(
|
||||
serialized["userHomeDir"],
|
||||
serde_json::json!(cwd.to_string())
|
||||
|
||||
@@ -78,9 +78,7 @@ async fn discover_root(
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if sandbox.is_some_and(|context| {
|
||||
context.should_run_in_sandbox()
|
||||
&& context.windows_sandbox_level
|
||||
== codex_protocol::config_types::WindowsSandboxLevel::Disabled
|
||||
context.should_run_in_sandbox() && !context.windows_sandbox_is_requested()
|
||||
}) {
|
||||
discovery.error = Some("filesystem sandbox is unavailable on this executor".to_string());
|
||||
return discovery;
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use codex_exec_server_protocol::JSONRPCErrorError;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
@@ -16,7 +17,6 @@ use codex_sandboxing::SandboxExecRequest;
|
||||
use codex_sandboxing::SandboxManager;
|
||||
use codex_sandboxing::SandboxTransformRequest;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::SandboxablePreference;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
use codex_utils_absolute_path::canonicalize_preserving_symlinks;
|
||||
@@ -144,10 +144,10 @@ impl FileSystemSandboxRunner {
|
||||
let sandbox_manager = sandbox_manager.with_allowed_symlinked_codex_home(
|
||||
self.runtime_paths.allowed_symlinked_codex_home.clone(),
|
||||
);
|
||||
let sandbox = sandbox_manager.select_initial(
|
||||
let (sandbox, windows_sandbox_level) = crate::sandbox_selection::select_sandbox(
|
||||
&sandbox_manager,
|
||||
permission_profile,
|
||||
SandboxablePreference::Require,
|
||||
sandbox_context.windows_sandbox_level,
|
||||
sandbox_context,
|
||||
/*has_managed_network_requirements*/ false,
|
||||
);
|
||||
if sandbox == SandboxType::None {
|
||||
@@ -182,7 +182,8 @@ impl FileSystemSandboxRunner {
|
||||
self.runtime_paths.codex_linux_sandbox_exe.as_deref()
|
||||
},
|
||||
use_legacy_landlock: sandbox_context.use_legacy_landlock,
|
||||
windows_sandbox_level: sandbox_context.windows_sandbox_level,
|
||||
windows_sandbox_level: windows_sandbox_level
|
||||
.unwrap_or(WindowsSandboxLevel::Disabled),
|
||||
windows_sandbox_private_desktop: sandbox_context
|
||||
.windows_sandbox_private_desktop,
|
||||
},
|
||||
@@ -721,8 +722,8 @@ mod tests {
|
||||
"filesystem sandbox cannot be enforced on this executor"
|
||||
);
|
||||
crate::FileSystemSandboxContext {
|
||||
windows_sandbox_level:
|
||||
codex_protocol::config_types::WindowsSandboxLevel::RestrictedToken,
|
||||
windows_sandbox_selection:
|
||||
codex_file_system::WindowsSandboxSelection::RestrictedToken,
|
||||
..sandbox_context
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@ mod resolved_capability;
|
||||
mod rpc;
|
||||
mod rpc_server_requests;
|
||||
mod runtime_paths;
|
||||
mod sandbox_selection;
|
||||
mod sandboxed_file_open;
|
||||
mod sandboxed_file_system;
|
||||
mod server;
|
||||
@@ -86,6 +87,7 @@ pub use codex_file_system::WalkEntryKind;
|
||||
pub use codex_file_system::WalkError;
|
||||
pub use codex_file_system::WalkOptions;
|
||||
pub use codex_file_system::WalkOutcome;
|
||||
pub use codex_file_system::WindowsSandboxSelection;
|
||||
pub use codex_file_system::WriteFileOptions;
|
||||
pub use codex_protocol::shell_environment::CODEX_EXEC_SERVER_NOISE_AUTH_TOKEN_ENV_VAR;
|
||||
pub use environment::CODEX_EXEC_SERVER_NOISE_CHATGPT_ACCOUNT_ID_ENV_VAR;
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::process_telemetry::ProcessTelemetry;
|
||||
use codex_exec_server_protocol::JSONRPCErrorError;
|
||||
use codex_file_system::WindowsSandboxSelection;
|
||||
use codex_network_proxy::CUSTOM_CA_ENV_KEYS;
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::ManagedProxyRouting;
|
||||
@@ -22,7 +23,6 @@ use codex_sandboxing::SandboxDirectSpawnTransformRequest;
|
||||
use codex_sandboxing::SandboxManager;
|
||||
use codex_sandboxing::SandboxTransformRequest;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::SandboxablePreference;
|
||||
use codex_sandboxing::WindowsSandboxFilesystemOverrides;
|
||||
use codex_sandboxing::WindowsSandboxProxySettingsMode;
|
||||
use codex_sandboxing::WindowsSandboxSpawnRequest;
|
||||
@@ -39,6 +39,7 @@ use crate::ExecServerRuntimePaths;
|
||||
use crate::protocol::ExecParams;
|
||||
use crate::rpc::internal_error;
|
||||
use crate::rpc::invalid_params;
|
||||
use crate::sandbox_selection::select_sandbox;
|
||||
|
||||
pub(crate) struct PreparedExecRequest {
|
||||
pub(crate) command: Vec<String>,
|
||||
@@ -53,7 +54,7 @@ pub(crate) struct PreparedExecRequest {
|
||||
struct PreparedWindowsSandboxRequest {
|
||||
permission_profile: PermissionProfile,
|
||||
workspace_roots: Vec<AbsolutePathBuf>,
|
||||
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
proxy_enforced: bool,
|
||||
network_proxy_restricting_sid: Option<String>,
|
||||
proxy_settings_mode: WindowsSandboxProxySettingsMode,
|
||||
@@ -87,7 +88,7 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
|
||||
telemetry: &ProcessTelemetry,
|
||||
) -> Result<PreparedExecRequest, JSONRPCErrorError> {
|
||||
if let Some(sandbox) = params.sandbox.as_ref()
|
||||
&& sandbox.windows_sandbox_level == codex_protocol::config_types::WindowsSandboxLevel::Mxc
|
||||
&& sandbox.windows_sandbox_selection == WindowsSandboxSelection::Mxc
|
||||
{
|
||||
if params.arg0.is_some() || sandbox.windows_sandbox_private_desktop {
|
||||
return Err(invalid_params(
|
||||
@@ -120,11 +121,9 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
|
||||
prepare_managed_network(
|
||||
params.managed_network.as_ref(),
|
||||
network_proxy,
|
||||
if params
|
||||
.sandbox
|
||||
.as_ref()
|
||||
.is_some_and(|sandbox| sandbox.windows_sandbox_level == WindowsSandboxLevel::Mxc)
|
||||
{
|
||||
if params.sandbox.as_ref().is_some_and(|sandbox| {
|
||||
sandbox.windows_sandbox_selection == WindowsSandboxSelection::Mxc
|
||||
}) {
|
||||
ManagedProxyRouting::DedicatedListeners
|
||||
} else {
|
||||
ManagedProxyRouting::SharedIngress
|
||||
@@ -213,10 +212,10 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
|
||||
#[cfg(target_os = "macos")]
|
||||
let sandbox_manager = sandbox_manager
|
||||
.with_allowed_symlinked_codex_home(runtime_paths.allowed_symlinked_codex_home.clone());
|
||||
let sandbox = sandbox_manager.select_initial(
|
||||
let (sandbox, windows_sandbox_level) = select_sandbox(
|
||||
&sandbox_manager,
|
||||
&permissions,
|
||||
SandboxablePreference::Require,
|
||||
sandbox_context.windows_sandbox_level,
|
||||
sandbox_context,
|
||||
params.enforce_managed_network,
|
||||
);
|
||||
if sandbox == SandboxType::None {
|
||||
@@ -272,7 +271,7 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
|
||||
runtime_paths.codex_linux_sandbox_exe.as_deref()
|
||||
},
|
||||
use_legacy_landlock: sandbox_context.use_legacy_landlock,
|
||||
windows_sandbox_level: sandbox_context.windows_sandbox_level,
|
||||
windows_sandbox_level: windows_sandbox_level.unwrap_or(WindowsSandboxLevel::Disabled),
|
||||
windows_sandbox_private_desktop: sandbox_context.windows_sandbox_private_desktop,
|
||||
},
|
||||
};
|
||||
@@ -284,10 +283,12 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
|
||||
}
|
||||
.map_err(|err| invalid_params(format!("failed to prepare process sandbox: {err}")))?;
|
||||
let windows_sandbox = if sandbox == SandboxType::WindowsRestrictedToken {
|
||||
let windows_sandbox_level = windows_sandbox_level.ok_or_else(|| {
|
||||
invalid_params("restricted token sandbox requires a sandbox level".to_string())
|
||||
})?;
|
||||
request.arg0 = params.arg0.clone();
|
||||
let proxy_enforced = params.enforce_managed_network;
|
||||
let use_elevated =
|
||||
windows_sandbox_uses_elevated_backend(sandbox_context.windows_sandbox_level);
|
||||
let use_elevated = windows_sandbox_uses_elevated_backend(windows_sandbox_level);
|
||||
let filesystem_overrides = if use_elevated {
|
||||
resolve_windows_elevated_filesystem_overrides(
|
||||
sandbox,
|
||||
@@ -300,14 +301,14 @@ pub(crate) async fn prepare_exec_request_with_telemetry(
|
||||
sandbox,
|
||||
&permissions,
|
||||
&native_sandbox_policy_cwd,
|
||||
sandbox_context.windows_sandbox_level,
|
||||
windows_sandbox_level,
|
||||
)
|
||||
}
|
||||
.map_err(|err| invalid_params(format!("failed to prepare process sandbox: {err}")))?;
|
||||
Some(PreparedWindowsSandboxRequest {
|
||||
permission_profile: permissions,
|
||||
workspace_roots: native_workspace_roots,
|
||||
windows_sandbox_level: sandbox_context.windows_sandbox_level,
|
||||
windows_sandbox_level,
|
||||
proxy_enforced,
|
||||
network_proxy_restricting_sid,
|
||||
proxy_settings_mode: windows_sandbox_proxy_settings_mode,
|
||||
|
||||
@@ -4,6 +4,8 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use codex_exec_server_protocol::JSONRPCErrorError;
|
||||
#[cfg(unix)]
|
||||
use codex_file_system::WindowsSandboxSelection;
|
||||
#[cfg(target_os = "macos")]
|
||||
use codex_network_proxy::ManagedNetworkSandboxContext;
|
||||
use codex_network_proxy::NetworkPolicyAuditObserver;
|
||||
@@ -135,8 +137,7 @@ async fn sandbox_request_wraps_native_argv_on_executor() {
|
||||
);
|
||||
|
||||
let mut params = params;
|
||||
params.sandbox.as_mut().unwrap().windows_sandbox_level =
|
||||
codex_protocol::config_types::WindowsSandboxLevel::Mxc;
|
||||
params.sandbox.as_mut().unwrap().windows_sandbox_selection = WindowsSandboxSelection::Mxc;
|
||||
let error = prepare_exec_request(
|
||||
¶ms,
|
||||
HashMap::new(),
|
||||
@@ -620,7 +621,7 @@ async fn managed_network_honors_windows_sandbox_level(windows_sandbox_level: Win
|
||||
permissions.clone(),
|
||||
cwd_uri.clone(),
|
||||
);
|
||||
sandbox.windows_sandbox_level = windows_sandbox_level;
|
||||
sandbox.windows_sandbox_selection = windows_sandbox_level.into();
|
||||
sandbox.windows_sandbox_proxy_settings_mode =
|
||||
Some(codex_sandboxing::WindowsSandboxProxySettingsMode::Preserve);
|
||||
let proxy_config = RemoteNetworkProxyConfig::from_effective_config(&NetworkProxyConfig {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//! Resolves an executor sandbox context to a concrete local sandbox implementation.
|
||||
|
||||
use codex_file_system::FileSystemSandboxContext;
|
||||
use codex_file_system::WindowsSandboxSelection;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_sandboxing::SandboxManager;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_sandboxing::SandboxablePreference;
|
||||
|
||||
pub(crate) fn select_sandbox(
|
||||
manager: &SandboxManager,
|
||||
permission_profile: &PermissionProfile,
|
||||
sandbox_context: &FileSystemSandboxContext,
|
||||
has_managed_network_requirements: bool,
|
||||
) -> (SandboxType, Option<WindowsSandboxLevel>) {
|
||||
let windows_sandbox_level = match sandbox_context.windows_sandbox_selection {
|
||||
WindowsSandboxSelection::Disabled => WindowsSandboxLevel::Disabled,
|
||||
WindowsSandboxSelection::RestrictedToken => WindowsSandboxLevel::RestrictedToken,
|
||||
WindowsSandboxSelection::Elevated => WindowsSandboxLevel::Elevated,
|
||||
WindowsSandboxSelection::Mxc => return (SandboxType::WindowsMxc, None),
|
||||
};
|
||||
let sandbox_type = manager.select_initial(
|
||||
permission_profile,
|
||||
SandboxablePreference::Require,
|
||||
windows_sandbox_level,
|
||||
has_managed_network_requirements,
|
||||
);
|
||||
(sandbox_type, Some(windows_sandbox_level))
|
||||
}
|
||||
@@ -3,7 +3,6 @@ use std::io;
|
||||
use base64::Engine as _;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use codex_exec_server_protocol::JSONRPCErrorError;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
|
||||
use crate::CapabilityRootsDiscoverParams;
|
||||
use crate::CapabilityRootsDiscoverResponse;
|
||||
@@ -81,8 +80,7 @@ impl FileSystemHandler {
|
||||
.and_then(|root| root.sandbox.as_ref())
|
||||
.filter(|sandbox| {
|
||||
sandbox.should_run_in_sandbox()
|
||||
&& (!cfg!(target_os = "windows")
|
||||
|| sandbox.windows_sandbox_level != WindowsSandboxLevel::Disabled)
|
||||
&& (!cfg!(target_os = "windows") || sandbox.windows_sandbox_is_requested())
|
||||
&& params
|
||||
.roots
|
||||
.iter()
|
||||
|
||||
@@ -12,20 +12,15 @@ use codex_exec_server::CapabilityRootsDiscoverResponse;
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_exec_server::InitializeParams;
|
||||
use codex_exec_server::InitializeResponse;
|
||||
use codex_exec_server::WindowsSandboxSelection;
|
||||
use codex_exec_server_protocol::CapabilityRootDiscoverRequest;
|
||||
use codex_exec_server_protocol::JSONRPCMessage;
|
||||
use codex_exec_server_protocol::JSONRPCResponse;
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::FileSystemSandboxEntry;
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::FileSystemSandboxPolicy;
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::permissions::NetworkSandboxPolicy;
|
||||
#[cfg(unix)]
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use common::exec_server::exec_server;
|
||||
@@ -188,10 +183,11 @@ async fn discovers_cursor_plugin_without_reading_default_mcp_for_inline_servers(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn sandboxed_discovery_batches_roots_without_combining_different_permissions()
|
||||
-> anyhow::Result<()> {
|
||||
#[cfg(windows)]
|
||||
crate::skip_if_mxc_unavailable!(Ok(()));
|
||||
let workspace = tempfile::tempdir()?;
|
||||
let first_root = workspace.path().join("first");
|
||||
let second_root = workspace.path().join("second");
|
||||
@@ -216,6 +212,7 @@ async fn sandboxed_discovery_batches_roots_without_combining_different_permissio
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
|
||||
workspace_uri,
|
||||
);
|
||||
let shared_sandbox = with_native_sandbox(shared_sandbox);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let fake_bwrap_directory = tempfile::tempdir()?;
|
||||
@@ -338,6 +335,7 @@ async fn sandboxed_discovery_batches_roots_without_combining_different_permissio
|
||||
),
|
||||
first_uri.clone(),
|
||||
);
|
||||
let first_only_sandbox = with_native_sandbox(first_only_sandbox);
|
||||
let read_second_root = FileSystemSandboxEntry::new(
|
||||
AbsolutePathBuf::from_absolute_path(&second_root)?.into(),
|
||||
FileSystemAccessMode::Read,
|
||||
@@ -350,6 +348,7 @@ async fn sandboxed_discovery_batches_roots_without_combining_different_permissio
|
||||
),
|
||||
second_uri.clone(),
|
||||
);
|
||||
let second_only_sandbox = with_native_sandbox(second_only_sandbox);
|
||||
let response = discover_roots(
|
||||
&mut server,
|
||||
vec![
|
||||
@@ -546,3 +545,10 @@ fn write_file(path: &std::path::Path, contents: &str) -> anyhow::Result<()> {
|
||||
std::fs::write(path, contents)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn with_native_sandbox(mut sandbox: FileSystemSandboxContext) -> FileSystemSandboxContext {
|
||||
if cfg!(windows) {
|
||||
sandbox.windows_sandbox_selection = WindowsSandboxSelection::Mxc;
|
||||
}
|
||||
sandbox
|
||||
}
|
||||
|
||||
@@ -32,6 +32,16 @@ pub(crate) const SYSTEM_PROXY_URL_ENV: &str = "CODEX_EXEC_SERVER_TEST_SYSTEM_PRO
|
||||
const CODEX_WINDOWS_SANDBOX_ARG1: &str = "--run-as-windows-sandbox";
|
||||
const DELAYED_OUTPUT_AFTER_EXIT_CHILD_ARG: &str = "--codex-test-delayed-output-after-exit-child";
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! skip_if_mxc_unavailable {
|
||||
($return_value:expr $(,)?) => {{
|
||||
if !codex_sandboxing::windows_mxc_available() {
|
||||
eprintln!("skipping test: native MXC is unavailable on this host");
|
||||
return $return_value;
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[ctor]
|
||||
pub static TEST_BINARY_DISPATCH_GUARD: Option<TestBinaryDispatchGuard> = {
|
||||
let guard = configure_test_binary_dispatch("codex-exec-server-tests", |exe_name, argv1| {
|
||||
|
||||
@@ -27,6 +27,8 @@ use codex_exec_server::ShellInfo;
|
||||
#[cfg(unix)]
|
||||
use codex_exec_server::ShellSnapshotRequest;
|
||||
use codex_exec_server::StartedExecProcess;
|
||||
#[cfg(any(unix, windows))]
|
||||
use codex_exec_server::WindowsSandboxSelection;
|
||||
use codex_exec_server::WriteStatus;
|
||||
#[cfg(unix)]
|
||||
use codex_network_proxy::NetworkProxyConfig;
|
||||
@@ -36,7 +38,6 @@ use codex_network_proxy::RemoteNetworkProxyConfig;
|
||||
use codex_network_proxy::RemoteNetworkProxyLaunchConfig;
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::config_types::ShellEnvironmentPolicyInherit;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
#[cfg(unix)]
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
#[cfg(unix)]
|
||||
@@ -104,10 +105,6 @@ async fn create_process_context(use_remote: bool) -> Result<ProcessContext> {
|
||||
}
|
||||
}
|
||||
|
||||
fn selected_windows_sandbox_available(windows_sandbox_level: WindowsSandboxLevel) -> bool {
|
||||
windows_sandbox_level != WindowsSandboxLevel::Mxc || codex_sandboxing::windows_mxc_available()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn codex_home_symlink_opt_out_respects_host_config_and_scope() -> Result<()> {
|
||||
@@ -1295,13 +1292,11 @@ async fn assert_exec_process_write_then_read_without_tty(use_remote: bool) -> Re
|
||||
}
|
||||
|
||||
async fn assert_remote_windows_sandbox_process_write(
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
expected_sandbox_type: codex_sandboxing::SandboxType,
|
||||
tty: bool,
|
||||
) -> Result<()> {
|
||||
if !selected_windows_sandbox_available(windows_sandbox_level) {
|
||||
eprintln!("skipping MXC enforcement test: native MXC is unavailable on this host");
|
||||
return Ok(());
|
||||
if expected_sandbox_type == codex_sandboxing::SandboxType::WindowsMxc {
|
||||
crate::skip_if_mxc_unavailable!(Ok(()));
|
||||
}
|
||||
let context = create_process_context(/*use_remote*/ true).await?;
|
||||
let workspace = TempDir::new()?;
|
||||
@@ -1311,7 +1306,19 @@ async fn assert_remote_windows_sandbox_process_write(
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
cwd.clone(),
|
||||
)?;
|
||||
sandbox.windows_sandbox_level = windows_sandbox_level;
|
||||
match expected_sandbox_type {
|
||||
codex_sandboxing::SandboxType::WindowsRestrictedToken => {
|
||||
sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken;
|
||||
}
|
||||
codex_sandboxing::SandboxType::WindowsMxc => {
|
||||
sandbox.windows_sandbox_selection = WindowsSandboxSelection::Mxc;
|
||||
}
|
||||
codex_sandboxing::SandboxType::None
|
||||
| codex_sandboxing::SandboxType::MacosSeatbelt
|
||||
| codex_sandboxing::SandboxType::LinuxSeccomp => {
|
||||
anyhow::bail!("expected a Windows sandbox type")
|
||||
}
|
||||
}
|
||||
|
||||
let session = match context
|
||||
.backend
|
||||
@@ -1767,19 +1774,16 @@ async fn exec_process_write_then_read_without_tty(use_remote: bool) -> Result<()
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
codex_sandboxing::SandboxType::WindowsRestrictedToken,
|
||||
false;
|
||||
"restricted_token"
|
||||
)]
|
||||
#[test_case(
|
||||
WindowsSandboxLevel::Mxc,
|
||||
codex_sandboxing::SandboxType::WindowsMxc,
|
||||
false;
|
||||
"mxc_pipe"
|
||||
)]
|
||||
#[test_case(
|
||||
WindowsSandboxLevel::Mxc,
|
||||
codex_sandboxing::SandboxType::WindowsMxc,
|
||||
true;
|
||||
"mxc_conpty"
|
||||
@@ -1788,12 +1792,10 @@ async fn exec_process_write_then_read_without_tty(use_remote: bool) -> Result<()
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[serial_test::serial(remote_exec_server)]
|
||||
async fn remote_windows_sandbox_process_accepts_process_write(
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
expected_sandbox_type: codex_sandboxing::SandboxType,
|
||||
tty: bool,
|
||||
) -> Result<()> {
|
||||
assert_remote_windows_sandbox_process_write(windows_sandbox_level, expected_sandbox_type, tty)
|
||||
.await
|
||||
assert_remote_windows_sandbox_process_write(expected_sandbox_type, tty).await
|
||||
}
|
||||
|
||||
#[test_case(false ; "local")]
|
||||
|
||||
@@ -14,10 +14,7 @@ use pretty_assertions::assert_eq;
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[serial_test::serial(remote_exec_server)]
|
||||
async fn mxc_tmpdir_uses_command_environment_over_rpc() -> Result<()> {
|
||||
if !selected_windows_sandbox_available(WindowsSandboxLevel::Mxc) {
|
||||
eprintln!("skipping MXC enforcement test: native MXC is unavailable on this host");
|
||||
return Ok(());
|
||||
}
|
||||
crate::skip_if_mxc_unavailable!(Ok(()));
|
||||
let root = TempDir::new()?;
|
||||
let command_temp = root.path().join("command temp");
|
||||
let server_temp = root.path().join("server temp");
|
||||
@@ -53,7 +50,7 @@ async fn mxc_tmpdir_uses_command_environment_over_rpc() -> Result<()> {
|
||||
PermissionProfile::from_runtime_permissions(&fs, NetworkSandboxPolicy::Restricted),
|
||||
cwd.clone(),
|
||||
);
|
||||
sandbox.windows_sandbox_level = WindowsSandboxLevel::Mxc;
|
||||
sandbox.windows_sandbox_selection = codex_exec_server::WindowsSandboxSelection::Mxc;
|
||||
let command_temp = command_temp.to_string_lossy().into_owned();
|
||||
let started = environment
|
||||
.get_exec_backend()
|
||||
|
||||
@@ -7,7 +7,7 @@ use codex_exec_server::ExecServerRuntimePaths;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_exec_server::LocalFileSystem;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_exec_server::WindowsSandboxSelection;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::permissions::FileSystemAccessMode;
|
||||
use codex_protocol::permissions::FileSystemPath;
|
||||
@@ -141,7 +141,7 @@ pub(crate) fn workspace_write_sandbox(
|
||||
PermissionProfile::from_runtime_permissions(&policy, NetworkSandboxPolicy::Restricted),
|
||||
PathUri::from_abs_path(&writable_root),
|
||||
);
|
||||
sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken;
|
||||
sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken;
|
||||
sandbox
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ fn sandbox_context(mut entries: Vec<FileSystemSandboxEntry>) -> FileSystemSandbo
|
||||
),
|
||||
);
|
||||
if cfg!(windows) {
|
||||
sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken;
|
||||
sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken;
|
||||
}
|
||||
sandbox
|
||||
}
|
||||
|
||||
@@ -20,9 +20,10 @@ use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_exec_server::GetMetadataOptions;
|
||||
use codex_exec_server::ReadFileOptions;
|
||||
use codex_exec_server::RemoveOptions;
|
||||
use codex_exec_server::WindowsSandboxSelection;
|
||||
use codex_exec_server::WriteFileOptions;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_sandboxing::SandboxType;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use futures::TryStreamExt;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -346,8 +347,12 @@ async fn file_system_no_follow_operations_reject_named_pipes(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(SandboxType::WindowsRestrictedToken; "restricted_token")]
|
||||
#[test_case(SandboxType::WindowsMxc; "mxc")]
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn file_system_remote_fs_helper_respects_windows_sandbox_write_policy() -> Result<()> {
|
||||
async fn file_system_remote_fs_helper_respects_windows_sandbox_write_policy(
|
||||
sandbox_type: SandboxType,
|
||||
) -> Result<()> {
|
||||
let context = create_file_system_context(FileSystemImplementation::Remote).await?;
|
||||
let file_system = context.file_system;
|
||||
let tmp = tempfile::TempDir::new()?;
|
||||
@@ -355,7 +360,39 @@ async fn file_system_remote_fs_helper_respects_windows_sandbox_write_policy() ->
|
||||
std::fs::create_dir_all(&readonly_dir)?;
|
||||
|
||||
let mut sandbox = read_only_sandbox_for_cwd(readonly_dir.clone())?;
|
||||
sandbox.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken;
|
||||
match sandbox_type {
|
||||
SandboxType::WindowsRestrictedToken => {
|
||||
sandbox.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken;
|
||||
}
|
||||
SandboxType::WindowsMxc => {
|
||||
sandbox.windows_sandbox_selection = WindowsSandboxSelection::Mxc;
|
||||
}
|
||||
SandboxType::None | SandboxType::MacosSeatbelt | SandboxType::LinuxSeccomp => {
|
||||
anyhow::bail!("expected a Windows sandbox type")
|
||||
}
|
||||
}
|
||||
|
||||
let blocked_file = readonly_dir.join("blocked.txt");
|
||||
if sandbox_type == SandboxType::WindowsMxc && !codex_sandboxing::windows_mxc_available() {
|
||||
let error = file_system
|
||||
.write_file(
|
||||
&PathUri::from_host_native_path(&blocked_file)?,
|
||||
b"blocked".to_vec(),
|
||||
WriteFileOptions::default(),
|
||||
Some(&sandbox),
|
||||
)
|
||||
.await
|
||||
.expect_err("unavailable MXC must fail closed");
|
||||
assert_eq!(
|
||||
(error.kind(), error.to_string()),
|
||||
(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"failed to prepare fs sandbox: failed to prepare MXC sandbox: native MXC is unavailable on this executor".to_owned(),
|
||||
)
|
||||
);
|
||||
assert!(!blocked_file.exists());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let readable_file = readonly_dir.join("readable.txt");
|
||||
std::fs::write(&readable_file, b"readable")?;
|
||||
@@ -369,12 +406,13 @@ async fn file_system_remote_fs_helper_respects_windows_sandbox_write_policy() ->
|
||||
// Some local Windows hosts cannot create restricted tokens. Reaching that
|
||||
// error still proves the remote fs helper went through the Windows sandbox
|
||||
// launcher; before the wrapper fix this read would have run unsandboxed.
|
||||
if is_unsupported_restricted_token_host(&read_result) {
|
||||
if sandbox_type == SandboxType::WindowsRestrictedToken
|
||||
&& is_unsupported_restricted_token_host(&read_result)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
assert_eq!(read_result?, b"readable");
|
||||
|
||||
let blocked_file = readonly_dir.join("blocked.txt");
|
||||
let error = file_system
|
||||
.write_file(
|
||||
&PathUri::from_host_native_path(&blocked_file)?,
|
||||
@@ -450,7 +488,7 @@ async fn file_system_private_desktop_survives_helper_exits_and_separates_permiss
|
||||
}
|
||||
|
||||
let mut readonly = read_only_sandbox_for_cwd(tmp.path().to_path_buf())?;
|
||||
readonly.windows_sandbox_level = WindowsSandboxLevel::RestrictedToken;
|
||||
readonly.windows_sandbox_selection = WindowsSandboxSelection::RestrictedToken;
|
||||
readonly.windows_sandbox_private_desktop = true;
|
||||
assert_eq!(
|
||||
file_system
|
||||
|
||||
@@ -310,10 +310,7 @@ async fn read_bounded_text(
|
||||
};
|
||||
if sandbox.is_some_and(FileSystemSandboxContext::should_run_in_sandbox)
|
||||
&& path.infer_path_convention() == Some(PathConvention::Windows)
|
||||
&& sandbox.is_some_and(|context| {
|
||||
context.windows_sandbox_level
|
||||
== codex_protocol::config_types::WindowsSandboxLevel::Disabled
|
||||
})
|
||||
&& sandbox.is_some_and(|context| !context.windows_sandbox_is_requested())
|
||||
{
|
||||
return Err(SkillProviderError::new(
|
||||
"executor skill resource requires an unavailable filesystem sandbox",
|
||||
|
||||
@@ -30,6 +30,7 @@ use codex_exec_server::WalkEntry;
|
||||
use codex_exec_server::WalkEntryKind;
|
||||
use codex_exec_server::WalkOptions;
|
||||
use codex_exec_server::WalkOutcome;
|
||||
use codex_exec_server::WindowsSandboxSelection;
|
||||
use codex_exec_server::WriteFileOptions;
|
||||
use codex_protocol::capabilities::CapabilityRootLocation;
|
||||
use codex_protocol::capabilities::SelectedCapabilityRoot;
|
||||
@@ -301,12 +302,12 @@ async fn skill_loading_and_reads_use_the_supplied_executor_file_system() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn windows_executor_skill_read_rejects_disabled_sandbox_on_any_orchestrator() {
|
||||
async fn windows_executor_skill_read_requires_a_requested_sandbox() {
|
||||
let provider = ExecutorSkillProvider::new_with_restriction_product(
|
||||
Arc::new(EnvironmentManager::default_for_tests()),
|
||||
/*restriction_product*/ None,
|
||||
);
|
||||
let sandbox = FileSystemSandboxContext::from_permission_profile(
|
||||
let mut sandbox = FileSystemSandboxContext::from_permission_profile(
|
||||
PermissionProfile::from_runtime_permissions(
|
||||
&FileSystemSandboxPolicy::restricted(Vec::new()),
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
@@ -317,6 +318,25 @@ async fn windows_executor_skill_read_rejects_disabled_sandbox_on_any_orchestrato
|
||||
"local",
|
||||
PathUri::parse("file:///C:/skill/SKILL.md").expect("Windows resource URI"),
|
||||
);
|
||||
let error = provider
|
||||
.read(SkillReadRequest {
|
||||
_lifetime: PhantomData,
|
||||
authority: SkillAuthority::new(SkillSourceKind::Executor, "windows-root"),
|
||||
package: SkillPackageId("skill://windows-root/C:/skill".into()),
|
||||
resource: resource.clone(),
|
||||
resolved_executor_roots: Vec::new(),
|
||||
sandbox: Some(sandbox.clone()),
|
||||
host_snapshot: None,
|
||||
mcp_resources: None,
|
||||
})
|
||||
.await
|
||||
.expect_err("disabled Windows sandbox must fail closed");
|
||||
assert_eq!(
|
||||
error.message,
|
||||
"executor skill resource requires an unavailable filesystem sandbox"
|
||||
);
|
||||
|
||||
sandbox.windows_sandbox_selection = WindowsSandboxSelection::Mxc;
|
||||
let error = provider
|
||||
.read(SkillReadRequest {
|
||||
_lifetime: PhantomData,
|
||||
@@ -329,11 +349,12 @@ async fn windows_executor_skill_read_rejects_disabled_sandbox_on_any_orchestrato
|
||||
mcp_resources: None,
|
||||
})
|
||||
.await
|
||||
.expect_err("disabled Windows sandbox must fail closed");
|
||||
|
||||
assert_eq!(
|
||||
error.message,
|
||||
"executor skill resource requires an unavailable filesystem sandbox"
|
||||
.expect_err("synthetic Windows resource should not be readable");
|
||||
assert!(
|
||||
error
|
||||
.message
|
||||
.starts_with("failed to read executor skill resource"),
|
||||
"{error:?}"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -325,6 +325,30 @@ impl TryFrom<ExecPermissionProfile> for PermissionProfile {
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows sandbox choice encoded in executor RPCs.
|
||||
///
|
||||
/// The serialized field retains its legacy `windowsSandboxLevel` name for compatibility, but MXC
|
||||
/// is a sandbox implementation rather than a RestrictedToken level.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum WindowsSandboxSelection {
|
||||
#[default]
|
||||
Disabled,
|
||||
RestrictedToken,
|
||||
Elevated,
|
||||
Mxc,
|
||||
}
|
||||
|
||||
impl From<WindowsSandboxLevel> for WindowsSandboxSelection {
|
||||
fn from(level: WindowsSandboxLevel) -> Self {
|
||||
match level {
|
||||
WindowsSandboxLevel::Disabled => Self::Disabled,
|
||||
WindowsSandboxLevel::RestrictedToken => Self::RestrictedToken,
|
||||
WindowsSandboxLevel::Elevated => Self::Elevated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct FileSystemSandboxContext {
|
||||
@@ -339,7 +363,8 @@ pub struct FileSystemSandboxContext {
|
||||
/// Executor-local default directories used to resolve `:tmpdir` policy entries.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub temporary_directories: Option<Vec<PathUri>>,
|
||||
pub windows_sandbox_level: WindowsSandboxLevel,
|
||||
#[serde(rename = "windowsSandboxLevel")]
|
||||
pub windows_sandbox_selection: WindowsSandboxSelection,
|
||||
#[serde(default)]
|
||||
pub windows_sandbox_private_desktop: bool,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -385,7 +410,7 @@ impl FileSystemSandboxContext {
|
||||
workspace_roots,
|
||||
user_home_dir: None,
|
||||
temporary_directories: None,
|
||||
windows_sandbox_level: WindowsSandboxLevel::Disabled,
|
||||
windows_sandbox_selection: WindowsSandboxSelection::Disabled,
|
||||
windows_sandbox_private_desktop: false,
|
||||
windows_sandbox_proxy_settings_mode: None,
|
||||
use_legacy_landlock: false,
|
||||
@@ -402,6 +427,11 @@ impl FileSystemSandboxContext {
|
||||
&& !file_system_policy.has_full_disk_write_access()
|
||||
}
|
||||
|
||||
/// Whether this context selects either supported Windows sandbox implementation.
|
||||
pub fn windows_sandbox_is_requested(&self) -> bool {
|
||||
self.windows_sandbox_selection != WindowsSandboxSelection::Disabled
|
||||
}
|
||||
|
||||
/// Borrows the executor-owned paths needed to interpret filesystem policy entries.
|
||||
///
|
||||
/// Returns `None` after the cwd has been omitted from a transport context.
|
||||
|
||||
@@ -299,7 +299,6 @@ pub enum WindowsSandboxLevel {
|
||||
Disabled,
|
||||
RestrictedToken,
|
||||
Elevated,
|
||||
Mxc,
|
||||
}
|
||||
|
||||
/// Controls whether a Windows sandbox launch reconciles persistent proxy settings or preserves
|
||||
|
||||
@@ -333,9 +333,6 @@ impl SandboxManager {
|
||||
if !self.should_sandbox(permission_profile, pref, has_managed_network_requirements) {
|
||||
return SandboxType::None;
|
||||
}
|
||||
if cfg!(windows) && windows_sandbox_level == WindowsSandboxLevel::Mxc {
|
||||
return SandboxType::WindowsMxc;
|
||||
}
|
||||
get_platform_sandbox(windows_sandbox_level != WindowsSandboxLevel::Disabled)
|
||||
.unwrap_or(SandboxType::None)
|
||||
}
|
||||
|
||||
@@ -25,48 +25,6 @@ use pretty_assertions::assert_eq;
|
||||
use std::collections::HashMap;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn mxc_selection_preserves_unsandboxed_requests_and_other_platforms() {
|
||||
let manager = SandboxManager::new();
|
||||
let platform_sandbox = if cfg!(windows) {
|
||||
SandboxType::WindowsMxc
|
||||
} else {
|
||||
get_platform_sandbox(/*windows_sandbox_enabled*/ false).unwrap_or(SandboxType::None)
|
||||
};
|
||||
for (permissions, preference, expected) in [
|
||||
(
|
||||
PermissionProfile::read_only(),
|
||||
SandboxablePreference::Auto,
|
||||
platform_sandbox,
|
||||
),
|
||||
(
|
||||
PermissionProfile::Disabled,
|
||||
SandboxablePreference::Require,
|
||||
platform_sandbox,
|
||||
),
|
||||
(
|
||||
PermissionProfile::read_only(),
|
||||
SandboxablePreference::Forbid,
|
||||
SandboxType::None,
|
||||
),
|
||||
(
|
||||
PermissionProfile::Disabled,
|
||||
SandboxablePreference::Auto,
|
||||
SandboxType::None,
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
manager.select_initial(
|
||||
&permissions,
|
||||
preference,
|
||||
WindowsSandboxLevel::Mxc,
|
||||
/*has_managed_network_requirements*/ false,
|
||||
),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn danger_full_access_defaults_to_no_sandbox_without_network_requirements() {
|
||||
let manager = SandboxManager::new();
|
||||
|
||||
Reference in New Issue
Block a user