From e5784ad7525eefa444c7c2017208676342c049fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 22:53:47 +0000 Subject: [PATCH 1/3] Consolidate shell argv helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tool/shelltool/localshell.go | 32 ++++++++++++---------- tool/shelltool/localshell_internal_test.go | 26 +++++++++++++++++- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/tool/shelltool/localshell.go b/tool/shelltool/localshell.go index 3fdc9063..11b618ef 100644 --- a/tool/shelltool/localshell.go +++ b/tool/shelltool/localshell.go @@ -10,7 +10,6 @@ import ( "os" "os/exec" "runtime" - "slices" "sort" "strings" "sync" @@ -594,37 +593,42 @@ func shellKindDescription(shell resolvedShell) string { } func (s resolvedShell) statelessArgvForCommand(command string) []string { - var suffix []string switch s.kind { case shellKindPowerShell: - suffix = []string{"-NoProfile", "-NoLogo", "-NonInteractive", "-Command", command} + return s.argvWithExtra([]string{"-NoProfile", "-NoLogo", "-NonInteractive", "-Command", command}) case shellKindCmd: - suffix = []string{"/d", "/c", command} + return s.argvWithExtra([]string{"/d", "/c", command}) case shellKindBash: - suffix = []string{"--noprofile", "--norc", "-c", command} + return s.argvWithExtra([]string{"--noprofile", "--norc", "-c", command}) default: - suffix = []string{"-c", command} + return s.argvWithExtra([]string{"-c", command}) } - return combineArgv(s.extraArgv, suffix) } func (s resolvedShell) persistentArgv() ([]string, error) { - var suffix []string switch s.kind { case shellKindPowerShell: - suffix = []string{"-NoProfile", "-NoLogo", "-NonInteractive", "-Command", "-"} + return s.launchArgv([]string{"-NoProfile", "-NoLogo", "-NonInteractive", "-Command", "-"}), nil case shellKindCmd: return nil, fmt.Errorf("persistent mode is not supported for cmd.exe; use pwsh, powershell, or a POSIX shell") case shellKindBash: - suffix = []string{"--noprofile", "--norc"} + return s.launchArgv([]string{"--noprofile", "--norc"}), nil default: - suffix = nil + return s.launchArgv(nil), nil } - return append([]string{s.binary}, combineArgv(s.extraArgv, suffix)...), nil } -func combineArgv(extra, suffix []string) []string { - return slices.Concat(extra, suffix) +func (s resolvedShell) launchArgv(suffix []string) []string { + return append([]string{s.binary}, s.argvWithExtra(suffix)...) +} + +func (s resolvedShell) argvWithExtra(suffix []string) []string { + if len(s.extraArgv) == 0 { + return suffix + } + argv := make([]string, 0, len(s.extraArgv)+len(suffix)) + argv = append(argv, s.extraArgv...) + return append(argv, suffix...) } func classifyShellKind(shell string) shellKind { diff --git a/tool/shelltool/localshell_internal_test.go b/tool/shelltool/localshell_internal_test.go index 9f8d6c12..fcc8a3a3 100644 --- a/tool/shelltool/localshell_internal_test.go +++ b/tool/shelltool/localshell_internal_test.go @@ -2,7 +2,10 @@ package shelltool -import "testing" +import ( + "slices" + "testing" +) func TestPreserveEnvironmentValuesKeepsOnlyAllowlist(t *testing.T) { source := []string{ @@ -11,6 +14,7 @@ func TestPreserveEnvironmentValuesKeepsOnlyAllowlist(t *testing.T) { "AF_SHELL_PARENT_VAR=should-not-leak", "tmp=/tmp/test", } + env := make(map[string]string) preserveEnvironmentValues(env, source) @@ -28,3 +32,23 @@ func TestPreserveEnvironmentValuesKeepsOnlyAllowlist(t *testing.T) { t.Fatal("unexpected non-preserved variable copied") } } + +func TestResolvedShellArgvIncludesExtraArgv(t *testing.T) { + shell := resolvedShell{ + binary: "/custom/bash", + kind: shellKindBash, + extraArgv: []string{"--login"}, + } + + if got, want := shell.statelessArgvForCommand("echo hi"), []string{"--login", "--noprofile", "--norc", "-c", "echo hi"}; !slices.Equal(got, want) { + t.Fatalf("statelessArgvForCommand = %v, want %v", got, want) + } + + got, err := shell.persistentArgv() + if err != nil { + t.Fatalf("persistentArgv: %v", err) + } + if want := []string{"/custom/bash", "--login", "--noprofile", "--norc"}; !slices.Equal(got, want) { + t.Fatalf("persistentArgv = %v, want %v", got, want) + } +} From 463d5ef68a586d18335c02c1ce4480bb08d85229 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:21:02 +0000 Subject: [PATCH 2/3] fix: avoid potential integer overflow in argvWithExtra allocation Co-authored-by: michelle-clayton-work <262183035+michelle-clayton-work@users.noreply.github.com> --- tool/shelltool/localshell.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tool/shelltool/localshell.go b/tool/shelltool/localshell.go index 11b618ef..338a596d 100644 --- a/tool/shelltool/localshell.go +++ b/tool/shelltool/localshell.go @@ -626,9 +626,7 @@ func (s resolvedShell) argvWithExtra(suffix []string) []string { if len(s.extraArgv) == 0 { return suffix } - argv := make([]string, 0, len(s.extraArgv)+len(suffix)) - argv = append(argv, s.extraArgv...) - return append(argv, suffix...) + return append(append([]string{}, s.extraArgv...), suffix...) } func classifyShellKind(shell string) shellKind { From 9bb81bfe5c3bd324fb6cd04b7117d38e7070460b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:22:42 +0000 Subject: [PATCH 3/3] fix: use slices.Clone in argvWithExtra to avoid overflow and clarify intent Co-authored-by: michelle-clayton-work <262183035+michelle-clayton-work@users.noreply.github.com> --- tool/shelltool/localshell.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tool/shelltool/localshell.go b/tool/shelltool/localshell.go index 338a596d..95f015ab 100644 --- a/tool/shelltool/localshell.go +++ b/tool/shelltool/localshell.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "runtime" + "slices" "sort" "strings" "sync" @@ -626,7 +627,7 @@ func (s resolvedShell) argvWithExtra(suffix []string) []string { if len(s.extraArgv) == 0 { return suffix } - return append(append([]string{}, s.extraArgv...), suffix...) + return append(slices.Clone(s.extraArgv), suffix...) } func classifyShellKind(shell string) shellKind {