mirror of
https://github.com/openai/codex.git
synced 2026-02-02 15:03:38 +00:00
Compare commits
3 Commits
patch-guar
...
codex/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f3f2add3e | ||
|
|
7ec9444fb6 | ||
|
|
8ec36fc336 |
89
codex-rs/Cargo.lock
generated
89
codex-rs/Cargo.lock
generated
@@ -1088,6 +1088,7 @@ dependencies = [
|
||||
"walkdir",
|
||||
"which",
|
||||
"wildmatch",
|
||||
"windows 0.58.0",
|
||||
"wiremock",
|
||||
]
|
||||
|
||||
@@ -2818,7 +2819,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4385,7 +4386,7 @@ dependencies = [
|
||||
"nix 0.30.1",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"windows",
|
||||
"windows 0.61.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6702,6 +6703,16 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||
dependencies = [
|
||||
"windows-core 0.58.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.3"
|
||||
@@ -6709,7 +6720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-core 0.61.2",
|
||||
"windows-future",
|
||||
"windows-link 0.1.3",
|
||||
"windows-numerics",
|
||||
@@ -6721,7 +6732,20 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||
dependencies = [
|
||||
"windows-implement 0.58.0",
|
||||
"windows-interface 0.58.0",
|
||||
"windows-result 0.2.0",
|
||||
"windows-strings 0.1.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6730,11 +6754,11 @@ version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-implement 0.60.0",
|
||||
"windows-interface 0.59.1",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6743,11 +6767,22 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-core 0.61.2",
|
||||
"windows-link 0.1.3",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
@@ -6759,6 +6794,17 @@ dependencies = [
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
@@ -6788,7 +6834,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-core 0.61.2",
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
@@ -6799,8 +6845,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6812,6 +6867,16 @@ dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result 0.2.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
|
||||
@@ -79,6 +79,21 @@ seccompiler = { workspace = true }
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation = "0.9"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.58", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Security_Isolation",
|
||||
"Win32_Security",
|
||||
"Win32_Security_Authorization",
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Threading",
|
||||
] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
windows_appcontainer_command_ext = []
|
||||
|
||||
# Build OpenSSL from source for musl builds.
|
||||
[target.x86_64-unknown-linux-musl.dependencies]
|
||||
openssl-sys = { workspace = true, features = ["vendored"] }
|
||||
|
||||
@@ -26,6 +26,7 @@ use crate::model_provider_info::built_in_model_providers;
|
||||
use crate::openai_model_info::get_model_info;
|
||||
use crate::protocol::AskForApproval;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::safety::set_windows_sandbox_enabled;
|
||||
use anyhow::Context;
|
||||
use codex_app_server_protocol::Tools;
|
||||
use codex_app_server_protocol::UserSavedConfig;
|
||||
@@ -170,6 +171,9 @@ pub struct Config {
|
||||
/// When this program is invoked, arg0 will be set to `codex-linux-sandbox`.
|
||||
pub codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
|
||||
/// Enable the experimental Windows sandbox implementation.
|
||||
pub experimental_windows_sandbox: bool,
|
||||
|
||||
/// Value to use for `reasoning.effort` when making a request using the
|
||||
/// Responses API.
|
||||
pub model_reasoning_effort: Option<ReasoningEffort>,
|
||||
@@ -751,6 +755,7 @@ pub struct ConfigToml {
|
||||
pub experimental_use_unified_exec_tool: Option<bool>,
|
||||
pub experimental_use_rmcp_client: Option<bool>,
|
||||
pub experimental_use_freeform_apply_patch: Option<bool>,
|
||||
pub experimental_windows_sandbox: Option<bool>,
|
||||
|
||||
pub projects: Option<HashMap<String, ProjectConfig>>,
|
||||
|
||||
@@ -1008,6 +1013,8 @@ impl Config {
|
||||
.or(cfg.tools.as_ref().and_then(|t| t.view_image))
|
||||
.unwrap_or(true);
|
||||
|
||||
let experimental_windows_sandbox = cfg.experimental_windows_sandbox.unwrap_or(false);
|
||||
|
||||
let model = model
|
||||
.or(config_profile.model)
|
||||
.or(cfg.model)
|
||||
@@ -1093,6 +1100,7 @@ impl Config {
|
||||
history,
|
||||
file_opener: cfg.file_opener.unwrap_or(UriBasedFileOpener::VsCode),
|
||||
codex_linux_sandbox_exe,
|
||||
experimental_windows_sandbox,
|
||||
|
||||
hide_agent_reasoning: cfg.hide_agent_reasoning.unwrap_or(false),
|
||||
show_raw_agent_reasoning: cfg
|
||||
@@ -1146,6 +1154,7 @@ impl Config {
|
||||
}
|
||||
},
|
||||
};
|
||||
set_windows_sandbox_enabled(config.experimental_windows_sandbox);
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -1903,6 +1912,7 @@ model_verbosity = "high"
|
||||
history: History::default(),
|
||||
file_opener: UriBasedFileOpener::VsCode,
|
||||
codex_linux_sandbox_exe: None,
|
||||
experimental_windows_sandbox: false,
|
||||
hide_agent_reasoning: false,
|
||||
show_raw_agent_reasoning: false,
|
||||
model_reasoning_effort: Some(ReasoningEffort::High),
|
||||
@@ -1965,6 +1975,7 @@ model_verbosity = "high"
|
||||
history: History::default(),
|
||||
file_opener: UriBasedFileOpener::VsCode,
|
||||
codex_linux_sandbox_exe: None,
|
||||
experimental_windows_sandbox: false,
|
||||
hide_agent_reasoning: false,
|
||||
show_raw_agent_reasoning: false,
|
||||
model_reasoning_effort: None,
|
||||
@@ -2042,6 +2053,7 @@ model_verbosity = "high"
|
||||
history: History::default(),
|
||||
file_opener: UriBasedFileOpener::VsCode,
|
||||
codex_linux_sandbox_exe: None,
|
||||
experimental_windows_sandbox: false,
|
||||
hide_agent_reasoning: false,
|
||||
show_raw_agent_reasoning: false,
|
||||
model_reasoning_effort: None,
|
||||
@@ -2105,6 +2117,7 @@ model_verbosity = "high"
|
||||
history: History::default(),
|
||||
file_opener: UriBasedFileOpener::VsCode,
|
||||
codex_linux_sandbox_exe: None,
|
||||
experimental_windows_sandbox: false,
|
||||
hide_agent_reasoning: false,
|
||||
show_raw_agent_reasoning: false,
|
||||
model_reasoning_effort: Some(ReasoningEffort::High),
|
||||
|
||||
@@ -27,6 +27,8 @@ use crate::protocol::SandboxPolicy;
|
||||
use crate::seatbelt::spawn_command_under_seatbelt;
|
||||
use crate::spawn::StdioPolicy;
|
||||
use crate::spawn::spawn_child_async;
|
||||
#[cfg(windows)]
|
||||
use crate::windows_appcontainer::spawn_command_under_windows_appcontainer;
|
||||
|
||||
const DEFAULT_TIMEOUT_MS: u64 = 10_000;
|
||||
|
||||
@@ -70,6 +72,9 @@ pub enum SandboxType {
|
||||
|
||||
/// Only available on Linux.
|
||||
LinuxSeccomp,
|
||||
|
||||
/// Only available on Windows.
|
||||
WindowsAppContainer,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -94,6 +99,31 @@ pub async fn process_exec_tool_call(
|
||||
let raw_output_result: std::result::Result<RawExecToolCallOutput, CodexErr> = match sandbox_type
|
||||
{
|
||||
SandboxType::None => exec(params, sandbox_policy, stdout_stream.clone()).await,
|
||||
SandboxType::WindowsAppContainer => {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let ExecParams {
|
||||
command,
|
||||
cwd: command_cwd,
|
||||
env,
|
||||
..
|
||||
} = params;
|
||||
let child = spawn_command_under_windows_appcontainer(
|
||||
command,
|
||||
command_cwd,
|
||||
sandbox_policy,
|
||||
sandbox_cwd,
|
||||
StdioPolicy::RedirectForShellTool,
|
||||
env,
|
||||
)
|
||||
.await?;
|
||||
consume_truncated_output(child, timeout_duration, stdout_stream.clone()).await
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
panic!("windows sandboxing is not available on this platform");
|
||||
}
|
||||
}
|
||||
SandboxType::MacosSeatbelt => {
|
||||
let ExecParams {
|
||||
command,
|
||||
@@ -198,7 +228,10 @@ pub async fn process_exec_tool_call(
|
||||
/// For now, we conservatively check for 'command not found' (exit code 127),
|
||||
/// and can add additional cases as necessary.
|
||||
fn is_likely_sandbox_denied(sandbox_type: SandboxType, exit_code: i32) -> bool {
|
||||
if sandbox_type == SandboxType::None {
|
||||
if matches!(
|
||||
sandbox_type,
|
||||
SandboxType::None | SandboxType::WindowsAppContainer
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,9 @@ mod tasks;
|
||||
mod user_notification;
|
||||
pub mod util;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod windows_appcontainer;
|
||||
|
||||
pub use apply_patch::CODEX_APPLY_PATCH_ARG1;
|
||||
pub use command_safety::is_safe_command;
|
||||
pub use safety::get_platform_sandbox;
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::collections::HashSet;
|
||||
use std::path::Component;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use codex_apply_patch::ApplyPatchAction;
|
||||
use codex_apply_patch::ApplyPatchFileChange;
|
||||
@@ -13,6 +15,12 @@ use crate::command_safety::is_safe_command::is_known_safe_command;
|
||||
use crate::protocol::AskForApproval;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
|
||||
static WINDOWS_SANDBOX_ENABLED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub(crate) fn set_windows_sandbox_enabled(enabled: bool) {
|
||||
WINDOWS_SANDBOX_ENABLED.store(enabled, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum SafetyCheck {
|
||||
AutoApprove {
|
||||
@@ -206,6 +214,12 @@ pub fn get_platform_sandbox() -> Option<SandboxType> {
|
||||
Some(SandboxType::MacosSeatbelt)
|
||||
} else if cfg!(target_os = "linux") {
|
||||
Some(SandboxType::LinuxSeccomp)
|
||||
} else if cfg!(target_os = "windows") {
|
||||
if WINDOWS_SANDBOX_ENABLED.load(Ordering::Relaxed) {
|
||||
Some(SandboxType::WindowsAppContainer)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -436,4 +450,19 @@ mod tests {
|
||||
};
|
||||
assert_eq!(safety_check, expected);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[test]
|
||||
fn windows_sandbox_toggle_controls_platform_sandbox() {
|
||||
set_windows_sandbox_enabled(false);
|
||||
assert_eq!(get_platform_sandbox(), None);
|
||||
|
||||
set_windows_sandbox_enabled(true);
|
||||
assert_eq!(
|
||||
get_platform_sandbox(),
|
||||
Some(SandboxType::WindowsAppContainer)
|
||||
);
|
||||
|
||||
set_windows_sandbox_enabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
467
codex-rs/core/src/windows_appcontainer.rs
Normal file
467
codex-rs/core/src/windows_appcontainer.rs
Normal file
@@ -0,0 +1,467 @@
|
||||
#![cfg(windows)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tokio::process::Child;
|
||||
use tracing::trace;
|
||||
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::spawn::StdioPolicy;
|
||||
|
||||
#[cfg(feature = "windows_appcontainer_command_ext")]
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::c_void;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::ptr::null_mut;
|
||||
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::spawn::CODEX_SANDBOX_ENV_VAR;
|
||||
use crate::spawn::CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR;
|
||||
|
||||
use windows::Win32::Foundation::ERROR_ALREADY_EXISTS;
|
||||
use windows::Win32::Foundation::ERROR_SUCCESS;
|
||||
use windows::Win32::Foundation::GetLastError;
|
||||
use windows::Win32::Foundation::HANDLE;
|
||||
use windows::Win32::Foundation::HLOCAL;
|
||||
use windows::Win32::Foundation::LocalFree;
|
||||
use windows::Win32::Foundation::WIN32_ERROR;
|
||||
use windows::Win32::Security::ACL;
|
||||
use windows::Win32::Security::Authorization::ConvertStringSidToSidW;
|
||||
use windows::Win32::Security::Authorization::EXPLICIT_ACCESS_W;
|
||||
use windows::Win32::Security::Authorization::GetNamedSecurityInfoW;
|
||||
use windows::Win32::Security::Authorization::SE_FILE_OBJECT;
|
||||
use windows::Win32::Security::Authorization::SET_ACCESS;
|
||||
use windows::Win32::Security::Authorization::SetEntriesInAclW;
|
||||
use windows::Win32::Security::Authorization::SetNamedSecurityInfoW;
|
||||
use windows::Win32::Security::Authorization::TRUSTEE_IS_SID;
|
||||
use windows::Win32::Security::Authorization::TRUSTEE_IS_UNKNOWN;
|
||||
use windows::Win32::Security::Authorization::TRUSTEE_W;
|
||||
use windows::Win32::Security::DACL_SECURITY_INFORMATION;
|
||||
use windows::Win32::Security::FreeSid;
|
||||
use windows::Win32::Security::Isolation::CreateAppContainerProfile;
|
||||
use windows::Win32::Security::Isolation::DeriveAppContainerSidFromAppContainerName;
|
||||
use windows::Win32::Security::OBJECT_INHERIT_ACE;
|
||||
use windows::Win32::Security::PSECURITY_DESCRIPTOR;
|
||||
use windows::Win32::Security::PSID;
|
||||
use windows::Win32::Security::SECURITY_CAPABILITIES;
|
||||
use windows::Win32::Security::SID_AND_ATTRIBUTES;
|
||||
use windows::Win32::Security::SUB_CONTAINERS_AND_OBJECTS_INHERIT;
|
||||
use windows::Win32::Storage::FileSystem::FILE_GENERIC_EXECUTE;
|
||||
use windows::Win32::Storage::FileSystem::FILE_GENERIC_READ;
|
||||
use windows::Win32::Storage::FileSystem::FILE_GENERIC_WRITE;
|
||||
use windows::Win32::System::Memory::GetProcessHeap;
|
||||
use windows::Win32::System::Memory::HEAP_FLAGS;
|
||||
use windows::Win32::System::Memory::HEAP_ZERO_MEMORY;
|
||||
use windows::Win32::System::Memory::HeapAlloc;
|
||||
use windows::Win32::System::Memory::HeapFree;
|
||||
use windows::Win32::System::Threading::DeleteProcThreadAttributeList;
|
||||
use windows::Win32::System::Threading::EXTENDED_STARTUPINFO_PRESENT;
|
||||
use windows::Win32::System::Threading::InitializeProcThreadAttributeList;
|
||||
use windows::Win32::System::Threading::LPPROC_THREAD_ATTRIBUTE_LIST;
|
||||
use windows::Win32::System::Threading::PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES;
|
||||
use windows::Win32::System::Threading::UpdateProcThreadAttribute;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::core::PWSTR;
|
||||
|
||||
const WINDOWS_APPCONTAINER_PROFILE_NAME: &str = "codex_appcontainer";
|
||||
const WINDOWS_APPCONTAINER_PROFILE_DESC: &str = "Codex Windows AppContainer profile";
|
||||
const WINDOWS_APPCONTAINER_SANDBOX_VALUE: &str = "windows_appcontainer";
|
||||
const INTERNET_CLIENT_SID: &str = "S-1-15-3-1";
|
||||
const PRIVATE_NETWORK_CLIENT_SID: &str = "S-1-15-3-3";
|
||||
|
||||
pub async fn spawn_command_under_windows_appcontainer(
|
||||
command: Vec<String>,
|
||||
command_cwd: PathBuf,
|
||||
sandbox_policy: &SandboxPolicy,
|
||||
sandbox_policy_cwd: &Path,
|
||||
stdio_policy: StdioPolicy,
|
||||
mut env: HashMap<String, String>,
|
||||
) -> io::Result<Child> {
|
||||
trace!("windows appcontainer sandbox command = {:?}", command);
|
||||
|
||||
let (program, rest) = command
|
||||
.split_first()
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "command args are empty"))?;
|
||||
|
||||
ensure_appcontainer_profile()?;
|
||||
let mut sid = derive_appcontainer_sid()?;
|
||||
let mut capability_sids = build_capabilities(sandbox_policy)?;
|
||||
let mut attribute_list = AttributeList::new(&mut sid, &mut capability_sids)?;
|
||||
|
||||
configure_writable_roots(sandbox_policy, sandbox_policy_cwd, sid.sid())?;
|
||||
configure_writable_roots_for_command_cwd(&command_cwd, sid.sid())?;
|
||||
|
||||
if !sandbox_policy.has_full_network_access() {
|
||||
env.insert(
|
||||
CODEX_SANDBOX_NETWORK_DISABLED_ENV_VAR.to_string(),
|
||||
"1".to_string(),
|
||||
);
|
||||
}
|
||||
env.insert(
|
||||
CODEX_SANDBOX_ENV_VAR.to_string(),
|
||||
WINDOWS_APPCONTAINER_SANDBOX_VALUE.to_string(),
|
||||
);
|
||||
|
||||
let mut cmd = Command::new(program);
|
||||
cmd.args(rest);
|
||||
cmd.current_dir(command_cwd);
|
||||
cmd.env_clear();
|
||||
cmd.envs(env);
|
||||
apply_stdio_policy(&mut cmd, stdio_policy);
|
||||
cmd.kill_on_drop(true);
|
||||
|
||||
unsafe {
|
||||
let std_cmd = cmd.as_std_mut();
|
||||
std_cmd.creation_flags(EXTENDED_STARTUPINFO_PRESENT.0);
|
||||
std_cmd.raw_attribute_list(attribute_list.as_mut_ptr().0);
|
||||
}
|
||||
|
||||
let child = cmd.spawn();
|
||||
drop(attribute_list);
|
||||
child
|
||||
}
|
||||
|
||||
fn apply_stdio_policy(cmd: &mut Command, policy: StdioPolicy) {
|
||||
match policy {
|
||||
StdioPolicy::RedirectForShellTool => {
|
||||
cmd.stdin(std::process::Stdio::null());
|
||||
cmd.stdout(std::process::Stdio::piped());
|
||||
cmd.stderr(std::process::Stdio::piped());
|
||||
}
|
||||
StdioPolicy::Inherit => {
|
||||
cmd.stdin(std::process::Stdio::inherit());
|
||||
cmd.stdout(std::process::Stdio::inherit());
|
||||
cmd.stderr(std::process::Stdio::inherit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_wide<S: AsRef<OsStr>>(s: S) -> Vec<u16> {
|
||||
s.as_ref().encode_wide().chain(std::iter::once(0)).collect()
|
||||
}
|
||||
|
||||
fn ensure_appcontainer_profile() -> io::Result<()> {
|
||||
unsafe {
|
||||
let name = to_wide(WINDOWS_APPCONTAINER_PROFILE_NAME);
|
||||
let desc = to_wide(WINDOWS_APPCONTAINER_PROFILE_DESC);
|
||||
match CreateAppContainerProfile(
|
||||
PCWSTR(name.as_ptr()),
|
||||
PCWSTR(name.as_ptr()),
|
||||
PCWSTR(desc.as_ptr()),
|
||||
None,
|
||||
) {
|
||||
Ok(profile_sid) => {
|
||||
if !profile_sid.is_invalid() {
|
||||
FreeSid(profile_sid);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
let already_exists = WIN32_ERROR::from(ERROR_ALREADY_EXISTS);
|
||||
if GetLastError() != already_exists {
|
||||
return Err(io::Error::from_raw_os_error(error.code().0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct SidHandle {
|
||||
ptr: PSID,
|
||||
}
|
||||
|
||||
impl SidHandle {
|
||||
fn sid(&self) -> PSID {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SidHandle {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.ptr.is_invalid() {
|
||||
FreeSid(self.ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_appcontainer_sid() -> io::Result<SidHandle> {
|
||||
unsafe {
|
||||
let name = to_wide(WINDOWS_APPCONTAINER_PROFILE_NAME);
|
||||
let sid = DeriveAppContainerSidFromAppContainerName(PCWSTR(name.as_ptr()))
|
||||
.map_err(|e| io::Error::from_raw_os_error(e.code().0))?;
|
||||
Ok(SidHandle { ptr: sid })
|
||||
}
|
||||
}
|
||||
|
||||
struct CapabilitySid {
|
||||
sid: PSID,
|
||||
}
|
||||
|
||||
impl CapabilitySid {
|
||||
fn new_from_string(value: &str) -> io::Result<Self> {
|
||||
unsafe {
|
||||
let mut sid_ptr = PSID::default();
|
||||
let wide = to_wide(value);
|
||||
ConvertStringSidToSidW(PCWSTR(wide.as_ptr()), &mut sid_ptr)
|
||||
.map_err(|e| io::Error::from_raw_os_error(e.code().0))?;
|
||||
Ok(Self { sid: sid_ptr })
|
||||
}
|
||||
}
|
||||
|
||||
fn sid_and_attributes(&self) -> SID_AND_ATTRIBUTES {
|
||||
SID_AND_ATTRIBUTES {
|
||||
Sid: self.sid,
|
||||
Attributes: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CapabilitySid {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.sid.is_invalid() {
|
||||
let _ = LocalFree(HLOCAL(self.sid.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_capabilities(policy: &SandboxPolicy) -> io::Result<Vec<CapabilitySid>> {
|
||||
if policy.has_full_network_access() {
|
||||
Ok(vec![
|
||||
CapabilitySid::new_from_string(INTERNET_CLIENT_SID)?,
|
||||
CapabilitySid::new_from_string(PRIVATE_NETWORK_CLIENT_SID)?,
|
||||
])
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
struct AttributeList<'a> {
|
||||
heap: HANDLE,
|
||||
buffer: *mut c_void,
|
||||
list: LPPROC_THREAD_ATTRIBUTE_LIST,
|
||||
sec_caps: SECURITY_CAPABILITIES,
|
||||
sid_and_attributes: Vec<SID_AND_ATTRIBUTES>,
|
||||
#[allow(dead_code)]
|
||||
sid: &'a mut SidHandle,
|
||||
#[allow(dead_code)]
|
||||
capabilities: &'a mut Vec<CapabilitySid>,
|
||||
}
|
||||
|
||||
impl<'a> AttributeList<'a> {
|
||||
fn new(sid: &'a mut SidHandle, caps: &'a mut Vec<CapabilitySid>) -> io::Result<Self> {
|
||||
unsafe {
|
||||
let mut list_size = 0usize;
|
||||
let _ = InitializeProcThreadAttributeList(
|
||||
LPPROC_THREAD_ATTRIBUTE_LIST::default(),
|
||||
1,
|
||||
0,
|
||||
&mut list_size,
|
||||
);
|
||||
let heap =
|
||||
GetProcessHeap().map_err(|e| io::Error::from_raw_os_error(e.code().0))?;
|
||||
let buffer = HeapAlloc(heap, HEAP_ZERO_MEMORY, list_size);
|
||||
if buffer.is_null() {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
let list = LPPROC_THREAD_ATTRIBUTE_LIST(buffer);
|
||||
InitializeProcThreadAttributeList(list, 1, 0, &mut list_size)
|
||||
.map_err(|e| io::Error::from_raw_os_error(e.code().0))?;
|
||||
|
||||
let mut sid_and_attributes: Vec<SID_AND_ATTRIBUTES> =
|
||||
caps.iter().map(CapabilitySid::sid_and_attributes).collect();
|
||||
|
||||
let mut sec_caps = SECURITY_CAPABILITIES {
|
||||
AppContainerSid: sid.sid(),
|
||||
Capabilities: if sid_and_attributes.is_empty() {
|
||||
null_mut()
|
||||
} else {
|
||||
sid_and_attributes.as_mut_ptr()
|
||||
},
|
||||
CapabilityCount: sid_and_attributes.len() as u32,
|
||||
Reserved: 0,
|
||||
};
|
||||
|
||||
UpdateProcThreadAttribute(
|
||||
list,
|
||||
0,
|
||||
PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES as usize,
|
||||
Some(&mut sec_caps as *mut _ as *const std::ffi::c_void),
|
||||
std::mem::size_of::<SECURITY_CAPABILITIES>(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map_err(|e| io::Error::from_raw_os_error(e.code().0))?;
|
||||
|
||||
Ok(Self {
|
||||
heap,
|
||||
buffer,
|
||||
list,
|
||||
sec_caps,
|
||||
sid_and_attributes,
|
||||
sid,
|
||||
capabilities: caps,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn as_mut_ptr(&mut self) -> LPPROC_THREAD_ATTRIBUTE_LIST {
|
||||
self.list
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AttributeList<'_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.list.is_invalid() {
|
||||
DeleteProcThreadAttributeList(self.list);
|
||||
}
|
||||
if !self.heap.is_invalid() && !self.buffer.is_null() {
|
||||
let _ = HeapFree(self.heap, HEAP_FLAGS(0), Some(self.buffer));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_writable_roots(
|
||||
policy: &SandboxPolicy,
|
||||
sandbox_policy_cwd: &Path,
|
||||
sid: PSID,
|
||||
) -> io::Result<()> {
|
||||
match policy {
|
||||
SandboxPolicy::DangerFullAccess => Ok(()),
|
||||
SandboxPolicy::ReadOnly => grant_path_with_flags(sandbox_policy_cwd, sid, false),
|
||||
SandboxPolicy::WorkspaceWrite { .. } => {
|
||||
let roots = policy.get_writable_roots_with_cwd(sandbox_policy_cwd);
|
||||
for writable in roots {
|
||||
grant_path_with_flags(&writable.root, sid, true)?;
|
||||
for ro in writable.read_only_subpaths {
|
||||
grant_path_with_flags(&ro, sid, false)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_writable_roots_for_command_cwd(command_cwd: &Path, sid: PSID) -> io::Result<()> {
|
||||
grant_path_with_flags(command_cwd, sid, true)
|
||||
}
|
||||
|
||||
fn grant_path_with_flags(path: &Path, sid: PSID, write: bool) -> io::Result<()> {
|
||||
if !path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let wide = to_wide(path.as_os_str());
|
||||
unsafe {
|
||||
let mut existing_dacl: *mut ACL = null_mut();
|
||||
let mut security_descriptor = PSECURITY_DESCRIPTOR::default();
|
||||
let status = GetNamedSecurityInfoW(
|
||||
PCWSTR(wide.as_ptr()),
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
None,
|
||||
None,
|
||||
Some(&mut existing_dacl),
|
||||
None,
|
||||
&mut security_descriptor,
|
||||
);
|
||||
if status != WIN32_ERROR::from(ERROR_SUCCESS) {
|
||||
if !security_descriptor.is_invalid() {
|
||||
let _ = LocalFree(HLOCAL(security_descriptor.0));
|
||||
}
|
||||
return Err(io::Error::from_raw_os_error(status.0 as i32));
|
||||
}
|
||||
|
||||
let permissions = if write {
|
||||
(FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE).0
|
||||
} else {
|
||||
(FILE_GENERIC_READ | FILE_GENERIC_EXECUTE).0
|
||||
};
|
||||
let explicit = EXPLICIT_ACCESS_W {
|
||||
grfAccessPermissions: permissions,
|
||||
grfAccessMode: SET_ACCESS,
|
||||
grfInheritance: (SUB_CONTAINERS_AND_OBJECTS_INHERIT | OBJECT_INHERIT_ACE).0,
|
||||
Trustee: TRUSTEE_W {
|
||||
TrusteeForm: TRUSTEE_IS_SID,
|
||||
TrusteeType: TRUSTEE_IS_UNKNOWN,
|
||||
ptstrName: PWSTR(sid.0.cast()),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
let explicit_entries = [explicit];
|
||||
let mut new_dacl: *mut ACL = null_mut();
|
||||
let add_result =
|
||||
SetEntriesInAclW(Some(&explicit_entries), Some(existing_dacl), &mut new_dacl);
|
||||
if add_result != WIN32_ERROR::from(ERROR_SUCCESS) {
|
||||
if !new_dacl.is_null() {
|
||||
let _ = LocalFree(HLOCAL(new_dacl.cast()));
|
||||
}
|
||||
if !security_descriptor.is_invalid() {
|
||||
let _ = LocalFree(HLOCAL(security_descriptor.0));
|
||||
}
|
||||
return Err(io::Error::from_raw_os_error(add_result.0 as i32));
|
||||
}
|
||||
|
||||
let set_result = SetNamedSecurityInfoW(
|
||||
PCWSTR(wide.as_ptr()),
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
None,
|
||||
None,
|
||||
Some(new_dacl),
|
||||
None,
|
||||
);
|
||||
if set_result != WIN32_ERROR::from(ERROR_SUCCESS) {
|
||||
if !new_dacl.is_null() {
|
||||
let _ = LocalFree(HLOCAL(new_dacl.cast()));
|
||||
}
|
||||
if !security_descriptor.is_invalid() {
|
||||
let _ = LocalFree(HLOCAL(security_descriptor.0));
|
||||
}
|
||||
return Err(io::Error::from_raw_os_error(set_result.0 as i32));
|
||||
}
|
||||
|
||||
if !new_dacl.is_null() {
|
||||
let _ = LocalFree(HLOCAL(new_dacl.cast()));
|
||||
}
|
||||
if !security_descriptor.is_invalid() {
|
||||
let _ = LocalFree(HLOCAL(security_descriptor.0));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "windows_appcontainer_command_ext")]
|
||||
pub use imp::spawn_command_under_windows_appcontainer;
|
||||
|
||||
#[cfg(not(feature = "windows_appcontainer_command_ext"))]
|
||||
pub async fn spawn_command_under_windows_appcontainer(
|
||||
command: Vec<String>,
|
||||
command_cwd: PathBuf,
|
||||
_sandbox_policy: &SandboxPolicy,
|
||||
_sandbox_policy_cwd: &Path,
|
||||
_stdio_policy: StdioPolicy,
|
||||
_env: HashMap<String, String>,
|
||||
) -> io::Result<Child> {
|
||||
let _ = (command, command_cwd);
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::Unsupported,
|
||||
"AppContainer sandboxing requires the `windows_appcontainer_command_ext` feature",
|
||||
))
|
||||
}
|
||||
76
codex-rs/core/tests/windows_appcontainer.rs
Normal file
76
codex-rs/core/tests/windows_appcontainer.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
#![cfg(all(windows, feature = "windows_appcontainer_command_ext"))]
|
||||
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use codex_core::spawn::StdioPolicy;
|
||||
use codex_core::windows_appcontainer::spawn_command_under_windows_appcontainer;
|
||||
use std::collections::HashMap;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
fn windows_workspace_policy() -> SandboxPolicy {
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn windows_appcontainer_writes_to_workspace() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let cwd = temp.path().to_path_buf();
|
||||
let policy_cwd = cwd.clone();
|
||||
let mut child = spawn_command_under_windows_appcontainer(
|
||||
vec![
|
||||
"cmd.exe".to_string(),
|
||||
"/C".to_string(),
|
||||
"echo hello>out.txt".to_string(),
|
||||
],
|
||||
cwd.clone(),
|
||||
&windows_workspace_policy(),
|
||||
&policy_cwd,
|
||||
StdioPolicy::RedirectForShellTool,
|
||||
HashMap::new(),
|
||||
)
|
||||
.await
|
||||
.expect("spawn cmd");
|
||||
|
||||
let status = child.wait().await.expect("wait");
|
||||
assert!(status.success(), "cmd.exe failed: {status:?}");
|
||||
|
||||
let contents = tokio::fs::read_to_string(temp.path().join("out.txt"))
|
||||
.await
|
||||
.expect("read redirected output");
|
||||
assert!(contents.to_lowercase().contains("hello"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn windows_appcontainer_sets_env_flags() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let cwd = temp.path().to_path_buf();
|
||||
let policy_cwd = cwd.clone();
|
||||
let mut child = spawn_command_under_windows_appcontainer(
|
||||
vec![
|
||||
"cmd.exe".to_string(),
|
||||
"/C".to_string(),
|
||||
"set CODEX_SANDBOX".to_string(),
|
||||
],
|
||||
cwd,
|
||||
&windows_workspace_policy(),
|
||||
&policy_cwd,
|
||||
StdioPolicy::RedirectForShellTool,
|
||||
HashMap::new(),
|
||||
)
|
||||
.await
|
||||
.expect("spawn cmd");
|
||||
|
||||
let mut stdout = Vec::new();
|
||||
if let Some(mut out) = child.stdout.take() {
|
||||
out.read_to_end(&mut stdout).await.expect("stdout");
|
||||
}
|
||||
let status = child.wait().await.expect("wait");
|
||||
assert!(status.success(), "cmd.exe env probe failed: {status:?}");
|
||||
let stdout_text = String::from_utf8_lossy(&stdout).to_lowercase();
|
||||
assert!(stdout_text.contains("codex_sandbox=windows_appcontainer"));
|
||||
assert!(stdout_text.contains("codex_sandbox_network_disabled=1"));
|
||||
}
|
||||
74
codex-rs/exec/tests/windows_sandbox.rs
Normal file
74
codex-rs/exec/tests/windows_sandbox.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
#![cfg(windows)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use codex_core::exec::ExecParams;
|
||||
use codex_core::exec::SandboxType;
|
||||
use codex_core::exec::process_exec_tool_call;
|
||||
use codex_core::protocol::SandboxPolicy;
|
||||
use codex_core::safety::set_windows_sandbox_enabled;
|
||||
|
||||
struct WindowsSandboxGuard;
|
||||
|
||||
impl WindowsSandboxGuard {
|
||||
fn enable() -> Self {
|
||||
set_windows_sandbox_enabled(true);
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowsSandboxGuard {
|
||||
fn drop(&mut self) {
|
||||
set_windows_sandbox_enabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
fn windows_workspace_policy(root: &PathBuf) -> SandboxPolicy {
|
||||
SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![root.clone()],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn exec_tool_uses_windows_sandbox() {
|
||||
let _guard = WindowsSandboxGuard::enable();
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
let cwd = temp.path().to_path_buf();
|
||||
let policy = windows_workspace_policy(&cwd);
|
||||
let params = ExecParams {
|
||||
command: vec![
|
||||
"cmd.exe".to_string(),
|
||||
"/C".to_string(),
|
||||
"set CODEX_SANDBOX".to_string(),
|
||||
],
|
||||
cwd: cwd.clone(),
|
||||
timeout_ms: None,
|
||||
env: HashMap::new(),
|
||||
with_escalated_permissions: None,
|
||||
justification: None,
|
||||
};
|
||||
|
||||
let output = process_exec_tool_call(
|
||||
params,
|
||||
SandboxType::WindowsAppContainer,
|
||||
&policy,
|
||||
temp.path(),
|
||||
&None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("exec output");
|
||||
|
||||
assert_eq!(output.exit_code, 0);
|
||||
assert!(
|
||||
output
|
||||
.aggregated_output
|
||||
.text
|
||||
.to_lowercase()
|
||||
.contains("codex_sandbox=windows_appcontainer")
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user