Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion codex-rs/shell-command/src/command_safety/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// Keep the PowerShell subprocess parser available as a test oracle, but do not
// compile it into production command classification.
#[cfg(test)]
#[allow(dead_code)]
mod powershell_parser;
mod powershell_tree_sitter;

pub mod is_dangerous_command;
pub mod is_safe_command;
#[cfg(windows)]
pub(crate) mod windows_safe_commands;
pub(crate) use powershell_parser::try_parse_powershell_ast_commands;
pub(crate) use powershell_tree_sitter::try_parse_powershell_commands;
56 changes: 3 additions & 53 deletions codex-rs/shell-command/src/command_safety/windows_safe_commands.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::command_safety::powershell_parser::PowershellParseOutcome;
use crate::command_safety::powershell_parser::parse_with_powershell_ast;
use crate::command_safety::try_parse_powershell_commands;
use std::path::Path;

/// On Windows, we conservatively allow only clearly read-only PowerShell invocations
Expand Down Expand Up @@ -92,14 +91,8 @@ fn parse_powershell_invocation(executable: &str, args: &[String]) -> Option<Vec<

/// Tokenizes an inline PowerShell script and delegates to the command splitter.
/// Examples of when this is called: pwsh.exe -Command '<script>' or pwsh.exe -Command:<script>
fn parse_powershell_script(executable: &str, script: &str) -> Option<Vec<Vec<String>>> {
if let PowershellParseOutcome::Commands(commands) =
parse_with_powershell_ast(executable, script)
{
Some(commands)
} else {
None
}
fn parse_powershell_script(_executable: &str, script: &str) -> Option<Vec<Vec<String>>> {
try_parse_powershell_commands(script)
}

/// Returns true when the executable name is one of the supported PowerShell binaries.
Expand Down Expand Up @@ -305,12 +298,6 @@ mod tests {
"Get-Content foo.rs | Select-Object -Skip 200".to_string()
]));

assert!(is_safe_command_windows(&[
pwsh.clone(),
"-Command".to_string(),
"(Get-Content foo.rs -Raw)".to_string()
]));

assert!(is_safe_command_windows(&[
pwsh,
"-Command".to_string(),
Expand Down Expand Up @@ -469,13 +456,6 @@ mod tests {
"ls @(calc.exe)"
])));

// Unsupported constructs that the AST parser refuses (no fallback to manual splitting).
assert!(!is_safe_command_windows(&vec_str(&[
"powershell.exe",
"-Command",
"ls && pwd"
])));

// Sub-expressions are rejected even if they contain otherwise safe commands.
assert!(!is_safe_command_windows(&vec_str(&[
"powershell.exe",
Expand Down Expand Up @@ -520,34 +500,4 @@ mod tests {
"Write-Output \"foo $bar\""
])));
}

#[test]
fn uses_invoked_powershell_variant_for_parsing() {
if !cfg!(windows) {
return;
}

let chain = "pwd && ls";
assert!(
!is_safe_command_windows(&vec_str(&[
"powershell.exe",
"-NoProfile",
"-Command",
chain,
])),
"`{chain}` is not recognized by powershell.exe"
);

if let Some(pwsh) = try_find_pwsh_executable_blocking() {
assert!(
is_safe_command_windows(&[
pwsh.as_path().to_str().unwrap().into(),
"-NoProfile".to_string(),
"-Command".to_string(),
chain.to_string(),
]),
"`{chain}` should be considered safe to pwsh.exe"
);
}
}
}
9 changes: 4 additions & 5 deletions codex-rs/shell-command/src/powershell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::path::PathBuf;

use codex_utils_absolute_path::AbsolutePathBuf;

use crate::command_safety::try_parse_powershell_ast_commands;
use crate::command_safety::try_parse_powershell_commands;
use crate::shell_detect::ShellType;
use crate::shell_detect::detect_shell_type;
Expand Down Expand Up @@ -74,13 +73,13 @@ pub fn extract_powershell_command(command: &[String]) -> Option<(&str, &str)> {
/// Parse the script body from a top-level PowerShell wrapper into argv-like commands.
///
/// This is intentionally narrower than the Windows safe-command parser: it only unwraps the
/// `-Command`/`-c` body from a PowerShell invocation we already recognize, then delegates the
/// script itself to the PowerShell AST parser.
/// `-Command`/`-c` body from a PowerShell invocation we already recognize, then lowers the
/// script in-process.
pub fn parse_powershell_command_into_plain_commands(
command: &[String],
) -> Option<Vec<Vec<String>>> {
let (executable, script) = extract_powershell_command(command)?;
try_parse_powershell_ast_commands(executable, script)
let (_, script) = extract_powershell_command(command)?;
try_parse_powershell_commands(script)
}

/// Parse literal PowerShell commands without starting a PowerShell executable.
Expand Down
Loading