mirror of
https://github.com/openai/codex.git
synced 2026-09-09 15:58:47 +00:00
Force no-profile PowerShell in Windows sandbox
This commit is contained in:
@@ -104,6 +104,7 @@ fn shell_command_for_invocation(invocation: &ToolInvocation) -> Option<(Vec<Stri
|
||||
invocation.session.user_shell(),
|
||||
&invocation.turn.tools_config.unified_exec_shell_mode,
|
||||
invocation.turn.tools_config.allow_login_shell,
|
||||
invocation.turn.windows_sandbox_level,
|
||||
)
|
||||
.ok()?;
|
||||
Some((command, invocation.turn.resolve_path(params.workdir)))
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::exec_policy::ExecApprovalRequest;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::maybe_emit_implicit_skill_invocation;
|
||||
use crate::shell::Shell;
|
||||
use crate::shell::ShellType;
|
||||
use crate::tools::context::FunctionToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutput;
|
||||
@@ -35,6 +36,7 @@ use crate::tools::runtimes::shell::ShellRuntime;
|
||||
use crate::tools::runtimes::shell::ShellRuntimeBackend;
|
||||
use crate::tools::sandboxing::ToolCtx;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::ExecCommandSource;
|
||||
use codex_shell_command::is_safe_command::is_known_safe_command;
|
||||
@@ -133,7 +135,19 @@ impl ShellCommandHandler {
|
||||
Ok(login.unwrap_or(allow_login_shell))
|
||||
}
|
||||
|
||||
fn base_command(shell: &Shell, command: &str, use_login_shell: bool) -> Vec<String> {
|
||||
fn base_command(
|
||||
shell: &Shell,
|
||||
command: &str,
|
||||
use_login_shell: bool,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
) -> Vec<String> {
|
||||
let use_login_shell = if windows_sandbox_level != WindowsSandboxLevel::Disabled
|
||||
&& matches!(shell.shell_type, ShellType::PowerShell)
|
||||
{
|
||||
false
|
||||
} else {
|
||||
use_login_shell
|
||||
};
|
||||
shell.derive_exec_args(command, use_login_shell)
|
||||
}
|
||||
|
||||
@@ -146,7 +160,12 @@ impl ShellCommandHandler {
|
||||
) -> Result<ExecParams, FunctionCallError> {
|
||||
let shell = session.user_shell();
|
||||
let use_login_shell = Self::resolve_use_login_shell(params.login, allow_login_shell)?;
|
||||
let command = Self::base_command(shell.as_ref(), ¶ms.command, use_login_shell);
|
||||
let command = Self::base_command(
|
||||
shell.as_ref(),
|
||||
¶ms.command,
|
||||
use_login_shell,
|
||||
turn_context.windows_sandbox_level,
|
||||
);
|
||||
|
||||
Ok(ExecParams {
|
||||
command,
|
||||
@@ -303,7 +322,12 @@ impl ToolHandler for ShellCommandHandler {
|
||||
Err(_) => return true,
|
||||
};
|
||||
let shell = invocation.session.user_shell();
|
||||
let command = Self::base_command(shell.as_ref(), ¶ms.command, use_login_shell);
|
||||
let command = Self::base_command(
|
||||
shell.as_ref(),
|
||||
¶ms.command,
|
||||
use_login_shell,
|
||||
invocation.turn.windows_sandbox_level,
|
||||
);
|
||||
!is_known_safe_command(&command)
|
||||
})
|
||||
.unwrap_or(true)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::ShellCommandToolCallParams;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -138,6 +139,7 @@ fn shell_command_handler_respects_explicit_login_flag() {
|
||||
&shell,
|
||||
"echo login shell",
|
||||
/*use_login_shell*/ true,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
);
|
||||
assert_eq!(
|
||||
login_command,
|
||||
@@ -148,6 +150,7 @@ fn shell_command_handler_respects_explicit_login_flag() {
|
||||
&shell,
|
||||
"echo non login shell",
|
||||
/*use_login_shell*/ false,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
);
|
||||
assert_eq!(
|
||||
non_login_command,
|
||||
@@ -155,6 +158,32 @@ fn shell_command_handler_respects_explicit_login_flag() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shell_command_handler_forces_non_login_powershell_inside_windows_sandbox() {
|
||||
let shell = Shell {
|
||||
shell_type: ShellType::PowerShell,
|
||||
shell_path: PathBuf::from("powershell.exe"),
|
||||
shell_snapshot: crate::shell::empty_shell_snapshot_receiver(),
|
||||
};
|
||||
|
||||
let command = ShellCommandHandler::base_command(
|
||||
&shell,
|
||||
"Get-ChildItem",
|
||||
/*use_login_shell*/ true,
|
||||
WindowsSandboxLevel::RestrictedToken,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
command,
|
||||
vec![
|
||||
"powershell.exe".to_string(),
|
||||
"-NoProfile".to_string(),
|
||||
"-Command".to_string(),
|
||||
"Get-ChildItem".to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shell_command_handler_defaults_to_non_login_when_disallowed() {
|
||||
let (session, turn_context) = make_session_and_context().await;
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::function_tool::FunctionCallError;
|
||||
use crate::maybe_emit_implicit_skill_invocation;
|
||||
use crate::sandboxing::SandboxPermissions;
|
||||
use crate::shell::Shell;
|
||||
use crate::shell::ShellType;
|
||||
use crate::shell::get_shell_by_model_provided_path;
|
||||
use crate::tools::context::ExecCommandToolOutput;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
@@ -25,6 +26,7 @@ use crate::unified_exec::WriteStdinRequest;
|
||||
use codex_features::Feature;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_otel::TOOL_CALL_UNIFIED_EXEC_METRIC;
|
||||
use codex_protocol::config_types::WindowsSandboxLevel;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
use codex_protocol::protocol::TerminalInteractionEvent;
|
||||
@@ -113,6 +115,7 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
invocation.session.user_shell(),
|
||||
&invocation.turn.tools_config.unified_exec_shell_mode,
|
||||
invocation.turn.tools_config.allow_login_shell,
|
||||
invocation.turn.windows_sandbox_level,
|
||||
) {
|
||||
Ok(command) => command,
|
||||
Err(_) => return true,
|
||||
@@ -198,6 +201,7 @@ impl ToolHandler for UnifiedExecHandler {
|
||||
session.user_shell(),
|
||||
&turn.tools_config.unified_exec_shell_mode,
|
||||
turn.tools_config.allow_login_shell,
|
||||
turn.windows_sandbox_level,
|
||||
)
|
||||
.map_err(FunctionCallError::RespondToModel)?;
|
||||
let command_for_display = codex_shell_command::parse_command::shlex_join(&command);
|
||||
@@ -377,8 +381,9 @@ pub(crate) fn get_command(
|
||||
session_shell: Arc<Shell>,
|
||||
shell_mode: &UnifiedExecShellMode,
|
||||
allow_login_shell: bool,
|
||||
windows_sandbox_level: WindowsSandboxLevel,
|
||||
) -> Result<Vec<String>, String> {
|
||||
let use_login_shell = match args.login {
|
||||
let mut use_login_shell = match args.login {
|
||||
Some(true) if !allow_login_shell => {
|
||||
return Err(
|
||||
"login shell is disabled by config; omit `login` or set it to false.".to_string(),
|
||||
@@ -396,6 +401,11 @@ pub(crate) fn get_command(
|
||||
shell
|
||||
});
|
||||
let shell = model_shell.as_ref().unwrap_or(session_shell.as_ref());
|
||||
if windows_sandbox_level != WindowsSandboxLevel::Disabled
|
||||
&& matches!(shell.shell_type, ShellType::PowerShell)
|
||||
{
|
||||
use_login_shell = false;
|
||||
}
|
||||
Ok(shell.derive_exec_args(&args.cmd, use_login_shell))
|
||||
}
|
||||
UnifiedExecShellMode::ZshFork(zsh_fork_config) => Ok(vec![
|
||||
|
||||
@@ -33,6 +33,7 @@ fn test_get_command_uses_default_shell_when_unspecified() -> anyhow::Result<()>
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
/*allow_login_shell*/ true,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
@@ -54,6 +55,7 @@ fn test_get_command_respects_explicit_bash_shell() -> anyhow::Result<()> {
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
/*allow_login_shell*/ true,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
@@ -80,6 +82,7 @@ fn test_get_command_respects_explicit_powershell_shell() -> anyhow::Result<()> {
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
/*allow_login_shell*/ true,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
@@ -87,6 +90,38 @@ fn test_get_command_respects_explicit_powershell_shell() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_command_forces_non_login_powershell_inside_windows_sandbox() -> anyhow::Result<()> {
|
||||
let json = r#"{"cmd": "Get-ChildItem", "login": true}"#;
|
||||
|
||||
let args: ExecCommandArgs = parse_arguments(json)?;
|
||||
let shell = Shell {
|
||||
shell_type: ShellType::PowerShell,
|
||||
shell_path: PathBuf::from("powershell.exe"),
|
||||
shell_snapshot: crate::shell::empty_shell_snapshot_receiver(),
|
||||
};
|
||||
|
||||
let command = get_command(
|
||||
&args,
|
||||
Arc::new(shell),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
/*allow_login_shell*/ true,
|
||||
WindowsSandboxLevel::Elevated,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
assert_eq!(
|
||||
command,
|
||||
vec![
|
||||
"powershell.exe".to_string(),
|
||||
"-NoProfile".to_string(),
|
||||
"-Command".to_string(),
|
||||
"Get-ChildItem".to_string(),
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_command_respects_explicit_cmd_shell() -> anyhow::Result<()> {
|
||||
let json = r#"{"cmd": "echo hello", "shell": "cmd"}"#;
|
||||
@@ -100,6 +135,7 @@ fn test_get_command_respects_explicit_cmd_shell() -> anyhow::Result<()> {
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
/*allow_login_shell*/ true,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
@@ -117,6 +153,7 @@ fn test_get_command_rejects_explicit_login_when_disallowed() -> anyhow::Result<(
|
||||
Arc::new(default_user_shell()),
|
||||
&UnifiedExecShellMode::Direct,
|
||||
/*allow_login_shell*/ false,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
)
|
||||
.expect_err("explicit login should be rejected");
|
||||
|
||||
@@ -150,6 +187,7 @@ fn test_get_command_ignores_explicit_shell_in_zsh_fork_mode() -> anyhow::Result<
|
||||
Arc::new(default_user_shell()),
|
||||
&shell_mode,
|
||||
/*allow_login_shell*/ true,
|
||||
WindowsSandboxLevel::Disabled,
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user