Use compatible PowerShell for elevated Windows sandbox commands (#41227)

## Why

Microsoft Store PowerShell executables under `WindowsApps` can be inaccessible to the dedicated elevated sandbox account.

## What changed

- For local elevated sandbox commands that target Store PowerShell, select the first compatible `pwsh.exe` or `powershell.exe` from `PATH` or the standard fallback locations.
- Continue adding `-NoProfile`, and preserve the selected shell for remote execution and other sandbox modes.

## Testing

Added unit coverage for Store-path filtering, fallback ordering, local replacement, and remote-path preservation.

GitOrigin-RevId: 8ab3d0db925e886879701445040f13902fb1ad24
This commit is contained in:
zm-oai authored and copyberry committed 2026-08-27 23:58:09 +00:00
1 parent dd453867fa
commit 426fa8cdab
3 files changed
+254 -15

No files matched your search

+119 -13
View File
@@ -27,7 +27,6 @@ use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::shell_environment::is_non_inheritable_env_var;
use codex_utils_absolute_path::AbsolutePathBuf;
use std::collections::HashMap;
#[cfg(unix)]
use std::path::Path;
pub(crate) mod apply_patch;
@@ -144,11 +143,34 @@ pub(crate) fn apply_zsh_fork_path_prepend(
runtime_path_prepends.prepend(env, zsh_bin_dir);
}
pub(crate) fn disable_powershell_profile_for_elevated_windows_sandbox(
pub(crate) fn prepare_powershell_command_for_elevated_windows_sandbox(
command: &[String],
shell_type: Option<&ShellType>,
sandbox_requested: bool,
windows_sandbox_level: WindowsSandboxLevel,
environment_is_remote: bool,
) -> Vec<String> {
prepare_powershell_command_for_elevated_windows_sandbox_with_fallback(
command,
shell_type,
sandbox_requested,
windows_sandbox_level,
environment_is_remote,
|path| {
codex_shell_command::shell_detect::fallback_powershell_shell_for_elevated_windows_sandbox(
path,
)
},
)
}
fn prepare_powershell_command_for_elevated_windows_sandbox_with_fallback(
command: &[String],
shell_type: Option<&ShellType>,
sandbox_requested: bool,
windows_sandbox_level: WindowsSandboxLevel,
environment_is_remote: bool,
find_fallback: impl FnOnce(&Path) -> Option<codex_shell_command::shell_detect::DetectedShell>,
) -> Vec<String> {
if shell_type != Some(&ShellType::PowerShell)
|| !sandbox_requested
@@ -158,17 +180,21 @@ pub(crate) fn disable_powershell_profile_for_elevated_windows_sandbox(
return command.to_vec();
}
let mut command = command.to_vec();
if !environment_is_remote && let Some(fallback) = find_fallback(Path::new(&command[0])) {
command[0] = fallback.shell_path.to_string_lossy().to_string();
}
if command[1..]
.iter()
.any(|arg| arg.eq_ignore_ascii_case("-NoProfile"))
{
return command.to_vec();
return command;
}
// The elevated Windows sandbox runs as a dedicated sandbox account while
// HOME/USERPROFILE may still point at the real user profile. Loading
// PowerShell profiles in that mixed context is not a valid login shell.
let mut command = command.to_vec();
command.insert(1, "-NoProfile".to_string());
command
}
@@ -395,7 +421,7 @@ fn shell_single_quote(input: &str) -> String {
}
#[cfg(test)]
mod disable_powershell_profile_tests {
mod prepare_powershell_command_tests {
use super::*;
use pretty_assertions::assert_eq;
@@ -407,11 +433,12 @@ mod disable_powershell_profile_tests {
"Write-Output ok".to_string(),
];
let rewritten = disable_powershell_profile_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
/*environment_is_remote*/ false,
);
assert_eq!(
@@ -433,11 +460,12 @@ mod disable_powershell_profile_tests {
"VwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIABvAGsA".to_string(),
];
let rewritten = disable_powershell_profile_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
/*environment_is_remote*/ false,
);
assert_eq!(
@@ -460,11 +488,12 @@ mod disable_powershell_profile_tests {
"Write-Output ok".to_string(),
];
let rewritten = disable_powershell_profile_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
/*environment_is_remote*/ false,
);
assert_eq!(rewritten, command);
@@ -473,16 +502,18 @@ mod disable_powershell_profile_tests {
#[test]
fn leaves_legacy_restricted_token_backend_alone() {
let command = vec![
"powershell.exe".to_string(),
r"C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.4.0_x64__8wekyb3d8bbwe\pwsh.exe"
.to_string(),
"-Command".to_string(),
"Write-Output ok".to_string(),
];
let rewritten = disable_powershell_profile_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::RestrictedToken,
/*environment_is_remote*/ false,
);
assert_eq!(rewritten, command);
@@ -491,16 +522,18 @@ mod disable_powershell_profile_tests {
#[test]
fn leaves_unsandboxed_attempts_alone() {
let command = vec![
"powershell.exe".to_string(),
r"C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.4.0_x64__8wekyb3d8bbwe\pwsh.exe"
.to_string(),
"-Command".to_string(),
"Write-Output ok".to_string(),
];
let rewritten = disable_powershell_profile_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ false,
WindowsSandboxLevel::Elevated,
/*environment_is_remote*/ false,
);
assert_eq!(rewritten, command);
@@ -514,15 +547,88 @@ mod disable_powershell_profile_tests {
"echo ok".to_string(),
];
let rewritten = disable_powershell_profile_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
&command,
Some(&ShellType::Bash),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
/*environment_is_remote*/ false,
);
assert_eq!(rewritten, command);
}
#[test]
fn local_elevated_powershell_uses_discovered_fallback() {
let command = vec![
r"C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.4.0_x64__8wekyb3d8bbwe\pwsh.exe"
.to_string(),
"-Command".to_string(),
"Write-Output ok".to_string(),
];
let fallback_path = std::path::PathBuf::from(r"C:\Program Files\PowerShell\7\pwsh.exe");
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox_with_fallback(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
/*environment_is_remote*/ false,
|_| {
Some(codex_shell_command::shell_detect::DetectedShell {
shell_type: ShellType::PowerShell,
shell_path: fallback_path.clone(),
})
},
);
assert_eq!(
rewritten,
vec![
fallback_path.to_string_lossy().to_string(),
"-NoProfile".to_string(),
"-Command".to_string(),
"Write-Output ok".to_string(),
]
);
}
#[test]
fn remote_elevated_powershell_keeps_remote_store_path_and_no_profile() {
let command = vec![
r"C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.4.0_x64__8wekyb3d8bbwe\pwsh.exe"
.to_string(),
"-Command".to_string(),
"Write-Output ok".to_string(),
];
let mut discovery_called = false;
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox_with_fallback(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
/*environment_is_remote*/ true,
|_| {
discovery_called = true;
Some(codex_shell_command::shell_detect::DetectedShell {
shell_type: ShellType::PowerShell,
shell_path: std::path::PathBuf::from(r"C:\Program Files\PowerShell\7\pwsh.exe"),
})
},
);
assert!(!discovery_called);
assert_eq!(
rewritten,
vec![
command[0].clone(),
"-NoProfile".to_string(),
"-Command".to_string(),
"Write-Output ok".to_string(),
]
);
}
}
#[cfg(all(test, unix))]
@@ -20,9 +20,9 @@ use crate::tools::network_approval::NetworkApprovalSpec;
use crate::tools::runtimes::RuntimePathPrepends;
#[cfg(unix)]
use crate::tools::runtimes::apply_zsh_fork_path_prepend;
use crate::tools::runtimes::disable_powershell_profile_for_elevated_windows_sandbox;
use crate::tools::runtimes::exec_env_for_sandbox_permissions;
use crate::tools::runtimes::maybe_wrap_shell_lc_with_snapshot;
use crate::tools::runtimes::prepare_powershell_command_for_elevated_windows_sandbox;
use crate::tools::runtimes::zsh_fork;
use crate::tools::sandboxing::Approvable;
use crate::tools::sandboxing::ApprovalAction;
@@ -399,11 +399,12 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
*script = format!("{exports}\n{script}");
}
}
let command = disable_powershell_profile_for_elevated_windows_sandbox(
let command = prepare_powershell_command_for_elevated_windows_sandbox(
&command,
Some(&req.shell_type),
attempt.sandbox_requested,
attempt.windows_sandbox_level,
environment_is_remote,
);
let command = if matches!(req.shell_type, ShellType::PowerShell) {
prefix_powershell_script_with_utf8(&command)