diff --git a/internal/cli/cli.go b/internal/cli/cli.go index ec120fb..43053b2 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -2,21 +2,11 @@ package cli import ( - "bufio" - "fmt" - "os" - "strings" - "syscall" - "git.eeqj.de/sneak/secret/internal/secret" "github.com/spf13/afero" "github.com/spf13/cobra" - "golang.org/x/term" ) -// Global scanner for consistent stdin reading -var stdinScanner *bufio.Scanner //nolint:gochecknoglobals // Needed for consistent stdin handling - // Instance encapsulates all CLI functionality and state type Instance struct { fs afero.Fs @@ -67,33 +57,3 @@ func (cli *Instance) SetStateDir(stateDir string) { func (cli *Instance) GetStateDir() string { return cli.stateDir } - -// getStdinScanner returns a shared scanner for stdin to avoid buffering issues -func getStdinScanner() *bufio.Scanner { - if stdinScanner == nil { - stdinScanner = bufio.NewScanner(os.Stdin) - } - - return stdinScanner -} - -// readLineFromStdin reads a single line from stdin with a prompt -// Uses a shared scanner to avoid buffering issues between multiple calls -func readLineFromStdin(prompt string) (string, error) { - // Check if stderr is a terminal - if not, we can't prompt interactively - if !term.IsTerminal(syscall.Stderr) { - return "", fmt.Errorf("cannot prompt for input: stderr is not a terminal (running in non-interactive mode)") - } - - fmt.Fprint(os.Stderr, prompt) // Write prompt to stderr, not stdout - scanner := getStdinScanner() - if !scanner.Scan() { - if err := scanner.Err(); err != nil { - return "", fmt.Errorf("failed to read from stdin: %w", err) - } - - return "", fmt.Errorf("failed to read from stdin: EOF") - } - - return strings.TrimSpace(scanner.Text()), nil -} diff --git a/internal/cli/init.go b/internal/cli/init.go index 4181d50..4b237c8 100644 --- a/internal/cli/init.go +++ b/internal/cli/init.go @@ -60,14 +60,17 @@ func (cli *Instance) Init(cmd *cobra.Command) error { mnemonicStr = envMnemonic } else { secret.Debug("Prompting user for mnemonic phrase") - // Read mnemonic from stdin using shared line reader - var err error - mnemonicStr, err = readLineFromStdin("Enter your BIP39 mnemonic phrase: ") + // Read mnemonic securely without echo + mnemonicBuffer, err := secret.ReadPassphrase("Enter your BIP39 mnemonic phrase: ") if err != nil { secret.Debug("Failed to read mnemonic from stdin", "error", err) return fmt.Errorf("failed to read mnemonic: %w", err) } + defer mnemonicBuffer.Destroy() + + mnemonicStr = mnemonicBuffer.String() + fmt.Fprintln(os.Stderr) // Add newline after hidden input } if mnemonicStr == "" {