From 68ad1ea7eab41bbef11d92bf682de7b78b93803e Mon Sep 17 00:00:00 2001 From: Tony <1094086026@qq.com> Date: Wed, 27 May 2026 19:16:52 +0800 Subject: [PATCH 1/2] fix: prevent action execution during shell completion after double dash When a shell autocomplete script appends --generate-bash-completion to a command line that already contains "--", the completion flag was passed through to the action as a positional argument, causing the action to execute during a completion attempt. This is particularly dangerous because tab-completion could trigger side effects (e.g., file creation, network calls) without the user pressing Enter. Changes: - checkShellCompleteFlag now strips --generate-bash-completion from arguments even when "--" is present, and returns a new boolean indicating whether the completion flag was detected - RunContext returns early (without executing the action) when the completion flag was detected but shell completion is disabled - Updated tests to verify the new behavior Fixes #1993 --- app.go | 9 ++++++++- help.go | 16 +++++++++++----- help_test.go | 13 +++++++++++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/app.go b/app.go index af072e7699..7b3558ae9f 100644 --- a/app.go +++ b/app.go @@ -319,7 +319,14 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { // flag name as the value of the flag before it which is undesirable // note that we can only do this because the shell autocomplete function // always appends the completion flag at the end of the command - shellComplete, arguments := checkShellCompleteFlag(a, arguments) + shellComplete, completionDetected, arguments := checkShellCompleteFlag(a, arguments) + + // If the completion flag was present but shell completion is disabled + // (e.g., because "--" was on the command line), do not execute the + // action. The flag has already been stripped from arguments. + if completionDetected && !shellComplete { + return nil + } cCtx := NewContext(a, nil, &Context{Context: ctx}) cCtx.shellComplete = shellComplete diff --git a/help.go b/help.go index d27e8ce385..cad9ead81c 100644 --- a/help.go +++ b/help.go @@ -435,28 +435,34 @@ func checkHelp(cCtx *Context) bool { return found } -func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { +func checkShellCompleteFlag(a *App, arguments []string) (bool, bool, []string) { if !a.EnableBashCompletion { - return false, arguments + return false, false, arguments } pos := len(arguments) - 1 lastArg := arguments[pos] if lastArg != "--generate-bash-completion" { - return false, arguments + return false, false, arguments } + // The completion flag was detected — strip it from arguments regardless + // of "--" presence, so that it never leaks into the action's arguments. + stripped := make([]string, 0, len(arguments)-1) + stripped = append(stripped, arguments[:pos]...) + stripped = append(stripped, arguments[pos+1:]...) + for _, arg := range arguments { // If arguments include "--", shell completion is disabled // because after "--" only positional arguments are accepted. // https://unix.stackexchange.com/a/11382 if arg == "--" { - return false, arguments + return false, true, stripped } } - return true, arguments[:pos] + return true, true, stripped } func checkCompletions(cCtx *Context) bool { diff --git a/help_test.go b/help_test.go index 8a856258ae..e2adcbbf10 100644 --- a/help_test.go +++ b/help_test.go @@ -1720,6 +1720,7 @@ func Test_checkShellCompleteFlag(t *testing.T) { app *App arguments []string wantShellCompletion bool + wantCompletionDetected bool wantArgs []string }{ { @@ -1727,6 +1728,7 @@ func Test_checkShellCompleteFlag(t *testing.T) { arguments: []string{"--generate-bash-completion"}, app: &App{}, wantShellCompletion: false, + wantCompletionDetected: false, wantArgs: []string{"--generate-bash-completion"}, }, { @@ -1736,6 +1738,7 @@ func Test_checkShellCompleteFlag(t *testing.T) { EnableBashCompletion: true, }, wantShellCompletion: false, + wantCompletionDetected: false, wantArgs: []string{"foo"}, }, { @@ -1745,7 +1748,8 @@ func Test_checkShellCompleteFlag(t *testing.T) { EnableBashCompletion: true, }, wantShellCompletion: false, - wantArgs: []string{"--", "foo", "--generate-bash-completion"}, + wantCompletionDetected: true, + wantArgs: []string{"--", "foo"}, }, { name: "--generate-bash-completion", @@ -1754,6 +1758,7 @@ func Test_checkShellCompleteFlag(t *testing.T) { EnableBashCompletion: true, }, wantShellCompletion: true, + wantCompletionDetected: true, wantArgs: []string{"foo"}, }, } @@ -1762,11 +1767,15 @@ func Test_checkShellCompleteFlag(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - shellCompletion, args := checkShellCompleteFlag(tt.app, tt.arguments) + shellCompletion, completionDetected, args := checkShellCompleteFlag(tt.app, tt.arguments) if tt.wantShellCompletion != shellCompletion { t.Errorf("Unexpected shell completion, got:\n%v\nexpected: %v", shellCompletion, tt.wantShellCompletion) } + if tt.wantCompletionDetected != completionDetected { + t.Errorf("Unexpected completion detected, got:\n%v\nexpected: %v", + completionDetected, tt.wantCompletionDetected) + } if !reflect.DeepEqual(tt.wantArgs, args) { t.Errorf("Unexpected arguments, got:\n%v\nexpected: %v", args, tt.wantArgs) From 9de647bd890dde3f8d133bf7f28a397711297e7f Mon Sep 17 00:00:00 2001 From: Tony <1094086026@qq.com> Date: Thu, 28 May 2026 17:43:09 +0800 Subject: [PATCH 2/2] style: apply goimports formatting to help_test.go --- help_test.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/help_test.go b/help_test.go index e2adcbbf10..cad0355f06 100644 --- a/help_test.go +++ b/help_test.go @@ -1716,20 +1716,20 @@ GLOBAL OPTIONS: func Test_checkShellCompleteFlag(t *testing.T) { t.Parallel() tests := []struct { - name string - app *App - arguments []string - wantShellCompletion bool + name string + app *App + arguments []string + wantShellCompletion bool wantCompletionDetected bool - wantArgs []string + wantArgs []string }{ { - name: "disable bash completion", - arguments: []string{"--generate-bash-completion"}, - app: &App{}, - wantShellCompletion: false, + name: "disable bash completion", + arguments: []string{"--generate-bash-completion"}, + app: &App{}, + wantShellCompletion: false, wantCompletionDetected: false, - wantArgs: []string{"--generate-bash-completion"}, + wantArgs: []string{"--generate-bash-completion"}, }, { name: "--generate-bash-completion isn't used", @@ -1737,9 +1737,9 @@ func Test_checkShellCompleteFlag(t *testing.T) { app: &App{ EnableBashCompletion: true, }, - wantShellCompletion: false, + wantShellCompletion: false, wantCompletionDetected: false, - wantArgs: []string{"foo"}, + wantArgs: []string{"foo"}, }, { name: "arguments include double dash", @@ -1747,9 +1747,9 @@ func Test_checkShellCompleteFlag(t *testing.T) { app: &App{ EnableBashCompletion: true, }, - wantShellCompletion: false, + wantShellCompletion: false, wantCompletionDetected: true, - wantArgs: []string{"--", "foo"}, + wantArgs: []string{"--", "foo"}, }, { name: "--generate-bash-completion", @@ -1757,9 +1757,9 @@ func Test_checkShellCompleteFlag(t *testing.T) { app: &App{ EnableBashCompletion: true, }, - wantShellCompletion: true, + wantShellCompletion: true, wantCompletionDetected: true, - wantArgs: []string{"foo"}, + wantArgs: []string{"foo"}, }, }