From 818c0c2e149a57608ba0833645f3ce642d0f8fc8 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sat, 28 Mar 2026 11:32:36 -0700 Subject: [PATCH] Add ReadPasswordBytes for crypto use cases Returns []byte so callers can zeroize the buffer after use. Refactors internals to share readRaw between both variants. Co-Authored-By: Claude Opus 4.6 (1M context) --- terminal/terminal.go | 20 +++++++++++++++++--- terminal/terminal_test.go | 7 +++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/terminal/terminal.go b/terminal/terminal.go index 3269794..e9fe0e3 100644 --- a/terminal/terminal.go +++ b/terminal/terminal.go @@ -12,11 +12,25 @@ import ( // from the terminal with echo disabled. It prints a newline after the // input is complete so the cursor advances normally. func ReadPassword(prompt string) (string, error) { - fmt.Fprint(os.Stderr, prompt) - b, err := term.ReadPassword(int(os.Stdin.Fd())) //nolint:gosec // fd fits in int - fmt.Fprintln(os.Stderr) + b, err := readRaw(prompt) if err != nil { return "", err } return string(b), nil } + +// ReadPasswordBytes is like ReadPassword but returns a []byte so the +// caller can zeroize the buffer after use. +func ReadPasswordBytes(prompt string) ([]byte, error) { + return readRaw(prompt) +} + +func readRaw(prompt string) ([]byte, error) { + fmt.Fprint(os.Stderr, prompt) + b, err := term.ReadPassword(int(os.Stdin.Fd())) //nolint:gosec // fd fits in int + fmt.Fprintln(os.Stderr) + if err != nil { + return nil, err + } + return b, nil +} diff --git a/terminal/terminal_test.go b/terminal/terminal_test.go index ba3074f..84d8dbb 100644 --- a/terminal/terminal_test.go +++ b/terminal/terminal_test.go @@ -12,3 +12,10 @@ func TestReadPasswordNotATTY(t *testing.T) { t.Fatal("expected error when stdin is not a terminal") } } + +func TestReadPasswordBytesNotATTY(t *testing.T) { + _, err := ReadPasswordBytes("Password: ") + if err == nil { + t.Fatal("expected error when stdin is not a terminal") + } +}