From a7e68abd4e302b0e8a72510476394844cbaf1878 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 12:36:27 +0000 Subject: [PATCH 1/2] Initial plan From 63281ab4b301f2a74bdf7d65b6bb9545a84794d4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 12:40:11 +0000 Subject: [PATCH 2/2] Fix shell injection in run_editor and add STDERR TTY check for --interactive - Avoid shell invocation in run_editor by parsing editor into argv and passing path as a separate argument, eliminating quoting/injection risks. - Add STDERR.tty? check alongside STDIN/STDOUT in --interactive mode so the command won't appear to hang when STDERR is redirected. --- src/cjules/commands/patch.cr | 2 +- src/cjules/unidiff.cr | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/cjules/commands/patch.cr b/src/cjules/commands/patch.cr index 30bfee8..350ae63 100644 --- a/src/cjules/commands/patch.cr +++ b/src/cjules/commands/patch.cr @@ -89,7 +89,7 @@ module Cjules if apply if interactive - unless STDIN.tty? && STDOUT.tty? + unless STDIN.tty? && STDOUT.tty? && STDERR.tty? STDERR.puts "error: --interactive requires a TTY" return 2 end diff --git a/src/cjules/unidiff.cr b/src/cjules/unidiff.cr index a2162a3..5a06911 100644 --- a/src/cjules/unidiff.cr +++ b/src/cjules/unidiff.cr @@ -204,9 +204,12 @@ module Cjules end private def run_editor(editor : String, path : String, output : IO, display : IO) : Bool - # Allow editor commands with flags by going through the shell. - cmd = "#{editor} #{shell_escape(path)}" - status = Process.run("sh", ["-lc", cmd], + # Parse editor command into program + flags and pass path as a separate arg + # to avoid shell injection and quoting issues. + parts = editor.split + program = parts[0] + args = parts[1..] + [path] + status = Process.run(program, args, input: input_for_editor, output: display, error: output) @@ -222,10 +225,6 @@ module Cjules STDIN end - private def shell_escape(s : String) : String - "'" + s.gsub("'", %q('\'')).to_s + "'" - end - private def check_applies?(patch_path : String, output : IO) : Bool out_io = IO::Memory.new err = IO::Memory.new