From 9ea38136b00937a87c94cbcaab872342b2adb6d9 Mon Sep 17 00:00:00 2001 From: Chris Bookholt Date: Thu, 14 May 2026 06:28:34 -0700 Subject: [PATCH] [codex] treat PowerShell stop-parsing forms as unsupported (#22643) ## Summary - Treat PowerShell stop-parsing token forms as unsupported in the AST-backed command flattener. - Add focused regressions at the parser layer and Windows command-safety layer. ## Why The command-safety parser lowers PowerShell AST elements into argv-like words. Stop-parsing syntax preserves a native-command argument shape that this lowering does not model, so these forms should stay on the conservative unsupported path. ## Validation - `cargo fmt --manifest-path codex-rs/Cargo.toml --all --check` - `cargo test --manifest-path codex-rs/Cargo.toml -p codex-shell-command` --- .../src/command_safety/powershell_parser.ps1 | 9 +++++++++ .../src/command_safety/powershell_parser.rs | 14 ++++++++++++++ .../src/command_safety/windows_safe_commands.rs | 10 ++++++++++ 3 files changed, 33 insertions(+) diff --git a/codex-rs/shell-command/src/command_safety/powershell_parser.ps1 b/codex-rs/shell-command/src/command_safety/powershell_parser.ps1 index 9f19f172eb..de696c88bd 100644 --- a/codex-rs/shell-command/src/command_safety/powershell_parser.ps1 +++ b/codex-rs/shell-command/src/command_safety/powershell_parser.ps1 @@ -42,6 +42,15 @@ function Invoke-ParseRequest { return @{ id = $RequestId; status = 'parse_errors' } } + # PowerShell's stop-parsing marker hands the remaining source text to native + # commands with runtime argument handling that does not match the AST shape we + # flatten below. Keep that form out of the argv-like lowering path entirely. + foreach ($token in $tokens) { + if ($token.Text -eq '--%') { + return @{ id = $RequestId; status = 'unsupported' } + } + } + # Only accept AST shapes we can flatten into a list of argv-like command words. # Anything more dynamic than that becomes "unsupported" instead of being guessed at. $commands = [System.Collections.ArrayList]::new() diff --git a/codex-rs/shell-command/src/command_safety/powershell_parser.rs b/codex-rs/shell-command/src/command_safety/powershell_parser.rs index 580f177f9f..ddcbdf5727 100644 --- a/codex-rs/shell-command/src/command_safety/powershell_parser.rs +++ b/codex-rs/shell-command/src/command_safety/powershell_parser.rs @@ -296,4 +296,18 @@ mod tests { ]), ); } + + #[test] + fn parser_process_rejects_stop_parsing_forms() { + let Some(powershell) = try_find_powershell_executable_blocking() else { + return; + }; + let powershell = powershell.as_path().to_str().unwrap(); + let mut parser = PowershellParserProcess::spawn(powershell).unwrap(); + + let parsed = parser + .parse("git log --% HEAD --output=codex_poc.txt") + .unwrap(); + assert_eq!(parsed, PowershellParseOutcome::Unsupported); + } } diff --git a/codex-rs/shell-command/src/command_safety/windows_safe_commands.rs b/codex-rs/shell-command/src/command_safety/windows_safe_commands.rs index 8ef3f8e8f9..a8dcef6de6 100644 --- a/codex-rs/shell-command/src/command_safety/windows_safe_commands.rs +++ b/codex-rs/shell-command/src/command_safety/windows_safe_commands.rs @@ -402,6 +402,16 @@ mod tests { ); } + #[test] + fn rejects_stop_parsing_git_forms() { + assert!(!is_safe_command_windows(&vec_str(&[ + "powershell.exe", + "-NoProfile", + "-Command", + "git log --% HEAD --output=codex_poc.txt", + ]))); + } + #[test] fn rejects_powershell_commands_with_side_effects() { assert!(!is_safe_command_windows(&vec_str(&[