[codex] Fix Windows sandbox runtime ACL refresh (#28943)

## Why

Codex Desktop repairs sandbox-user read/execute access for binaries
copied to `%LOCALAPPDATA%\OpenAI\Codex\bin`, but Computer Use launches
its bundled Node runtime from `%LOCALAPPDATA%\OpenAI\Codex\runtimes`.

On fresh Windows installations, `CodexSandboxUsers` may therefore be
unable to execute the bundled Node binary. The command runner starts,
but `CreateProcessAsUserW` fails with error 5 (`ACCESS_DENIED`), causing
the Node REPL to exit before Computer Use can discover applications.

This is a follow-up to #21564, which added the original runtime `bin`
ACL repair.

## What changed

- Expand the Codex Desktop runtime ACL roots from only `bin` to both
`bin` and `runtimes`.
- Apply the existing inherited read/execute ACL repair to each runtime
directory when it exists.
- Rename the setup helper to reflect that it now handles multiple
runtime paths.

## Validation

- `cargo fmt -- --check`
- `just test -p codex-windows-sandbox` was run: 113 tests passed and
five environment-dependent legacy execution tests failed because
`CreateRestrictedToken` returned error 87.
This commit is contained in:
iceweasel-oai
2026-06-18 11:04:30 -07:00
committed by GitHub
Unverified
parent 4c7228e423
commit afbb69a2fb
2 changed files with 76 additions and 71 deletions
@@ -819,7 +819,7 @@ fn run_setup_full(payload: &Payload, log: &mut dyn Write, sbx_dir: &Path) -> Res
}
if refresh_only {
setup_runtime_bin::ensure_codex_app_runtime_bin_readable(
setup_runtime_bin::ensure_codex_app_runtime_paths_readable(
sandbox_group_psid,
&mut refresh_errors,
log,
@@ -10,83 +10,88 @@ use windows_sys::Win32::Security::OBJECT_INHERIT_ACE;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_EXECUTE;
use windows_sys::Win32::Storage::FileSystem::FILE_GENERIC_READ;
pub(super) fn ensure_codex_app_runtime_bin_readable(
pub(super) fn ensure_codex_app_runtime_paths_readable(
sandbox_group_psid: *mut c_void,
refresh_errors: &mut Vec<String>,
log: &mut dyn Write,
) -> Result<()> {
let local_app_data = std::env::var_os("LOCALAPPDATA")
let local_app_data = local_app_data_root();
let Some(local_app_data) = local_app_data else {
return Ok(());
};
let read_execute_mask = FILE_GENERIC_READ | FILE_GENERIC_EXECUTE;
let codex_root = local_app_data.join("OpenAI").join("Codex");
for runtime_path in [codex_root.join("bin"), codex_root.join("runtimes")] {
if !runtime_path.is_dir() {
continue;
}
let has_access = match path_mask_allows(
&runtime_path,
&[sandbox_group_psid],
read_execute_mask,
/*require_all_bits*/ true,
) {
Ok(has_access) => has_access,
Err(err) => {
refresh_errors.push(format!(
"runtime read/execute mask check failed on {} for sandbox_group: {err}",
runtime_path.display()
));
super::log_line(
log,
&format!(
"runtime read/execute mask check failed on {} for sandbox_group: {err}; continuing",
runtime_path.display()
),
)?;
false
}
};
if has_access {
continue;
}
super::log_line(
log,
&format!(
"granting read/execute ACE to {} for sandbox users",
runtime_path.display()
),
)?;
let result = unsafe {
ensure_allow_mask_aces_with_inheritance(
&runtime_path,
&[sandbox_group_psid],
read_execute_mask,
OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE,
)
};
if let Err(err) = result {
refresh_errors.push(format!(
"grant read/execute ACE failed on {} for sandbox_group: {err}",
runtime_path.display()
));
super::log_line(
log,
&format!(
"grant read/execute ACE failed on {} for sandbox_group: {err}",
runtime_path.display()
),
)?;
}
}
Ok(())
}
fn local_app_data_root() -> Option<PathBuf> {
std::env::var_os("LOCALAPPDATA")
.map(PathBuf::from)
.or_else(|| {
std::env::var_os("USERPROFILE")
.map(PathBuf::from)
.map(|profile| profile.join("AppData").join("Local"))
});
let Some(local_app_data) = local_app_data else {
return Ok(());
};
// Codex desktop copies bundled Windows binaries out of WindowsApps to this
// fixed LocalAppData cache before launching codex.exe.
let runtime_bin_dir = local_app_data.join("OpenAI").join("Codex").join("bin");
if !runtime_bin_dir.is_dir() {
return Ok(());
}
let read_execute_mask = FILE_GENERIC_READ | FILE_GENERIC_EXECUTE;
let has_access = match path_mask_allows(
&runtime_bin_dir,
&[sandbox_group_psid],
read_execute_mask,
/*require_all_bits*/ true,
) {
Ok(has_access) => has_access,
Err(err) => {
refresh_errors.push(format!(
"runtime bin read/execute mask check failed on {} for sandbox_group: {err}",
runtime_bin_dir.display()
));
super::log_line(
log,
&format!(
"runtime bin read/execute mask check failed on {} for sandbox_group: {err}; continuing",
runtime_bin_dir.display()
),
)?;
false
}
};
if has_access {
return Ok(());
}
super::log_line(
log,
&format!(
"granting read/execute ACE to {} for sandbox users",
runtime_bin_dir.display()
),
)?;
let result = unsafe {
ensure_allow_mask_aces_with_inheritance(
&runtime_bin_dir,
&[sandbox_group_psid],
read_execute_mask,
OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE,
)
};
if let Err(err) = result {
refresh_errors.push(format!(
"grant read/execute ACE failed on {} for sandbox_group: {err}",
runtime_bin_dir.display()
));
super::log_line(
log,
&format!(
"grant read/execute ACE failed on {} for sandbox_group: {err}",
runtime_bin_dir.display()
),
)?;
}
Ok(())
})
}