Backport MXC PowerShell fix and safe alpha publishing to 0.159.0-alpha.12 (#50061)

Backport two reviewed upstream fixes onto the exact content parent of
0.159.0-alpha.12 for the desktop 260930 train:

- Public #49019 (internal #10589),
4fd5745e84: reuse unpackaged PowerShell
fallback for local MXC. Remote, unsandboxed, restricted-token handling
and elevated-only -NoProfile behavior are preserved.
- Public #49704, 5602705c96: keep newer
npm alpha pointers unchanged when publishing an older frozen hotfix. The
existing OIDC publisher uses version-specific tags; registry lookup
failure aborts publication. No new credentials or permission changes.

Both patches apply cleanly and match their upstream patch IDs. Eight
files total, +360/-71; no extra dependencies or unrelated product
changes. Frozen base: 1cc7e23612. Intended
publication: 0.159.0-alpha.12.1 through the exact-SHA-guarded branch
publisher.

Validation: just fmt and whitespace checks passed; all 50 focused
PowerShell library tests passed; all 17 release/tag-selection tests
passed. Initial product-only head passed all three production build
checks (Linux/macOS/Windows); updated-head checks are pending. Native
MXC execution is not claimed by Linux validation. The included Windows
integration test skips if MXC is unavailable.

Initial CI manifest-policy failure reproduces on the exact frozen base;
Codespell and cargo-shear failures concern unchanged files. No unrelated
lint fixes included.

---------

Co-authored-by: Ian MacLeod <imac@openai.com>
This commit is contained in:
andrewgu-oai
2026-10-01 11:40:51 -07:00
committed by GitHub
co-authored by Ian MacLeod
parent 1cc7e23612
commit 51215e9f88
8 changed files with 360 additions and 71 deletions
+43
View File
@@ -0,0 +1,43 @@
"""Choose a forward-only npm alpha dist-tag under rust-release's concurrency lock."""
import json
from pathlib import Path
import subprocess
import sys
import tarfile
from releases import should_update_version
def npm_alpha_tag(tarball: Path, version: str, tag: str) -> str:
# Read the actual package name: platform archives share @openai/codex,
# while the SDK and proxy have independent alpha pointers.
with tarfile.open(tarball, "r:gz") as archive:
with archive.extractfile("package/package.json") as manifest:
package = json.load(manifest)["name"]
result = subprocess.run(
["npm", "view", package, "dist-tags", "--json", "--prefer-online"],
check=True,
capture_output=True,
text=True,
)
current = json.loads(result.stdout).get(tag, "")
if tag != "alpha":
# build_npm_package.py appends -<platform> to the release version.
current = current.removesuffix(f"-{tag.removeprefix('alpha-')}")
if should_update_version(version, current):
return tag
# npm publish always writes a tag. Keep this version installable without
# changing alpha, alpha-<platform>, or latest. Prefix avoids semver tags,
# which npm rejects. Include the platform for versions of the same package.
print(
f"Keeping {package}@{tag} at {current}; publishing {version} separately",
file=sys.stderr,
)
return f"release-{version}-{tag}"
if __name__ == "__main__":
print(npm_alpha_tag(Path(sys.argv[1]), sys.argv[2], sys.argv[3]))
+95
View File
@@ -1,5 +1,13 @@
import io
import json
from pathlib import Path
import subprocess
import tarfile
import tempfile
import unittest
from unittest.mock import patch
from npm_alpha_tag import npm_alpha_tag
from releases import is_valid_release_version, should_update_version
@@ -52,5 +60,92 @@ class VersionComparisonTest(unittest.TestCase):
self.assertRaises(ValueError, should_update_version, "0.123", "")
class NpmAlphaTagTest(unittest.TestCase):
def test_published_tags_across_packages_and_platforms(self) -> None:
# Each package/tag reads its own registry value, even after a partially
# published release. Also exercise a missing pointer and dotted hotfix.
cases = [
(
"@openai/codex",
"alpha",
"0.158.0-alpha.10",
"0.157.0-alpha.11.1",
"release-0.157.0-alpha.11.1-alpha",
),
(
"@openai/codex",
"alpha-linux-x64",
"0.158.0-alpha.10-linux-x64",
"0.157.0-alpha.11.1",
"release-0.157.0-alpha.11.1-alpha-linux-x64",
),
(
"@openai/codex",
"alpha-win32-arm64",
"0.158.0-alpha.9-win32-arm64",
"0.158.0-alpha.10",
"alpha-win32-arm64",
),
(
"@openai/codex-sdk",
"alpha",
"0.158.0-alpha.10",
"0.158.0-alpha.10.1",
"alpha",
),
(
"@openai/codex-responses-api-proxy",
"alpha",
"0.159.0-alpha.1",
"0.158.0-alpha.10.1",
"release-0.158.0-alpha.10.1-alpha",
),
("@openai/codex-sdk", "alpha", None, "0.158.0-alpha.10", "alpha"),
]
with tempfile.TemporaryDirectory() as tmpdir:
tarball = Path(tmpdir) / "package.tgz"
for package, tag, current, version, expected in cases:
with self.subTest(package=package, tag=tag, version=version):
content = json.dumps({"name": package}).encode()
with tarfile.open(tarball, "w:gz") as archive:
info = tarfile.TarInfo("package/package.json")
info.size = len(content)
archive.addfile(info, io.BytesIO(content))
tags = {tag: current} if current is not None else {}
result = subprocess.CompletedProcess([], 0, json.dumps(tags))
with patch(
"npm_alpha_tag.subprocess.run", return_value=result
) as run:
self.assertEqual(npm_alpha_tag(tarball, version, tag), expected)
run.assert_called_once_with(
[
"npm",
"view",
package,
"dist-tags",
"--json",
"--prefer-online",
],
check=True,
capture_output=True,
text=True,
)
def test_registry_failure_prevents_publish(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
tarball = Path(tmpdir) / "package.tgz"
content = b'{"name": "@openai/codex"}'
with tarfile.open(tarball, "w:gz") as archive:
info = tarfile.TarInfo("package/package.json")
info.size = len(content)
archive.addfile(info, io.BytesIO(content))
with patch(
"npm_alpha_tag.subprocess.run",
side_effect=subprocess.CalledProcessError(1, "npm"),
):
with self.assertRaises(subprocess.CalledProcessError):
npm_alpha_tag(tarball, "0.158.0-alpha.10", "alpha")
if __name__ == "__main__":
unittest.main()
+11
View File
@@ -1778,6 +1778,11 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
@@ -1898,6 +1903,12 @@ jobs:
;;
esac
# The workflow concurrency group holds the lock from this lookup
# through publish, including concurrent alpha/hotfix release runs.
if [[ "${NPM_TAG}" == "alpha" ]]; then
tag="$(python3 .github/scripts/npm_alpha_tag.py "${tarball}" "${VERSION}" "${tag}")"
fi
publish_cmd=(npm publish "${GITHUB_WORKSPACE}/${tarball}")
if [[ -n "${tag}" ]]; then
publish_cmd+=(--tag "${tag}")
+61 -54
View File
@@ -13,6 +13,7 @@ use crate::shell::Shell;
use crate::shell::ShellType;
use codex_apply_patch::CODEX_APPLY_PATCH_PRESERVE_LINE_ENDINGS_ENV_VAR;
use codex_core_plugins::PLUGIN_METRICS_OUTPUT_ENV_VAR;
use codex_file_system::WindowsSandboxSelection;
#[cfg(unix)]
use codex_install_context::InstallContext;
#[cfg(target_os = "macos")]
@@ -25,7 +26,6 @@ use codex_network_proxy::PROXY_ENV_KEYS;
use codex_network_proxy::PROXY_GIT_SSH_COMMAND_ENV_KEY;
pub(crate) use codex_network_proxy::is_managed_proxy_env_var;
pub(crate) use codex_network_proxy::strip_managed_proxy_env;
use codex_protocol::config_types::WindowsSandboxLevel;
use codex_protocol::shell_environment::is_non_inheritable_env_var;
use codex_shell_command::shell_snapshot::posix_env_path_expansion_function;
use codex_utils_absolute_path::AbsolutePathBuf;
@@ -152,38 +152,37 @@ pub(crate) fn apply_zsh_fork_path_prepend(
runtime_path_prepends.prepend(env, zsh_bin_dir);
}
pub(crate) fn prepare_powershell_command_for_elevated_windows_sandbox(
pub(crate) fn prepare_powershell_command_for_windows_sandbox(
command: &[String],
shell_type: Option<&ShellType>,
sandbox_requested: bool,
windows_sandbox_level: WindowsSandboxLevel,
windows_sandbox: WindowsSandboxSelection,
environment_is_remote: bool,
) -> Vec<String> {
prepare_powershell_command_for_elevated_windows_sandbox_with_fallback(
prepare_powershell_command_for_windows_sandbox_with_fallback(
command,
shell_type,
sandbox_requested,
windows_sandbox_level,
windows_sandbox,
environment_is_remote,
|path| {
codex_shell_command::shell_detect::fallback_powershell_shell_for_elevated_windows_sandbox(
path,
)
},
codex_shell_command::shell_detect::fallback_powershell_shell_for_windows_sandbox,
)
}
fn prepare_powershell_command_for_elevated_windows_sandbox_with_fallback(
fn prepare_powershell_command_for_windows_sandbox_with_fallback(
command: &[String],
shell_type: Option<&ShellType>,
sandbox_requested: bool,
windows_sandbox_level: WindowsSandboxLevel,
windows_sandbox: WindowsSandboxSelection,
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
|| windows_sandbox_level != WindowsSandboxLevel::Elevated
|| !matches!(
windows_sandbox,
WindowsSandboxSelection::Elevated | WindowsSandboxSelection::Mxc
)
|| command.is_empty()
{
return command.to_vec();
@@ -194,9 +193,10 @@ fn prepare_powershell_command_for_elevated_windows_sandbox_with_fallback(
command[0] = fallback.shell_path.to_string_lossy().to_string();
}
if command[1..]
.iter()
.any(|arg| arg.eq_ignore_ascii_case("-NoProfile"))
if windows_sandbox != WindowsSandboxSelection::Elevated
|| command[1..]
.iter()
.any(|arg| arg.eq_ignore_ascii_case("-NoProfile"))
{
return command;
}
@@ -716,11 +716,11 @@ mod prepare_powershell_command_tests {
"Write-Output ok".to_string(),
];
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_windows_sandbox(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
WindowsSandboxSelection::Elevated,
/*environment_is_remote*/ false,
);
@@ -743,11 +743,11 @@ mod prepare_powershell_command_tests {
"VwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIABvAGsA".to_string(),
];
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_windows_sandbox(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
WindowsSandboxSelection::Elevated,
/*environment_is_remote*/ false,
);
@@ -762,8 +762,9 @@ mod prepare_powershell_command_tests {
);
}
#[test]
fn preserves_existing_no_profile() {
#[test_case::test_case(WindowsSandboxSelection::Elevated; "elevated")]
#[test_case::test_case(WindowsSandboxSelection::Mxc; "mxc")]
fn preserves_existing_no_profile(windows_sandbox: WindowsSandboxSelection) {
let command = vec![
"pwsh.exe".to_string(),
"-NoProfile".to_string(),
@@ -771,11 +772,11 @@ mod prepare_powershell_command_tests {
"Write-Output ok".to_string(),
];
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_windows_sandbox(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
windows_sandbox,
/*environment_is_remote*/ false,
);
@@ -791,19 +792,20 @@ mod prepare_powershell_command_tests {
"Write-Output ok".to_string(),
];
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_windows_sandbox(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::RestrictedToken,
WindowsSandboxSelection::RestrictedToken,
/*environment_is_remote*/ false,
);
assert_eq!(rewritten, command);
}
#[test]
fn leaves_unsandboxed_attempts_alone() {
#[test_case::test_case(WindowsSandboxSelection::Elevated; "elevated")]
#[test_case::test_case(WindowsSandboxSelection::Mxc; "mxc")]
fn leaves_unsandboxed_attempts_alone(windows_sandbox: WindowsSandboxSelection) {
let command = vec![
r"C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.4.0_x64__8wekyb3d8bbwe\pwsh.exe"
.to_string(),
@@ -811,37 +813,42 @@ mod prepare_powershell_command_tests {
"Write-Output ok".to_string(),
];
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_windows_sandbox(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ false,
WindowsSandboxLevel::Elevated,
windows_sandbox,
/*environment_is_remote*/ false,
);
assert_eq!(rewritten, command);
}
#[test]
fn leaves_non_powershell_alone() {
#[test_case::test_case(WindowsSandboxSelection::Elevated; "elevated")]
#[test_case::test_case(WindowsSandboxSelection::Mxc; "mxc")]
fn leaves_non_powershell_alone(windows_sandbox: WindowsSandboxSelection) {
let command = vec![
"/bin/bash".to_string(),
"-lc".to_string(),
"echo ok".to_string(),
];
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox(
let rewritten = prepare_powershell_command_for_windows_sandbox(
&command,
Some(&ShellType::Bash),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
windows_sandbox,
/*environment_is_remote*/ false,
);
assert_eq!(rewritten, command);
}
#[test]
fn local_elevated_powershell_uses_discovered_fallback() {
#[test_case::test_case(WindowsSandboxSelection::Elevated, &["-NoProfile", "-Command", "Write-Output ok"]; "elevated")]
#[test_case::test_case(WindowsSandboxSelection::Mxc, &["-Command", "Write-Output ok"]; "mxc")]
fn local_sandboxed_powershell_uses_discovered_fallback(
windows_sandbox: WindowsSandboxSelection,
expected_args: &[&str],
) {
let command = vec![
r"C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.4.0_x64__8wekyb3d8bbwe\pwsh.exe"
.to_string(),
@@ -850,11 +857,11 @@ mod prepare_powershell_command_tests {
];
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(
let rewritten = prepare_powershell_command_for_windows_sandbox_with_fallback(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
windows_sandbox,
/*environment_is_remote*/ false,
|_| {
Some(codex_shell_command::shell_detect::DetectedShell {
@@ -866,17 +873,19 @@ mod prepare_powershell_command_tests {
assert_eq!(
rewritten,
vec![
fallback_path.to_string_lossy().to_string(),
"-NoProfile".to_string(),
"-Command".to_string(),
"Write-Output ok".to_string(),
]
vec![fallback_path.to_string_lossy().to_string()]
.into_iter()
.chain(expected_args.iter().map(std::string::ToString::to_string))
.collect::<Vec<_>>()
);
}
#[test]
fn remote_elevated_powershell_keeps_remote_store_path_and_no_profile() {
#[test_case::test_case(WindowsSandboxSelection::Elevated, &["-NoProfile", "-Command", "Write-Output ok"]; "elevated")]
#[test_case::test_case(WindowsSandboxSelection::Mxc, &["-Command", "Write-Output ok"]; "mxc")]
fn remote_sandboxed_powershell_keeps_remote_store_path(
windows_sandbox: WindowsSandboxSelection,
expected_args: &[&str],
) {
let command = vec![
r"C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.4.0_x64__8wekyb3d8bbwe\pwsh.exe"
.to_string(),
@@ -885,11 +894,11 @@ mod prepare_powershell_command_tests {
];
let mut discovery_called = false;
let rewritten = prepare_powershell_command_for_elevated_windows_sandbox_with_fallback(
let rewritten = prepare_powershell_command_for_windows_sandbox_with_fallback(
&command,
Some(&ShellType::PowerShell),
/*sandbox_requested*/ true,
WindowsSandboxLevel::Elevated,
windows_sandbox,
/*environment_is_remote*/ true,
|_| {
discovery_called = true;
@@ -903,12 +912,10 @@ mod prepare_powershell_command_tests {
assert!(!discovery_called);
assert_eq!(
rewritten,
vec![
command[0].clone(),
"-NoProfile".to_string(),
"-Command".to_string(),
"Write-Output ok".to_string(),
]
vec![command[0].clone()]
.into_iter()
.chain(expected_args.iter().map(std::string::ToString::to_string))
.collect::<Vec<_>>()
);
}
}
@@ -24,7 +24,7 @@ use crate::tools::runtimes::RuntimePathPrepends;
use crate::tools::runtimes::apply_zsh_fork_path_prepend;
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::prepare_powershell_command_for_windows_sandbox;
use crate::tools::runtimes::zsh_fork;
use crate::tools::sandboxing::Approvable;
use crate::tools::sandboxing::ApprovalAction;
@@ -34,6 +34,7 @@ use crate::tools::sandboxing::Sandboxable;
use crate::tools::sandboxing::ToolCtx;
use crate::tools::sandboxing::ToolError;
use crate::tools::sandboxing::ToolRuntime;
use crate::tools::sandboxing::executor_windows_sandbox_selection;
use crate::tools::sandboxing::managed_network_for_sandbox_permissions;
use crate::tools::sandboxing::sandbox_permissions_preserving_denied_reads;
use crate::unified_exec::NoopSpawnLifecycle;
@@ -571,11 +572,17 @@ impl<'a> ToolRuntime<UnifiedExecRequest, UnifiedExecAttempt> for UnifiedExecRunt
{
network.restore_and_disable_brokered_credentials(&mut env, &mut command);
}
let command = prepare_powershell_command_for_elevated_windows_sandbox(
// The executor selection is Disabled for non-Windows target paths, so this
// preparation is a no-op for PowerShell on other platforms.
let command = prepare_powershell_command_for_windows_sandbox(
&command,
Some(&req.shell_type),
attempt.sandbox_requested,
attempt.windows_sandbox_level,
executor_windows_sandbox_selection(
attempt.windows_sandbox_type,
attempt.windows_sandbox_level,
attempt.sandbox_cwd,
),
environment_is_remote,
);
let command = if matches!(req.shell_type, ShellType::PowerShell) {
+6
View File
@@ -26,6 +26,9 @@ pub static CODEX_ALIASES_TEMP_DIR: Option<TestBinaryDispatchGuard> = {
if argv1 == Some(CODEX_FS_HELPER_ARG1) {
return TestBinaryDispatchMode::DispatchArg0Only;
}
if argv1 == Some(codex_sandboxing::CODEX_WINDOWS_MXC_ARG1) {
return TestBinaryDispatchMode::DispatchArg0Only;
}
if exe_name == CODEX_LINUX_SANDBOX_ARG0 {
return TestBinaryDispatchMode::DispatchArg0Only;
}
@@ -218,6 +221,9 @@ mod turn_state;
mod unified_exec;
#[path = "unified_exec_launch_failure_tests.rs"]
mod unified_exec_launch_failure;
#[cfg(windows)]
#[path = "unified_exec_mxc_powershell_tests.rs"]
mod unified_exec_mxc_powershell;
mod unified_exec_process_events;
mod unified_exec_stdin_approval;
mod unified_exec_stdin_review_size;
@@ -0,0 +1,120 @@
//! Exercises local PowerShell discovery before launching commands in native MXC.
use anyhow::Context;
use anyhow::Result;
use codex_protocol::models::PermissionProfile;
use codex_shell_command::shell_detect::DetectedShell;
use codex_shell_command::shell_detect::ShellType;
use core_test_support::responses::mount_function_call_agent_response;
use core_test_support::test_codex::TestCodexHarness;
use core_test_support::test_codex::test_codex;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::path::PathBuf;
use std::time::Duration;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn mxc_replaces_store_powershell_before_launch() -> Result<()> {
if !codex_sandboxing::windows_mxc_available() {
eprintln!("skipping test: native MXC is unavailable on this host");
return Ok(());
}
// The Store executable need not exist: the fallback must replace it before launch.
let shell = DetectedShell {
shell_type: ShellType::PowerShell,
shell_path: PathBuf::from(
r"C:\Program Files\WindowsApps\Microsoft.PowerShell_test\pwsh.exe",
),
};
let builder = test_codex()
.with_user_shell(shell.into())
.with_pre_build_hook(|home| {
std::fs::write(home.join("config.toml"), "[windows]\nsandbox = \"mxc\"\n")
.expect("write MXC config");
})
.with_config(|config| {
config.codex_self_exe = Some(std::env::current_exe().expect("test executable"));
});
// Discovery is intentionally host-local; remote execution must not use the host's shell.
let harness = TestCodexHarness::with_builder(builder).await?;
let mock = mount_function_call_agent_response(
harness.server(),
"mxc-powershell",
&json!({
"cmd": "Set-Content -LiteralPath fallback.txt -Value mxc-powershell -NoNewline",
"login": false,
"yield_time_ms": 10_000,
})
.to_string(),
"exec_command",
)
.await;
harness
.submit_with_permission_profile(
"Write a file using PowerShell in the sandbox.",
PermissionProfile::workspace_write(),
)
.await?;
let request = mock.completion.single_request();
let tool_output = request.function_call_output("mxc-powershell");
let mut output = tool_output["output"]
.as_str()
.expect("command output")
.to_owned();
// A slow MXC launch may outlive exec_command's yield window.
tokio::time::timeout(Duration::from_secs(120), async {
for poll in 0.. {
let Some(session_id) = output
.lines()
.find_map(|line| line.strip_prefix("Process running with session ID "))
else {
break;
};
let session_id = session_id.parse::<u32>()?;
let call_id = format!("mxc-powershell-poll-{poll}");
let poll_mock = mount_function_call_agent_response(
harness.server(),
&call_id,
&json!({
"session_id": session_id,
"chars": "",
"yield_time_ms": 1_000,
})
.to_string(),
"write_stdin",
)
.await;
harness
.submit_with_permission_profile(
"Wait for the PowerShell command to finish.",
PermissionProfile::workspace_write(),
)
.await?;
let request = poll_mock.completion.single_request();
let tool_output = request.function_call_output(&call_id);
output = tool_output["output"]
.as_str()
.expect("poll output")
.to_owned();
}
Ok::<_, anyhow::Error>(())
})
.await
.context("timed out waiting for MXC PowerShell to exit")??;
assert!(output.contains("Process exited with code 0"), "{output}");
assert_eq!(
std::fs::read_to_string(harness.path("fallback.txt"))?,
"mxc-powershell"
);
let metadata: serde_json::Value = serde_json::from_str(
&mock
.function_call
.single_request()
.header("x-codex-turn-metadata")
.expect("turn metadata"),
)?;
assert_eq!(metadata["sandbox"], "windows_mxc");
Ok(())
}
+14 -14
View File
@@ -132,7 +132,7 @@ fn file_exists(path: &std::path::Path) -> Option<PathBuf> {
}
}
// Store PowerShell can be inaccessible to the elevated sandbox account;
// Store PowerShell cannot run under MXC and can be inaccessible to the elevated sandbox account;
// WindowsApps also contains valid Codex frameworks.
fn is_inaccessible_windows_apps_powershell_path(path: &std::path::Path) -> bool {
path.as_os_str()
@@ -156,7 +156,7 @@ fn targets_inaccessible_windows_apps_powershell(path: &std::path::Path) -> bool
.is_some_and(|resolved| is_inaccessible_windows_apps_powershell_path(&resolved))
}
fn is_elevated_sandbox_compatible_powershell_path(path: &std::path::Path) -> bool {
fn is_windows_sandbox_compatible_powershell_path(path: &std::path::Path) -> bool {
if !path
.extension()
.is_some_and(|extension| extension.eq_ignore_ascii_case("exe"))
@@ -167,19 +167,19 @@ fn is_elevated_sandbox_compatible_powershell_path(path: &std::path::Path) -> boo
!targets_inaccessible_windows_apps_powershell(path)
}
fn get_elevated_sandbox_compatible_powershell_path(
fn get_windows_sandbox_compatible_powershell_path(
binary_name: &str,
fallback_paths: &[&str],
) -> Option<PathBuf> {
if let Ok(mut paths) = which::which_all(binary_name)
&& let Some(path) = paths.find(|path| is_elevated_sandbox_compatible_powershell_path(path))
&& let Some(path) = paths.find(|path| is_windows_sandbox_compatible_powershell_path(path))
{
return Some(path);
}
for path in fallback_paths {
let path = std::path::Path::new(path);
if is_elevated_sandbox_compatible_powershell_path(path)
if is_windows_sandbox_compatible_powershell_path(path)
&& let Some(path) = file_exists(path)
{
return Some(path);
@@ -281,21 +281,21 @@ fn get_powershell_shell() -> Option<DetectedShell> {
}
/// Returns a replacement only when shell_path targets Store PowerShell and
/// an elevated sandbox-compatible PowerShell executable can be discovered.
/// an unpackaged PowerShell executable can be discovered.
///
/// The caller owns the elevated-sandbox policy decision. Normal shell discovery
/// The caller decides whether the sandbox backend requires a fallback. Normal shell discovery
/// intentionally keeps the user's ordered PATH selection unchanged.
pub fn fallback_powershell_shell_for_elevated_windows_sandbox(
pub fn fallback_powershell_shell_for_windows_sandbox(
shell_path: &std::path::Path,
) -> Option<DetectedShell> {
if !cfg!(windows) || !targets_inaccessible_windows_apps_powershell(shell_path) {
return None;
}
let shell_path = get_elevated_sandbox_compatible_powershell_path("pwsh", PWSH_FALLBACK_PATHS)
let shell_path = get_windows_sandbox_compatible_powershell_path("pwsh", PWSH_FALLBACK_PATHS)
.or_else(|| {
get_elevated_sandbox_compatible_powershell_path("powershell", POWERSHELL_FALLBACK_PATHS)
})?;
get_windows_sandbox_compatible_powershell_path("powershell", POWERSHELL_FALLBACK_PATHS)
})?;
Some(DetectedShell {
shell_type: ShellType::PowerShell,
@@ -385,7 +385,7 @@ mod tests {
r"C:\PROGRAM FILES\WINDOWSAPPS\MICROSOFT.POWERSHELL\PWSH.EXE",
r"C:\portable\pwsh.cmd",
] {
assert!(!is_elevated_sandbox_compatible_powershell_path(
assert!(!is_windows_sandbox_compatible_powershell_path(
std::path::Path::new(path)
));
}
@@ -400,7 +400,7 @@ mod tests {
r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe",
r"C:\portable\NotWindowsApps\pwsh.EXE",
] {
assert!(is_elevated_sandbox_compatible_powershell_path(
assert!(is_windows_sandbox_compatible_powershell_path(
std::path::Path::new(path)
));
}
@@ -421,7 +421,7 @@ mod tests {
PathBuf::from(r"C:\Program Files\PowerShell\7\pwsh.exe"),
]
.into_iter()
.find(|path| is_elevated_sandbox_compatible_powershell_path(path));
.find(|path| is_windows_sandbox_compatible_powershell_path(path));
assert_eq!(found, Some(portable));
}