From c5defd0f6c28301fec7c869cde649c27e0e06608 Mon Sep 17 00:00:00 2001 From: Mukul Date: Sun, 19 Jul 2026 16:02:25 +0530 Subject: [PATCH] Write interactive prompts to stderr and handle echo-disable failures Interactive prompts from 'helm registry login' (username/password/token) and the signing-key passphrase prompts in 'helm package' and 'helm plugin package' are printed to stdout. Anyone capturing stdout (scripts, pipelines) gets prompt text mixed into the output; the docker/kubectl/oras convention is to keep prompts on stderr so stdout stays machine-readable. Also in registry login's readLine: - the term.DisableEcho error was silently discarded, so a failure to disable echo would print the password to the terminal with no warning; it now returns an error instead - echo handling is only attempted when stdin is actually a terminal, so piped input (e.g. 'echo pw | helm registry login ...' without --password-stdin) keeps working as before Signed-off-by: Mukul --- pkg/action/package.go | 5 +++-- pkg/cmd/plugin_package.go | 5 +++-- pkg/cmd/registry_login.go | 11 +++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/action/package.go b/pkg/action/package.go index 1b7901f17..58d0250cb 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -206,11 +206,12 @@ func (p *Package) Clearsign(filename string) error { // promptUser implements provenance.PassphraseFetcher func promptUser(name string) ([]byte, error) { - fmt.Printf("Password for key %q > ", name) + // Prompts go to stderr so stdout stays clean for scripting. + fmt.Fprintf(os.Stderr, "Password for key %q > ", name) // syscall.Stdin is not an int in all environments and needs to be coerced // into one there (e.g., Windows) pw, err := term.ReadPassword(int(syscall.Stdin)) - fmt.Println() + fmt.Fprintln(os.Stderr) return pw, err } diff --git a/pkg/cmd/plugin_package.go b/pkg/cmd/plugin_package.go index 019ae6d4a..452695e06 100644 --- a/pkg/cmd/plugin_package.go +++ b/pkg/cmd/plugin_package.go @@ -172,9 +172,10 @@ func (o *pluginPackageOptions) run(out io.Writer) error { } func (o *pluginPackageOptions) promptUser(name string) ([]byte, error) { - fmt.Printf("Password for key %q > ", name) + // Prompts go to stderr so stdout stays clean for scripting. + fmt.Fprintf(os.Stderr, "Password for key %q > ", name) pw, err := term.ReadPassword(int(syscall.Stdin)) - fmt.Println() + fmt.Fprintln(os.Stderr) return pw, err } diff --git a/pkg/cmd/registry_login.go b/pkg/cmd/registry_login.go index bffc56445..325469f35 100644 --- a/pkg/cmd/registry_login.go +++ b/pkg/cmd/registry_login.go @@ -136,14 +136,17 @@ func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStd // Copied/adapted from https://github.com/oras-project/oras func readLine(prompt string, silent bool) (string, error) { - fmt.Print(prompt) - if silent { + // Prompts go to stderr so stdout stays clean for scripting. + fmt.Fprint(os.Stderr, prompt) + if silent && term.IsTerminal(os.Stdin.Fd()) { fd := os.Stdin.Fd() state, err := term.SaveState(fd) if err != nil { return "", err } - term.DisableEcho(fd, state) + if err := term.DisableEcho(fd, state); err != nil { + return "", fmt.Errorf("failed to disable terminal echo for password input: %w", err) + } defer term.RestoreTerminal(fd, state) } @@ -153,7 +156,7 @@ func readLine(prompt string, silent bool) (string, error) { return "", err } if silent { - fmt.Println() + fmt.Fprintln(os.Stderr) } return string(line), nil