From 86c6cc24c0ba8880cb05e635cdb8b779a0b2d05a Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sat, 29 Aug 2026 16:54:01 +0200 Subject: [PATCH] internal/term: sanitize terminal prompts Reported by Joe Doyle of Trail of Bits. --- internal/term/term.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/internal/term/term.go b/internal/term/term.go index 42d7f31..18fe2bd 100644 --- a/internal/term/term.go +++ b/internal/term/term.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "runtime" + "strings" + "unicode" "golang.org/x/term" ) @@ -13,6 +15,15 @@ import ( // characters being printed to the console. var enableVirtualTerminalProcessing func(out *os.File) error +func printPrompt(out *os.File, prompt string) { + fmt.Fprintf(out, "%s ", strings.Map(func(r rune) rune { + if unicode.IsControl(r) { + return '\uFFFD' + } + return r + }, prompt)) +} + // clearLine clears the current line on the terminal, or opens a new line if // terminal escape codes don't work. func clearLine(out *os.File) { @@ -64,7 +75,7 @@ func WithTerminal(f func(in, out *os.File) error) error { // ReadSecret reads a value from the terminal with no echo. The prompt is ephemeral. func ReadSecret(prompt string) (s []byte, err error) { err = WithTerminal(func(in, out *os.File) error { - fmt.Fprintf(out, "%s ", prompt) + printPrompt(out, prompt) defer clearLine(out) s, err = term.ReadPassword(int(in.Fd())) return err @@ -75,7 +86,7 @@ func ReadSecret(prompt string) (s []byte, err error) { // ReadPublic reads a value from the terminal. The prompt is ephemeral. func ReadPublic(prompt string) (s []byte, err error) { err = WithTerminal(func(in, out *os.File) error { - fmt.Fprintf(out, "%s ", prompt) + printPrompt(out, prompt) defer clearLine(out) oldState, err := term.MakeRaw(int(in.Fd())) @@ -96,7 +107,7 @@ func ReadPublic(prompt string) (s []byte, err error) { // prompt is ephemeral. func ReadCharacter(prompt string) (c byte, err error) { err = WithTerminal(func(in, out *os.File) error { - fmt.Fprintf(out, "%s ", prompt) + printPrompt(out, prompt) defer clearLine(out) oldState, err := term.MakeRaw(int(in.Fd()))