mirror of
https://github.com/openai/codex.git
synced 2026-05-05 03:47:01 +00:00
Compare commits
6 Commits
codex/wind
...
codex/wind
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83c952f1a0 | ||
|
|
56c383d9bf | ||
|
|
f21dcba492 | ||
|
|
72abfcabcc | ||
|
|
d4a25eaa37 | ||
|
|
1fef4bd159 |
@@ -372,6 +372,7 @@ async fn run_command_under_windows_session(
|
||||
None,
|
||||
/*tty*/ false,
|
||||
/*stdin_open*/ true,
|
||||
&[],
|
||||
config.permissions.windows_sandbox_private_desktop,
|
||||
)
|
||||
.await
|
||||
@@ -386,6 +387,7 @@ async fn run_command_under_windows_session(
|
||||
None,
|
||||
/*tty*/ false,
|
||||
/*stdin_open*/ true,
|
||||
&[],
|
||||
config.permissions.windows_sandbox_private_desktop,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -110,11 +110,11 @@ pub(crate) struct WindowsSandboxFilesystemOverrides {
|
||||
pub(crate) read_roots_include_platform_defaults: bool,
|
||||
pub(crate) write_roots_override: Option<Vec<PathBuf>>,
|
||||
pub(crate) additional_deny_write_paths: Vec<AbsolutePathBuf>,
|
||||
pub(crate) protected_metadata_targets: Vec<WindowsProtectedMetadataTarget>,
|
||||
}
|
||||
|
||||
/// Layer: Windows adapter layer. This is the Windows projection of
|
||||
/// `WritableRoot::protected_metadata_names` from `FileSystemSandboxPolicy`.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub(crate) struct WindowsProtectedMetadataTarget {
|
||||
pub(crate) path: AbsolutePathBuf,
|
||||
@@ -124,7 +124,6 @@ pub(crate) struct WindowsProtectedMetadataTarget {
|
||||
/// Layer: Windows adapter layer. The enforcement layer needs to know why a
|
||||
/// protected metadata path is absent instead of treating every missing path as
|
||||
/// an existing filesystem object.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub(crate) enum WindowsProtectedMetadataMode {
|
||||
ExistingDeny,
|
||||
@@ -408,39 +407,18 @@ pub fn build_exec_request(
|
||||
ExecRequest::from_sandbox_exec_request(request, options, windows_sandbox_policy_cwd)
|
||||
})
|
||||
.map_err(CodexErr::from)?;
|
||||
let use_windows_elevated_backend = windows_sandbox_uses_elevated_backend(
|
||||
exec_req.windows_sandbox_level,
|
||||
exec_req.network.is_some(),
|
||||
);
|
||||
let sandbox_policy = exec_req.compatibility_sandbox_policy();
|
||||
exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend {
|
||||
resolve_windows_elevated_filesystem_overrides(
|
||||
exec_req.sandbox,
|
||||
&sandbox_policy,
|
||||
&exec_req.file_system_sandbox_policy,
|
||||
exec_req.network_sandbox_policy,
|
||||
sandbox_cwd,
|
||||
use_windows_elevated_backend,
|
||||
)
|
||||
} else {
|
||||
resolve_windows_restricted_token_filesystem_overrides(
|
||||
exec_req.sandbox,
|
||||
&sandbox_policy,
|
||||
&exec_req.file_system_sandbox_policy,
|
||||
exec_req.network_sandbox_policy,
|
||||
sandbox_cwd,
|
||||
exec_req.windows_sandbox_level,
|
||||
)
|
||||
}
|
||||
.map_err(CodexErr::UnsupportedOperation)?;
|
||||
ensure_windows_sandbox_filesystem_overrides(&mut exec_req)
|
||||
.map_err(CodexErr::UnsupportedOperation)?;
|
||||
Ok(exec_req)
|
||||
}
|
||||
|
||||
pub(crate) async fn execute_exec_request(
|
||||
exec_request: ExecRequest,
|
||||
mut exec_request: ExecRequest,
|
||||
stdout_stream: Option<StdoutStream>,
|
||||
after_spawn: Option<Box<dyn FnOnce() + Send>>,
|
||||
) -> Result<ExecToolCallOutput> {
|
||||
ensure_windows_sandbox_filesystem_overrides(&mut exec_request)
|
||||
.map_err(CodexErr::UnsupportedOperation)?;
|
||||
let sandbox_policy = exec_request.compatibility_sandbox_policy();
|
||||
let ExecRequest {
|
||||
command,
|
||||
@@ -490,6 +468,36 @@ pub(crate) async fn execute_exec_request(
|
||||
finalize_exec_result(raw_output_result, sandbox, duration)
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_windows_sandbox_filesystem_overrides(
|
||||
exec_req: &mut ExecRequest,
|
||||
) -> std::result::Result<(), String> {
|
||||
let use_windows_elevated_backend = windows_sandbox_uses_elevated_backend(
|
||||
exec_req.windows_sandbox_level,
|
||||
exec_req.network.is_some(),
|
||||
);
|
||||
let sandbox_policy = exec_req.compatibility_sandbox_policy();
|
||||
exec_req.windows_sandbox_filesystem_overrides = if use_windows_elevated_backend {
|
||||
resolve_windows_elevated_filesystem_overrides(
|
||||
exec_req.sandbox,
|
||||
&sandbox_policy,
|
||||
&exec_req.file_system_sandbox_policy,
|
||||
exec_req.network_sandbox_policy,
|
||||
&exec_req.windows_sandbox_policy_cwd,
|
||||
use_windows_elevated_backend,
|
||||
)
|
||||
} else {
|
||||
resolve_windows_restricted_token_filesystem_overrides(
|
||||
exec_req.sandbox,
|
||||
&sandbox_policy,
|
||||
&exec_req.file_system_sandbox_policy,
|
||||
exec_req.network_sandbox_policy,
|
||||
&exec_req.windows_sandbox_policy_cwd,
|
||||
exec_req.windows_sandbox_level,
|
||||
)
|
||||
}?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_raw_output_result(
|
||||
params: ExecParams,
|
||||
network_sandbox_policy: NetworkSandboxPolicy,
|
||||
@@ -648,6 +656,28 @@ async fn exec_windows_sandbox(
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let protected_metadata_targets = windows_sandbox_filesystem_overrides
|
||||
.map(|overrides| {
|
||||
overrides
|
||||
.protected_metadata_targets
|
||||
.iter()
|
||||
.map(|target| {
|
||||
let mode = match target.mode {
|
||||
WindowsProtectedMetadataMode::ExistingDeny => {
|
||||
codex_windows_sandbox::ProtectedMetadataMode::ExistingDeny
|
||||
}
|
||||
WindowsProtectedMetadataMode::MissingCreationMonitor => {
|
||||
codex_windows_sandbox::ProtectedMetadataMode::MissingCreationMonitor
|
||||
}
|
||||
};
|
||||
codex_windows_sandbox::ProtectedMetadataTarget {
|
||||
path: target.path.to_path_buf(),
|
||||
mode,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let spawn_res = tokio::task::spawn_blocking(move || {
|
||||
if use_elevated {
|
||||
run_windows_sandbox_capture_elevated(
|
||||
@@ -666,6 +696,7 @@ async fn exec_windows_sandbox(
|
||||
elevated_read_roots_include_platform_defaults,
|
||||
write_roots_override: elevated_write_roots_override.as_deref(),
|
||||
deny_write_paths_override: &elevated_deny_write_paths,
|
||||
protected_metadata_targets: &protected_metadata_targets,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
@@ -678,6 +709,7 @@ async fn exec_windows_sandbox(
|
||||
env,
|
||||
timeout_ms,
|
||||
&additional_deny_write_paths,
|
||||
&protected_metadata_targets,
|
||||
windows_sandbox_private_desktop,
|
||||
)
|
||||
}
|
||||
@@ -1151,7 +1183,9 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides(
|
||||
}
|
||||
}
|
||||
|
||||
if additional_deny_write_paths.is_empty() {
|
||||
let protected_metadata_targets = windows_protected_metadata_targets(&split_writable_roots)?;
|
||||
|
||||
if additional_deny_write_paths.is_empty() && protected_metadata_targets.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -1163,6 +1197,7 @@ pub(crate) fn resolve_windows_restricted_token_filesystem_overrides(
|
||||
.into_iter()
|
||||
.map(|path| AbsolutePathBuf::from_absolute_path(path).map_err(|err| err.to_string()))
|
||||
.collect::<std::result::Result<_, _>>()?,
|
||||
protected_metadata_targets,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -1283,9 +1318,12 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides(
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
let protected_metadata_targets = windows_protected_metadata_targets(&split_writable_roots)?;
|
||||
|
||||
if read_roots_override.is_none()
|
||||
&& write_roots_override.is_none()
|
||||
&& additional_deny_write_paths.is_empty()
|
||||
&& protected_metadata_targets.is_empty()
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -1296,9 +1334,36 @@ pub(crate) fn resolve_windows_elevated_filesystem_overrides(
|
||||
read_roots_override,
|
||||
write_roots_override,
|
||||
additional_deny_write_paths,
|
||||
protected_metadata_targets,
|
||||
}))
|
||||
}
|
||||
|
||||
fn windows_protected_metadata_targets(
|
||||
writable_roots: &[codex_protocol::protocol::WritableRoot],
|
||||
) -> std::result::Result<Vec<WindowsProtectedMetadataTarget>, String> {
|
||||
let mut targets = BTreeSet::new();
|
||||
for writable_root in writable_roots {
|
||||
for metadata_name in &writable_root.protected_metadata_names {
|
||||
let path =
|
||||
normalize_windows_override_path(writable_root.root.join(metadata_name).as_path())?;
|
||||
let path = AbsolutePathBuf::from_absolute_path(path).map_err(|err| err.to_string())?;
|
||||
targets.insert(WindowsProtectedMetadataTarget {
|
||||
mode: windows_protected_metadata_mode(&path),
|
||||
path,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(targets.into_iter().collect())
|
||||
}
|
||||
|
||||
fn windows_protected_metadata_mode(path: &AbsolutePathBuf) -> WindowsProtectedMetadataMode {
|
||||
if std::fs::symlink_metadata(path.as_path()).is_ok() {
|
||||
return WindowsProtectedMetadataMode::ExistingDeny;
|
||||
}
|
||||
|
||||
WindowsProtectedMetadataMode::MissingCreationMonitor
|
||||
}
|
||||
|
||||
fn has_reopened_writable_descendant(
|
||||
writable_roots: &[codex_protocol::protocol::WritableRoot],
|
||||
) -> bool {
|
||||
|
||||
@@ -663,6 +663,20 @@ fn windows_restricted_token_supports_full_read_split_write_read_carveouts() {
|
||||
read_roots_include_platform_defaults: false,
|
||||
write_roots_override: None,
|
||||
additional_deny_write_paths: expected_deny_write_paths,
|
||||
protected_metadata_targets: vec![
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".agents"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".codex"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".git"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
],
|
||||
}))
|
||||
);
|
||||
}
|
||||
@@ -700,6 +714,7 @@ fn windows_elevated_supports_split_restricted_read_roots() {
|
||||
read_roots_include_platform_defaults: false,
|
||||
write_roots_override: None,
|
||||
additional_deny_write_paths: vec![],
|
||||
protected_metadata_targets: vec![],
|
||||
}))
|
||||
);
|
||||
}
|
||||
@@ -707,6 +722,9 @@ fn windows_elevated_supports_split_restricted_read_roots() {
|
||||
#[test]
|
||||
fn windows_elevated_supports_split_write_read_carveouts() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
let expected_root = dunce::canonicalize(temp_dir.path())
|
||||
.expect("canonical temp dir")
|
||||
.abs();
|
||||
let docs = temp_dir.path().join("docs");
|
||||
std::fs::create_dir_all(&docs).expect("create docs");
|
||||
let expected_docs = dunce::canonicalize(&docs).expect("canonical docs");
|
||||
@@ -757,6 +775,146 @@ fn windows_elevated_supports_split_write_read_carveouts() {
|
||||
codex_utils_absolute_path::AbsolutePathBuf::from_absolute_path(expected_docs)
|
||||
.expect("absolute docs"),
|
||||
],
|
||||
protected_metadata_targets: vec![
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: expected_root.join(".agents"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: expected_root.join(".codex"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: expected_root.join(".git"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_metadata_plan_marks_existing_metadata_for_deny() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
let cwd = dunce::canonicalize(temp_dir.path())
|
||||
.expect("canonical temp dir")
|
||||
.abs();
|
||||
std::fs::create_dir_all(cwd.join(".git").as_path()).expect("create .git");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(
|
||||
/*subpath*/ None,
|
||||
),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
},
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
resolve_windows_elevated_filesystem_overrides(
|
||||
SandboxType::WindowsRestrictedToken,
|
||||
&policy,
|
||||
&file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
&cwd,
|
||||
/*use_windows_elevated_backend*/ true,
|
||||
),
|
||||
Ok(Some(WindowsSandboxFilesystemOverrides {
|
||||
read_roots_override: None,
|
||||
read_roots_include_platform_defaults: false,
|
||||
write_roots_override: None,
|
||||
additional_deny_write_paths: vec![],
|
||||
protected_metadata_targets: vec![
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".agents"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".codex"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".git"),
|
||||
mode: WindowsProtectedMetadataMode::ExistingDeny,
|
||||
},
|
||||
],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_metadata_plan_does_not_materialize_nested_missing_git() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
let repo = dunce::canonicalize(temp_dir.path())
|
||||
.expect("canonical temp dir")
|
||||
.abs();
|
||||
std::fs::create_dir_all(repo.join(".git").as_path()).expect("create parent .git");
|
||||
let cwd = repo.join("child");
|
||||
std::fs::create_dir_all(cwd.as_path()).expect("create child workspace");
|
||||
let policy = SandboxPolicy::WorkspaceWrite {
|
||||
writable_roots: vec![],
|
||||
network_access: false,
|
||||
exclude_tmpdir_env_var: true,
|
||||
exclude_slash_tmp: true,
|
||||
};
|
||||
let file_system_policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::Root,
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Read,
|
||||
},
|
||||
codex_protocol::permissions::FileSystemSandboxEntry {
|
||||
path: codex_protocol::permissions::FileSystemPath::Special {
|
||||
value: codex_protocol::permissions::FileSystemSpecialPath::project_roots(
|
||||
/*subpath*/ None,
|
||||
),
|
||||
},
|
||||
access: codex_protocol::permissions::FileSystemAccessMode::Write,
|
||||
},
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
resolve_windows_elevated_filesystem_overrides(
|
||||
SandboxType::WindowsRestrictedToken,
|
||||
&policy,
|
||||
&file_system_policy,
|
||||
NetworkSandboxPolicy::Restricted,
|
||||
&cwd,
|
||||
/*use_windows_elevated_backend*/ true,
|
||||
),
|
||||
Ok(Some(WindowsSandboxFilesystemOverrides {
|
||||
read_roots_override: None,
|
||||
read_roots_include_platform_defaults: false,
|
||||
write_roots_override: None,
|
||||
additional_deny_write_paths: vec![],
|
||||
protected_metadata_targets: vec![
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".agents"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".codex"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".git"),
|
||||
mode: WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ pub(crate) struct ExecServerEnvConfig {
|
||||
pub(crate) local_policy_env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExecRequest {
|
||||
pub command: Vec<String>,
|
||||
pub cwd: AbsolutePathBuf,
|
||||
|
||||
@@ -11,6 +11,8 @@ use tokio::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::exec::WindowsProtectedMetadataMode;
|
||||
use crate::exec_env::CODEX_THREAD_ID_ENV_VAR;
|
||||
use crate::exec_env::create_env;
|
||||
use crate::exec_policy::ExecApprovalRequest;
|
||||
@@ -163,6 +165,45 @@ fn exec_server_params_for_request(
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_exec_request_for_open_session(
|
||||
request: &ExecRequest,
|
||||
) -> Result<ExecRequest, UnifiedExecError> {
|
||||
let mut request = request.clone();
|
||||
crate::exec::ensure_windows_sandbox_filesystem_overrides(&mut request)
|
||||
.map_err(UnifiedExecError::create_process)?;
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn protected_metadata_targets_for_windows_session(
|
||||
request: &ExecRequest,
|
||||
) -> Vec<codex_windows_sandbox::ProtectedMetadataTarget> {
|
||||
request
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.as_ref()
|
||||
.map(|overrides| {
|
||||
overrides
|
||||
.protected_metadata_targets
|
||||
.iter()
|
||||
.map(|target| {
|
||||
let mode = match target.mode {
|
||||
WindowsProtectedMetadataMode::ExistingDeny => {
|
||||
codex_windows_sandbox::ProtectedMetadataMode::ExistingDeny
|
||||
}
|
||||
WindowsProtectedMetadataMode::MissingCreationMonitor => {
|
||||
codex_windows_sandbox::ProtectedMetadataMode::MissingCreationMonitor
|
||||
}
|
||||
};
|
||||
codex_windows_sandbox::ProtectedMetadataTarget {
|
||||
path: target.path.to_path_buf(),
|
||||
mode,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Borrowed process state prepared for a `write_stdin` or poll operation.
|
||||
struct PreparedProcessHandles {
|
||||
process: Arc<UnifiedExecProcess>,
|
||||
@@ -873,6 +914,7 @@ impl UnifiedExecProcessManager {
|
||||
mut spawn_lifecycle: SpawnLifecycleHandle,
|
||||
environment: &codex_exec_server::Environment,
|
||||
) -> Result<UnifiedExecProcess, UnifiedExecError> {
|
||||
let request = prepare_exec_request_for_open_session(request)?;
|
||||
let inherited_fds = spawn_lifecycle.inherited_fds();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -888,6 +930,8 @@ impl UnifiedExecProcessManager {
|
||||
"windows sandbox: failed to resolve codex_home: {err}"
|
||||
))
|
||||
})?;
|
||||
let protected_metadata_targets =
|
||||
protected_metadata_targets_for_windows_session(&request);
|
||||
let spawned = match request.windows_sandbox_level {
|
||||
codex_protocol::config_types::WindowsSandboxLevel::Elevated => {
|
||||
codex_windows_sandbox::spawn_windows_sandbox_session_elevated(
|
||||
@@ -900,6 +944,7 @@ impl UnifiedExecProcessManager {
|
||||
None,
|
||||
tty,
|
||||
tty,
|
||||
&protected_metadata_targets,
|
||||
request.windows_sandbox_private_desktop,
|
||||
)
|
||||
.await
|
||||
@@ -916,6 +961,7 @@ impl UnifiedExecProcessManager {
|
||||
None,
|
||||
tty,
|
||||
tty,
|
||||
&protected_metadata_targets,
|
||||
request.windows_sandbox_private_desktop,
|
||||
)
|
||||
.await
|
||||
@@ -938,7 +984,7 @@ impl UnifiedExecProcessManager {
|
||||
|
||||
let started = environment
|
||||
.get_exec_backend()
|
||||
.start(exec_server_params_for_request(process_id, request, tty))
|
||||
.start(exec_server_params_for_request(process_id, &request, tty))
|
||||
.await
|
||||
.map_err(|err| UnifiedExecError::create_process(err.to_string()))?;
|
||||
spawn_lifecycle.after_spawn();
|
||||
|
||||
@@ -135,6 +135,68 @@ fn exec_server_process_id_matches_unified_exec_process_id() {
|
||||
assert_eq!(exec_server_process_id(/*process_id*/ 4321), "4321");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_session_prepares_windows_metadata_overrides_for_unified_exec() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
let cwd: codex_utils_absolute_path::AbsolutePathBuf = dunce::canonicalize(temp_dir.path())
|
||||
.expect("canonical temp dir")
|
||||
.try_into()
|
||||
.expect("absolute temp dir");
|
||||
let permission_profile = codex_protocol::models::PermissionProfile::workspace_write_with(
|
||||
&[],
|
||||
codex_protocol::permissions::NetworkSandboxPolicy::Restricted,
|
||||
/*exclude_tmpdir_env_var*/ true,
|
||||
/*exclude_slash_tmp*/ true,
|
||||
);
|
||||
let (file_system_sandbox_policy, network_sandbox_policy) =
|
||||
permission_profile.to_runtime_permissions();
|
||||
let request = ExecRequest {
|
||||
command: vec![
|
||||
"cmd.exe".to_string(),
|
||||
"/c".to_string(),
|
||||
"echo ok".to_string(),
|
||||
],
|
||||
cwd: cwd.clone(),
|
||||
env: HashMap::new(),
|
||||
exec_server_env_config: None,
|
||||
network: None,
|
||||
expiration: crate::exec::ExecExpiration::DefaultTimeout,
|
||||
capture_policy: crate::exec::ExecCapturePolicy::ShellTool,
|
||||
sandbox: codex_sandboxing::SandboxType::WindowsRestrictedToken,
|
||||
windows_sandbox_policy_cwd: cwd.clone(),
|
||||
windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::RestrictedToken,
|
||||
windows_sandbox_private_desktop: false,
|
||||
permission_profile,
|
||||
file_system_sandbox_policy,
|
||||
network_sandbox_policy,
|
||||
windows_sandbox_filesystem_overrides: None,
|
||||
arg0: None,
|
||||
};
|
||||
|
||||
let prepared = prepare_exec_request_for_open_session(&request).expect("prepare request");
|
||||
let overrides = prepared
|
||||
.windows_sandbox_filesystem_overrides
|
||||
.expect("metadata overrides");
|
||||
|
||||
assert_eq!(
|
||||
overrides.protected_metadata_targets,
|
||||
vec![
|
||||
crate::exec::WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".agents"),
|
||||
mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
crate::exec::WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".codex"),
|
||||
mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
crate::exec::WindowsProtectedMetadataTarget {
|
||||
path: cwd.join(".git"),
|
||||
mode: crate::exec::WindowsProtectedMetadataMode::MissingCreationMonitor,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn network_denial_fallback_message_names_sandbox_network_proxy() {
|
||||
let message = network_denial_message_for_session(/*session*/ None, /*deferred*/ None).await;
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::setup::ProtectedMetadataTarget;
|
||||
|
||||
pub struct ElevatedSandboxCaptureRequest<'a> {
|
||||
pub policy_json_or_preset: &'a str,
|
||||
pub sandbox_policy_cwd: &'a Path,
|
||||
@@ -16,6 +18,7 @@ pub struct ElevatedSandboxCaptureRequest<'a> {
|
||||
pub read_roots_include_platform_defaults: bool,
|
||||
pub write_roots_override: Option<&'a [PathBuf]>,
|
||||
pub deny_write_paths_override: &'a [PathBuf],
|
||||
pub protected_metadata_targets: &'a [ProtectedMetadataTarget],
|
||||
}
|
||||
|
||||
mod windows_impl {
|
||||
@@ -36,6 +39,7 @@ mod windows_impl {
|
||||
use crate::logging::log_success;
|
||||
use crate::policy::SandboxPolicy;
|
||||
use crate::policy::parse_policy;
|
||||
use crate::protected_metadata::prepare_protected_metadata_targets;
|
||||
use crate::runner_client::spawn_runner_transport;
|
||||
use crate::token::convert_string_sid_to_sid;
|
||||
use anyhow::Result;
|
||||
@@ -125,6 +129,7 @@ mod windows_impl {
|
||||
read_roots_include_platform_defaults,
|
||||
write_roots_override,
|
||||
deny_write_paths_override,
|
||||
protected_metadata_targets,
|
||||
} = request;
|
||||
let policy = parse_policy(policy_json_or_preset)?;
|
||||
normalize_null_device_env(&mut env_map);
|
||||
@@ -137,6 +142,8 @@ mod windows_impl {
|
||||
|
||||
let logs_base_dir: Option<&Path> = Some(sandbox_base.as_path());
|
||||
log_start(&command, logs_base_dir);
|
||||
let protected_metadata_guard =
|
||||
prepare_protected_metadata_targets(protected_metadata_targets);
|
||||
let sandbox_creds = require_logon_sandbox_creds(
|
||||
&policy,
|
||||
sandbox_policy_cwd,
|
||||
@@ -147,6 +154,7 @@ mod windows_impl {
|
||||
read_roots_include_platform_defaults,
|
||||
write_roots_override,
|
||||
deny_write_paths_override,
|
||||
protected_metadata_targets,
|
||||
proxy_enforced,
|
||||
)?;
|
||||
// Build capability SID for ACL grants.
|
||||
@@ -210,7 +218,7 @@ mod windows_impl {
|
||||
|
||||
let mut stdout = Vec::new();
|
||||
let mut stderr = Vec::new();
|
||||
let (exit_code, timed_out) = loop {
|
||||
let (mut exit_code, timed_out) = loop {
|
||||
let msg = read_frame(&mut pipe_read)?
|
||||
.ok_or_else(|| anyhow::anyhow!("runner pipe closed before exit"))?;
|
||||
match msg.message {
|
||||
@@ -234,6 +242,12 @@ mod windows_impl {
|
||||
}
|
||||
};
|
||||
|
||||
let protected_metadata_violations =
|
||||
protected_metadata_guard.cleanup_created_monitored_paths()?;
|
||||
if !protected_metadata_violations.is_empty() && exit_code == 0 {
|
||||
exit_code = 1;
|
||||
}
|
||||
|
||||
if exit_code == 0 {
|
||||
log_success(&command, logs_base_dir);
|
||||
} else {
|
||||
|
||||
@@ -140,6 +140,7 @@ pub fn require_logon_sandbox_creds(
|
||||
read_roots_include_platform_defaults: bool,
|
||||
write_roots_override: Option<&[PathBuf]>,
|
||||
deny_write_paths_override: &[PathBuf],
|
||||
protected_metadata_targets: &[crate::setup::ProtectedMetadataTarget],
|
||||
proxy_enforced: bool,
|
||||
) -> Result<SandboxCreds> {
|
||||
let sandbox_dir = crate::setup::sandbox_dir(codex_home);
|
||||
@@ -202,6 +203,7 @@ pub fn require_logon_sandbox_creds(
|
||||
read_roots_include_platform_defaults,
|
||||
write_roots: Some(needed_write.clone()),
|
||||
deny_write_paths: Some(deny_write_paths_override.to_vec()),
|
||||
protected_metadata_targets: Some(protected_metadata_targets.to_vec()),
|
||||
},
|
||||
)?;
|
||||
identity = select_identity(network_identity, codex_home)?;
|
||||
@@ -221,6 +223,7 @@ pub fn require_logon_sandbox_creds(
|
||||
read_roots_include_platform_defaults,
|
||||
write_roots: Some(needed_write),
|
||||
deny_write_paths: Some(deny_write_paths_override.to_vec()),
|
||||
protected_metadata_targets: Some(protected_metadata_targets.to_vec()),
|
||||
},
|
||||
)?;
|
||||
let identity = identity.ok_or_else(|| {
|
||||
|
||||
@@ -168,6 +168,8 @@ pub use process::read_handle_loop;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use process::spawn_process_with_pipes;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use protected_metadata::protected_metadata_existing_deny_paths;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use session::spawn_windows_sandbox_session_elevated;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use session::spawn_windows_sandbox_session_legacy;
|
||||
@@ -256,6 +258,7 @@ pub use stub::run_windows_sandbox_legacy_preflight;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows_impl {
|
||||
use super::ProtectedMetadataTarget;
|
||||
use super::acl::add_allow_ace;
|
||||
use super::acl::add_deny_write_ace;
|
||||
use super::acl::allow_null_device;
|
||||
@@ -269,6 +272,7 @@ mod windows_impl {
|
||||
use super::path_normalization::canonicalize_path;
|
||||
use super::policy::SandboxPolicy;
|
||||
use super::process::create_process_as_user;
|
||||
use super::protected_metadata::prepare_protected_metadata_targets;
|
||||
use super::sandbox_utils::ensure_codex_home_exists;
|
||||
use super::spawn_prep::prepare_legacy_spawn_context;
|
||||
use super::token::convert_string_sid_to_sid;
|
||||
@@ -348,6 +352,7 @@ mod windows_impl {
|
||||
env_map,
|
||||
timeout_ms,
|
||||
&[],
|
||||
&[],
|
||||
use_private_desktop,
|
||||
)
|
||||
}
|
||||
@@ -362,6 +367,7 @@ mod windows_impl {
|
||||
mut env_map: HashMap<String, String>,
|
||||
timeout_ms: Option<u64>,
|
||||
additional_deny_write_paths: &[PathBuf],
|
||||
protected_metadata_targets: &[ProtectedMetadataTarget],
|
||||
use_private_desktop: bool,
|
||||
) -> Result<CaptureResult> {
|
||||
let common = prepare_legacy_spawn_context(
|
||||
@@ -431,6 +437,11 @@ mod windows_impl {
|
||||
let persist_aces = is_workspace_write;
|
||||
let AllowDenyPaths { allow, mut deny } =
|
||||
compute_allow_paths(&policy, sandbox_policy_cwd, ¤t_dir, &env_map);
|
||||
let protected_metadata_guard =
|
||||
prepare_protected_metadata_targets(protected_metadata_targets);
|
||||
for path in protected_metadata_guard.deny_paths() {
|
||||
deny.insert(path.clone());
|
||||
}
|
||||
for path in additional_deny_write_paths {
|
||||
if path.exists() {
|
||||
deny.insert(path.clone());
|
||||
@@ -581,11 +592,16 @@ mod windows_impl {
|
||||
let _ = t_err.join();
|
||||
let stdout = rx_out.recv().unwrap_or_default();
|
||||
let stderr = rx_err.recv().unwrap_or_default();
|
||||
let exit_code = if timed_out {
|
||||
let mut exit_code = if timed_out {
|
||||
128 + 64
|
||||
} else {
|
||||
exit_code_u32 as i32
|
||||
};
|
||||
let protected_metadata_violations =
|
||||
protected_metadata_guard.cleanup_created_monitored_paths()?;
|
||||
if !protected_metadata_violations.is_empty() && exit_code == 0 {
|
||||
exit_code = 1;
|
||||
}
|
||||
|
||||
if exit_code == 0 {
|
||||
log_success(&command, logs_base_dir);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::setup::ProtectedMetadataMode;
|
||||
use crate::setup::ProtectedMetadataTarget;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use std::collections::HashSet;
|
||||
use std::fs::Metadata;
|
||||
use std::io;
|
||||
use std::os::windows::fs::FileTypeExt;
|
||||
@@ -62,11 +61,36 @@ pub(crate) fn prepare_protected_metadata_targets(
|
||||
}
|
||||
|
||||
pub fn protected_metadata_existing_deny_paths(path: &Path) -> Vec<PathBuf> {
|
||||
if std::fs::symlink_metadata(path).is_ok() {
|
||||
vec![path.to_path_buf()]
|
||||
} else {
|
||||
Vec::new()
|
||||
let Ok(metadata) = std::fs::symlink_metadata(path) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
let mut paths = Vec::new();
|
||||
push_deny_path(&mut paths, &mut seen, path.to_path_buf());
|
||||
|
||||
let file_type = metadata.file_type();
|
||||
if (is_directory_reparse_point(&metadata)
|
||||
|| file_type.is_symlink_dir()
|
||||
|| file_type.is_symlink_file())
|
||||
&& let Ok(target_path) = dunce::canonicalize(path)
|
||||
{
|
||||
push_deny_path(&mut paths, &mut seen, target_path);
|
||||
}
|
||||
|
||||
paths
|
||||
}
|
||||
|
||||
fn push_deny_path(paths: &mut Vec<PathBuf>, seen: &mut HashSet<String>, path: PathBuf) {
|
||||
if seen.insert(path_text_key(&path)) {
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
fn path_text_key(path: &Path) -> String {
|
||||
path.to_string_lossy()
|
||||
.replace('\\', "/")
|
||||
.to_ascii_lowercase()
|
||||
}
|
||||
|
||||
fn existing_metadata_path(path: &Path) -> Result<Option<PathBuf>> {
|
||||
@@ -171,4 +195,36 @@ mod tests {
|
||||
assert!(!target.exists());
|
||||
assert!(!created.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn existing_deny_paths_include_symlink_target() {
|
||||
let temp_dir = tempfile::TempDir::new().expect("tempdir");
|
||||
let target_dir = temp_dir.path().join("target-codex");
|
||||
let symlink_dir = temp_dir.path().join(".codex");
|
||||
std::fs::create_dir_all(&target_dir).expect("create target");
|
||||
if let Err(err) = std::os::windows::fs::symlink_dir(&target_dir, &symlink_dir) {
|
||||
eprintln!("skipping symlink test because symlink creation failed: {err}");
|
||||
return;
|
||||
}
|
||||
|
||||
let guard = prepare_protected_metadata_targets(&[ProtectedMetadataTarget {
|
||||
path: symlink_dir.clone(),
|
||||
mode: ProtectedMetadataMode::ExistingDeny,
|
||||
}]);
|
||||
let deny_paths: Vec<PathBuf> = guard.deny_paths().cloned().collect();
|
||||
let canonical_target = dunce::canonicalize(&target_dir).expect("canonical target");
|
||||
|
||||
assert!(
|
||||
deny_paths
|
||||
.iter()
|
||||
.any(|path| path_text_key(path) == path_text_key(&symlink_dir)),
|
||||
"deny paths should include metadata symlink: {deny_paths:?}"
|
||||
);
|
||||
assert!(
|
||||
deny_paths
|
||||
.iter()
|
||||
.any(|path| path_text_key(path) == path_text_key(&canonical_target)),
|
||||
"deny paths should include symlink target: {deny_paths:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD as BASE64;
|
||||
use codex_otel::StatsigMetricsSettings;
|
||||
use codex_windows_sandbox::LOG_FILE_NAME;
|
||||
use codex_windows_sandbox::ProtectedMetadataMode;
|
||||
use codex_windows_sandbox::ProtectedMetadataTarget;
|
||||
use codex_windows_sandbox::SETUP_VERSION;
|
||||
use codex_windows_sandbox::SetupErrorCode;
|
||||
use codex_windows_sandbox::SetupErrorReport;
|
||||
@@ -24,6 +26,7 @@ use codex_windows_sandbox::is_command_cwd_root;
|
||||
use codex_windows_sandbox::load_or_create_cap_sids;
|
||||
use codex_windows_sandbox::log_note;
|
||||
use codex_windows_sandbox::path_mask_allows;
|
||||
use codex_windows_sandbox::protected_metadata_existing_deny_paths;
|
||||
use codex_windows_sandbox::sandbox_bin_dir;
|
||||
use codex_windows_sandbox::sandbox_dir;
|
||||
use codex_windows_sandbox::sandbox_secrets_dir;
|
||||
@@ -87,6 +90,8 @@ struct Payload {
|
||||
write_roots: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
deny_write_paths: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
protected_metadata_targets: Vec<ProtectedMetadataTarget>,
|
||||
proxy_ports: Vec<u16>,
|
||||
#[serde(default)]
|
||||
allow_local_binding: bool,
|
||||
@@ -816,6 +821,66 @@ fn run_setup_full(payload: &Payload, log: &mut File, sbx_dir: &Path) -> Result<(
|
||||
}
|
||||
}
|
||||
|
||||
for target in &payload.protected_metadata_targets {
|
||||
if !matches!(target.mode, ProtectedMetadataMode::ExistingDeny) {
|
||||
continue;
|
||||
}
|
||||
let deny_paths = protected_metadata_existing_deny_paths(&target.path);
|
||||
if deny_paths.is_empty() {
|
||||
log_line(
|
||||
log,
|
||||
&format!(
|
||||
"protected metadata {} missing during setup; skipping",
|
||||
target.path.display()
|
||||
),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
for path in deny_paths {
|
||||
if !seen_deny_paths.insert(path.clone()) {
|
||||
continue;
|
||||
}
|
||||
if std::fs::symlink_metadata(&path).is_err() {
|
||||
log_line(
|
||||
log,
|
||||
&format!(
|
||||
"protected metadata {} missing during setup; skipping",
|
||||
path.display()
|
||||
),
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let canonical_path = canonicalize_path(&path);
|
||||
let deny_psid = if canonical_path.starts_with(&canonical_command_cwd) {
|
||||
workspace_psid
|
||||
} else {
|
||||
cap_psid
|
||||
};
|
||||
|
||||
match unsafe { add_deny_write_ace(&path, deny_psid) } {
|
||||
Ok(true) => {
|
||||
log_line(
|
||||
log,
|
||||
&format!("applied deny ACE to protect metadata {}", path.display()),
|
||||
)?;
|
||||
}
|
||||
Ok(false) => {}
|
||||
Err(err) => {
|
||||
refresh_errors.push(format!(
|
||||
"metadata deny ACE failed on {}: {err}",
|
||||
path.display()
|
||||
));
|
||||
log_line(
|
||||
log,
|
||||
&format!("metadata deny ACE failed on {}: {err}", path.display()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock_sandbox_dir(
|
||||
&sandbox_bin_dir(&payload.codex_home),
|
||||
&payload.real_user,
|
||||
|
||||
@@ -97,6 +97,7 @@ pub struct SetupRootOverrides {
|
||||
pub read_roots_include_platform_defaults: bool,
|
||||
pub write_roots: Option<Vec<PathBuf>>,
|
||||
pub deny_write_paths: Option<Vec<PathBuf>>,
|
||||
pub protected_metadata_targets: Option<Vec<ProtectedMetadataTarget>>,
|
||||
}
|
||||
|
||||
/// Layer: Windows enforcement request boundary. These targets are projected by
|
||||
@@ -169,6 +170,7 @@ pub fn run_setup_refresh_with_extra_read_roots(
|
||||
read_roots_include_platform_defaults: false,
|
||||
write_roots: Some(Vec::new()),
|
||||
deny_write_paths: None,
|
||||
protected_metadata_targets: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -186,6 +188,8 @@ fn run_setup_refresh_inner(
|
||||
}
|
||||
let (read_roots, write_roots) = build_payload_roots(&request, &overrides);
|
||||
let deny_write_paths = build_payload_deny_write_paths(&request, overrides.deny_write_paths);
|
||||
let protected_metadata_targets =
|
||||
build_payload_protected_metadata_targets(overrides.protected_metadata_targets);
|
||||
let network_identity =
|
||||
SandboxNetworkIdentity::from_policy(request.policy, request.proxy_enforced);
|
||||
let offline_proxy_settings = offline_proxy_settings_from_env(request.env_map, network_identity);
|
||||
@@ -198,6 +202,7 @@ fn run_setup_refresh_inner(
|
||||
read_roots,
|
||||
write_roots,
|
||||
deny_write_paths,
|
||||
protected_metadata_targets,
|
||||
proxy_ports: offline_proxy_settings.proxy_ports,
|
||||
allow_local_binding: offline_proxy_settings.allow_local_binding,
|
||||
otel: None,
|
||||
@@ -436,6 +441,8 @@ struct ElevationPayload {
|
||||
write_roots: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
deny_write_paths: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
protected_metadata_targets: Vec<ProtectedMetadataTarget>,
|
||||
proxy_ports: Vec<u16>,
|
||||
#[serde(default)]
|
||||
allow_local_binding: bool,
|
||||
@@ -738,6 +745,8 @@ pub fn run_elevated_setup(
|
||||
})?;
|
||||
let (read_roots, write_roots) = build_payload_roots(&request, &overrides);
|
||||
let deny_write_paths = build_payload_deny_write_paths(&request, overrides.deny_write_paths);
|
||||
let protected_metadata_targets =
|
||||
build_payload_protected_metadata_targets(overrides.protected_metadata_targets);
|
||||
let network_identity =
|
||||
SandboxNetworkIdentity::from_policy(request.policy, request.proxy_enforced);
|
||||
let offline_proxy_settings = offline_proxy_settings_from_env(request.env_map, network_identity);
|
||||
@@ -750,6 +759,7 @@ pub fn run_elevated_setup(
|
||||
read_roots,
|
||||
write_roots,
|
||||
deny_write_paths,
|
||||
protected_metadata_targets,
|
||||
proxy_ports: offline_proxy_settings.proxy_ports,
|
||||
allow_local_binding: offline_proxy_settings.allow_local_binding,
|
||||
real_user: std::env::var("USERNAME").unwrap_or_else(|_| "Administrators".to_string()),
|
||||
@@ -834,6 +844,12 @@ fn build_payload_deny_write_paths(
|
||||
deny_write_paths
|
||||
}
|
||||
|
||||
fn build_payload_protected_metadata_targets(
|
||||
explicit_targets: Option<Vec<ProtectedMetadataTarget>>,
|
||||
) -> Vec<ProtectedMetadataTarget> {
|
||||
explicit_targets.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn expand_user_profile_root(roots: Vec<PathBuf>) -> Vec<PathBuf> {
|
||||
let Ok(user_profile) = std::env::var("USERPROFILE") else {
|
||||
return roots;
|
||||
@@ -1345,6 +1361,7 @@ mod tests {
|
||||
read_roots_include_platform_defaults: true,
|
||||
write_roots: None,
|
||||
deny_write_paths: None,
|
||||
protected_metadata_targets: None,
|
||||
},
|
||||
);
|
||||
let expected_helper =
|
||||
@@ -1392,6 +1409,7 @@ mod tests {
|
||||
read_roots_include_platform_defaults: false,
|
||||
write_roots: None,
|
||||
deny_write_paths: None,
|
||||
protected_metadata_targets: None,
|
||||
},
|
||||
);
|
||||
let expected_helper =
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::policy::SandboxPolicy;
|
||||
use crate::policy::parse_policy;
|
||||
use crate::sandbox_utils::ensure_codex_home_exists;
|
||||
use crate::sandbox_utils::inject_git_safe_directory;
|
||||
use crate::setup::ProtectedMetadataTarget;
|
||||
use crate::token::convert_string_sid_to_sid;
|
||||
use crate::token::create_readonly_token_with_cap;
|
||||
use crate::token::create_workspace_write_token_with_caps_from;
|
||||
@@ -219,9 +220,11 @@ pub(crate) fn apply_legacy_session_acl_rules(
|
||||
psid_generic: &LocalSid,
|
||||
psid_workspace: Option<&LocalSid>,
|
||||
persist_aces: bool,
|
||||
additional_deny_paths: &[PathBuf],
|
||||
) -> Vec<PathBuf> {
|
||||
let AllowDenyPaths { allow, deny } =
|
||||
let AllowDenyPaths { allow, mut deny } =
|
||||
compute_allow_paths(policy, sandbox_policy_cwd, current_dir, env_map);
|
||||
deny.extend(additional_deny_paths.iter().cloned());
|
||||
let mut guards: Vec<PathBuf> = Vec::new();
|
||||
let canonical_cwd = canonicalize_path(current_dir);
|
||||
unsafe {
|
||||
@@ -264,6 +267,7 @@ pub(crate) fn prepare_elevated_spawn_context(
|
||||
cwd: &Path,
|
||||
env_map: &mut HashMap<String, String>,
|
||||
command: &[String],
|
||||
protected_metadata_targets: &[ProtectedMetadataTarget],
|
||||
) -> Result<ElevatedSpawnContext> {
|
||||
let common = prepare_spawn_context_common(
|
||||
policy_json_or_preset,
|
||||
@@ -298,6 +302,7 @@ pub(crate) fn prepare_elevated_spawn_context(
|
||||
/*read_roots_include_platform_defaults*/ false,
|
||||
write_roots_override,
|
||||
&deny_write_paths,
|
||||
protected_metadata_targets,
|
||||
/*proxy_enforced*/ false,
|
||||
)?;
|
||||
let caps = load_or_create_cap_sids(codex_home)?;
|
||||
|
||||
@@ -7,7 +7,9 @@ use crate::ipc_framed::EmptyPayload;
|
||||
use crate::ipc_framed::FramedMessage;
|
||||
use crate::ipc_framed::Message;
|
||||
use crate::ipc_framed::SpawnRequest;
|
||||
use crate::protected_metadata::prepare_protected_metadata_targets;
|
||||
use crate::runner_client::spawn_runner_transport;
|
||||
use crate::setup::ProtectedMetadataTarget;
|
||||
use crate::spawn_prep::prepare_elevated_spawn_context;
|
||||
use anyhow::Result;
|
||||
use codex_utils_pty::ProcessDriver;
|
||||
@@ -29,6 +31,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated(
|
||||
timeout_ms: Option<u64>,
|
||||
tty: bool,
|
||||
stdin_open: bool,
|
||||
protected_metadata_targets: &[ProtectedMetadataTarget],
|
||||
use_private_desktop: bool,
|
||||
) -> Result<SpawnedProcess> {
|
||||
let elevated = prepare_elevated_spawn_context(
|
||||
@@ -38,8 +41,10 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated(
|
||||
cwd,
|
||||
&mut env_map,
|
||||
&command,
|
||||
protected_metadata_targets,
|
||||
)?;
|
||||
|
||||
let protected_metadata_guard = prepare_protected_metadata_targets(protected_metadata_targets);
|
||||
let spawn_request = SpawnRequest {
|
||||
command: command.clone(),
|
||||
cwd: cwd.to_path_buf(),
|
||||
@@ -99,6 +104,7 @@ pub(crate) async fn spawn_windows_sandbox_session_elevated(
|
||||
stdout_tx,
|
||||
stderr_rx.as_ref().map(|(tx, _rx)| tx.clone()),
|
||||
exit_tx,
|
||||
Some(protected_metadata_guard),
|
||||
);
|
||||
|
||||
Ok(finish_driver_spawn(
|
||||
|
||||
@@ -9,6 +9,9 @@ use crate::process::StderrMode;
|
||||
use crate::process::StdinMode;
|
||||
use crate::process::read_handle_loop;
|
||||
use crate::process::spawn_process_with_pipes;
|
||||
use crate::protected_metadata::ProtectedMetadataGuard;
|
||||
use crate::protected_metadata::prepare_protected_metadata_targets;
|
||||
use crate::setup::ProtectedMetadataTarget;
|
||||
use crate::spawn_prep::LocalSid;
|
||||
use crate::spawn_prep::allow_null_device_for_workspace_write;
|
||||
use crate::spawn_prep::apply_legacy_session_acl_rules;
|
||||
@@ -202,10 +205,11 @@ fn finalize_exit(
|
||||
output_join: std::thread::JoinHandle<()>,
|
||||
guards: Vec<PathBuf>,
|
||||
cap_sid: Option<String>,
|
||||
protected_metadata_guard: ProtectedMetadataGuard,
|
||||
logs_base_dir: Option<&Path>,
|
||||
command: Vec<String>,
|
||||
) {
|
||||
let exit_code = {
|
||||
let mut exit_code = {
|
||||
let mut raw_exit = 1u32;
|
||||
if let Ok(guard) = process_handle.lock()
|
||||
&& let Some(handle) = guard.as_ref()
|
||||
@@ -219,6 +223,21 @@ fn finalize_exit(
|
||||
};
|
||||
|
||||
let _ = output_join.join();
|
||||
let protected_metadata_failure =
|
||||
match protected_metadata_guard.cleanup_created_monitored_paths() {
|
||||
Ok(paths) => {
|
||||
if !paths.is_empty() && exit_code == 0 {
|
||||
exit_code = 1;
|
||||
}
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
if exit_code == 0 {
|
||||
exit_code = 1;
|
||||
}
|
||||
Some(format!("protected metadata cleanup failed: {err:#}"))
|
||||
}
|
||||
};
|
||||
let _ = exit_tx.send(exit_code);
|
||||
|
||||
unsafe {
|
||||
@@ -232,7 +251,9 @@ fn finalize_exit(
|
||||
}
|
||||
}
|
||||
|
||||
if exit_code == 0 {
|
||||
if let Some(message) = protected_metadata_failure {
|
||||
log_failure(&command, &message, logs_base_dir);
|
||||
} else if exit_code == 0 {
|
||||
log_success(&command, logs_base_dir);
|
||||
} else {
|
||||
log_failure(&command, &format!("exit code {exit_code}"), logs_base_dir);
|
||||
@@ -286,6 +307,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy(
|
||||
timeout_ms: Option<u64>,
|
||||
tty: bool,
|
||||
stdin_open: bool,
|
||||
protected_metadata_targets: &[ProtectedMetadataTarget],
|
||||
use_private_desktop: bool,
|
||||
) -> Result<SpawnedProcess> {
|
||||
let common = prepare_legacy_spawn_context(
|
||||
@@ -304,6 +326,9 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy(
|
||||
allow_null_device_for_workspace_write(common.is_workspace_write);
|
||||
|
||||
let persist_aces = common.is_workspace_write;
|
||||
let protected_metadata_guard = prepare_protected_metadata_targets(protected_metadata_targets);
|
||||
let additional_deny_write_paths: Vec<PathBuf> =
|
||||
protected_metadata_guard.deny_paths().cloned().collect();
|
||||
let guards = apply_legacy_session_acl_rules(
|
||||
&common.policy,
|
||||
sandbox_policy_cwd,
|
||||
@@ -312,6 +337,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy(
|
||||
&security.psid_generic,
|
||||
security.psid_workspace.as_ref(),
|
||||
persist_aces,
|
||||
&additional_deny_write_paths,
|
||||
);
|
||||
|
||||
let (writer_tx, writer_rx) = mpsc::channel::<Vec<u8>>(128);
|
||||
@@ -404,6 +430,7 @@ pub(crate) async fn spawn_windows_sandbox_session_legacy(
|
||||
output_join,
|
||||
guards_for_wait,
|
||||
cap_sid_for_wait,
|
||||
protected_metadata_guard,
|
||||
common.logs_base_dir.as_deref(),
|
||||
command_for_wait,
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::ipc_framed::ResizePayload;
|
||||
use crate::ipc_framed::StdinPayload;
|
||||
use crate::ipc_framed::decode_bytes;
|
||||
use crate::ipc_framed::encode_bytes;
|
||||
use crate::protected_metadata::ProtectedMetadataGuard;
|
||||
use anyhow::Result;
|
||||
use codex_utils_pty::ProcessDriver;
|
||||
use codex_utils_pty::SpawnedProcess;
|
||||
@@ -97,6 +98,7 @@ pub(crate) fn start_runner_stdout_reader(
|
||||
stdout_tx: broadcast::Sender<Vec<u8>>,
|
||||
stderr_tx: Option<broadcast::Sender<Vec<u8>>>,
|
||||
exit_tx: oneshot::Sender<i32>,
|
||||
protected_metadata_guard: Option<ProtectedMetadataGuard>,
|
||||
) {
|
||||
std::thread::spawn(move || {
|
||||
loop {
|
||||
@@ -140,7 +142,27 @@ pub(crate) fn start_runner_stdout_reader(
|
||||
}
|
||||
}
|
||||
Message::Exit { payload } => {
|
||||
let _ = exit_tx.send(payload.exit_code);
|
||||
let mut exit_code = payload.exit_code;
|
||||
if let Some(protected_metadata_guard) = protected_metadata_guard {
|
||||
match protected_metadata_guard.cleanup_created_monitored_paths() {
|
||||
Ok(paths) => {
|
||||
if !paths.is_empty() && exit_code == 0 {
|
||||
exit_code = 1;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
send_runner_error(
|
||||
&format!("protected metadata cleanup failed: {err:#}"),
|
||||
&stdout_tx,
|
||||
stderr_tx.as_ref(),
|
||||
);
|
||||
if exit_code == 0 {
|
||||
exit_code = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = exit_tx.send(exit_code);
|
||||
break;
|
||||
}
|
||||
Message::Error { payload } => {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
mod backends;
|
||||
|
||||
use crate::setup::ProtectedMetadataTarget;
|
||||
use anyhow::Result;
|
||||
use codex_utils_pty::SpawnedProcess;
|
||||
use std::collections::HashMap;
|
||||
@@ -25,6 +26,7 @@ pub async fn spawn_windows_sandbox_session_legacy(
|
||||
timeout_ms: Option<u64>,
|
||||
tty: bool,
|
||||
stdin_open: bool,
|
||||
protected_metadata_targets: &[ProtectedMetadataTarget],
|
||||
use_private_desktop: bool,
|
||||
) -> Result<SpawnedProcess> {
|
||||
backends::legacy::spawn_windows_sandbox_session_legacy(
|
||||
@@ -37,6 +39,7 @@ pub async fn spawn_windows_sandbox_session_legacy(
|
||||
timeout_ms,
|
||||
tty,
|
||||
stdin_open,
|
||||
protected_metadata_targets,
|
||||
use_private_desktop,
|
||||
)
|
||||
.await
|
||||
@@ -53,6 +56,7 @@ pub async fn spawn_windows_sandbox_session_elevated(
|
||||
timeout_ms: Option<u64>,
|
||||
tty: bool,
|
||||
stdin_open: bool,
|
||||
protected_metadata_targets: &[ProtectedMetadataTarget],
|
||||
use_private_desktop: bool,
|
||||
) -> Result<SpawnedProcess> {
|
||||
backends::elevated::spawn_windows_sandbox_session_elevated(
|
||||
@@ -65,6 +69,7 @@ pub async fn spawn_windows_sandbox_session_elevated(
|
||||
timeout_ms,
|
||||
tty,
|
||||
stdin_open,
|
||||
protected_metadata_targets,
|
||||
use_private_desktop,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -162,6 +162,7 @@ fn legacy_non_tty_cmd_emits_output() {
|
||||
Some(5_000),
|
||||
/*tty*/ false,
|
||||
/*stdin_open*/ false,
|
||||
&[],
|
||||
/*use_private_desktop*/ true,
|
||||
)
|
||||
.await
|
||||
@@ -202,6 +203,7 @@ fn legacy_non_tty_powershell_emits_output() {
|
||||
Some(5_000),
|
||||
/*tty*/ false,
|
||||
/*stdin_open*/ false,
|
||||
&[],
|
||||
/*use_private_desktop*/ true,
|
||||
)
|
||||
.await
|
||||
@@ -426,6 +428,7 @@ fn legacy_tty_powershell_emits_output_and_accepts_input() {
|
||||
Some(10_000),
|
||||
/*tty*/ true,
|
||||
/*stdin_open*/ true,
|
||||
&[],
|
||||
/*use_private_desktop*/ true,
|
||||
)
|
||||
.await
|
||||
@@ -474,6 +477,7 @@ fn legacy_tty_cmd_emits_output_and_accepts_input() {
|
||||
Some(10_000),
|
||||
/*tty*/ true,
|
||||
/*stdin_open*/ true,
|
||||
&[],
|
||||
/*use_private_desktop*/ true,
|
||||
)
|
||||
.await
|
||||
@@ -525,6 +529,7 @@ fn legacy_tty_cmd_default_desktop_emits_output_and_accepts_input() {
|
||||
Some(10_000),
|
||||
/*tty*/ true,
|
||||
/*stdin_open*/ true,
|
||||
&[],
|
||||
/*use_private_desktop*/ false,
|
||||
)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user