passterisk

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 7, 2026 License: MIT Imports: 8 Imported by: 0

README

passterisk

Masked password input for Go terminal programs.

Why this exists

The Go standard library has no masked password reader. The closest option is golang.org/x/term.ReadPassword, which reads a line silently — you type, nothing appears, and you have no signal that your keystrokes are landing. That works, but it's a bad UX:

  • Users can't tell if the program is hung, if their keyboard is connected, or if they've already typed three characters or thirteen.
  • Backspace works at the byte level, not the rune level — paste a Unicode password and a single backspace can leave a half-formed UTF-8 sequence.
  • There's no way to opt into the familiar ***** echo style most CLIs use.

Most projects end up reinventing this: raw-mode toggling, byte-by-byte UTF-8 reassembly, backspace handling that respects rune boundaries (and rune widths is two columns wide), Ctrl-C / Ctrl-D handling, and a non-TTY fallback for piped input. Get any one of those wrong and you ship a password prompt that mangles non-ASCII input or swallows interrupts.

passterisk is that code, written once, tested, and packaged.

What it does

  • Echoes a configurable mask rune (* by default; supports any Unicode rune, including wide chars and Nerd Font glyphs) for each character typed.
  • Correctly handles multi-byte UTF-8 input — accumulates bytes until a full rune is available before echoing or appending.
  • Backspace erases one rune and the right number of terminal columns, regardless of mask width.
  • Interrupt keys (Ctrl-C, Ctrl-D by default; configurable) abort cleanly with ErrInterrupted.
  • ClearLine option wipes the echoed mask row after Enter, so the prompt doesn't leave ******** on screen.
  • Output defaults to os.Stderr so it never collides with stdout pipelines.
  • Falls back to a plain line read when stdin isn't a TTY (piped input, CI, etc.) instead of blowing up on raw-mode failure.
  • Cross-platform: handles both DEL (127, Unix) and BS (8, Windows) for backspace.

Install

go get github.com/kenk667/passterisk

Usage

Quick start (defaults: * mask, Ctrl-C/Ctrl-D abort, stderr prompt)
package main

import (
    "errors"
    "fmt"
    "log"
    "os"

    "github.com/kenk667/passterisk"
)

func main() {
    pw, err := passterisk.Read("Password: ", passterisk.DefaultOptions)
    if errors.Is(err, passterisk.ErrInterrupted) {
        fmt.Fprintln(os.Stderr, "aborted")
        os.Exit(130)
    }
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("got password of length", len(pw))
}
Silent input (no echo at all)

Matches golang.org/x/term.ReadPassword behavior — useful when you don't want to leak password length:

opts := passterisk.Options{Mask: 0, InterruptKeys: []byte{3, 4}}
pw, err := passterisk.Read("Password: ", opts)
Custom mask rune

Any Unicode rune works, including wide characters and Nerd Font glyphs:

opts := passterisk.DefaultOptions
opts.Mask = '•'
pw, err := passterisk.Read("Password: ", opts)
Clear the masked row after Enter

Wipes ******** off the screen once the user submits, so the terminal stays tidy:

opts := passterisk.DefaultOptions
opts.ClearLine = true
pw, err := passterisk.Read("Password: ", opts)
Add ESC as an extra interrupt key
opts := passterisk.DefaultOptions
opts.InterruptKeys = []byte{3, 4, 27} // Ctrl-C, Ctrl-D, ESC
pw, err := passterisk.Read("Password: ", opts)
Change the project-wide defaults

DefaultOptions is a package-level var — mutate it once at startup and every call site picks up the change:

func init() {
    passterisk.DefaultOptions.Mask = '•'
    passterisk.DefaultOptions.ClearLine = true
}
Behavior reference
Situation What happens
User presses Enter Returns the accumulated string, nil error
User presses Backspace Removes one rune from the buffer and erases the corresponding mask cells
User presses Ctrl-C or Ctrl-D Returns "", ErrInterrupted
Stdin is not a TTY (piped input) Falls back to a plain line read with no echo manipulation
Stdin closes mid-input (EOF) Returns whatever was typed so far plus io.EOF
User pastes Unicode Multi-byte UTF-8 sequences are reassembled before echo; backspace erases by rune
Options
Field Type Default Effect
Mask rune '*' Echoed for each character. 0 = silent.
ClearLine bool false Erase the mask row from the terminal on Enter.
Output io.Writer os.Stderr Where the prompt and mask are written. Stderr keeps stdout clean for pipelines.
InterruptKeys []byte {3, 4} Single-byte values that abort with ErrInterrupted.

License

MIT.

Documentation

Overview

Package passterisk provides masked password input for terminals. It handles raw mode, UTF-8 multi-byte sequences, wide characters, backspace, interrupt keys, and non-TTY fallback — cross-platform.

Index

Constants

This section is empty.

Variables

View Source
var DefaultOptions = Options{
	Mask:          '*',
	ClearLine:     false,
	Output:        os.Stderr,
	InterruptKeys: []byte{3, 4},
}

DefaultOptions is the project-wide default. Override individual fields at the call site, or change this var to affect all calls.

View Source
var ErrInterrupted = errors.New("interrupted")

ErrInterrupted is returned when the user presses an interrupt key (Ctrl-C, Ctrl-D).

Functions

func Read

func Read(prompt string, opts Options) (string, error)

Read prints prompt to opts.Output then reads a masked line from stdin. In non-TTY contexts (piped input) it falls back to a plain line read with no echo.

Types

type Options

type Options struct {
	// Mask is the rune echoed for each character typed.
	// 0 = no echo (blank, current terminal default behavior).
	// Supports any Unicode rune including Nerd Font glyphs (e.g. '' for ).
	Mask rune

	// ClearLine erases the echoed mask characters from the terminal after Enter.
	// Useful when you don't want the row of asterisks left on screen.
	ClearLine bool

	// Output is where the prompt and mask echo are written.
	// Defaults to os.Stderr so it never interferes with stdout pipelines.
	Output io.Writer

	// InterruptKeys are single-byte values that abort input and return ErrInterrupted.
	// Defaults to Ctrl-C (3) and Ctrl-D (4).
	InterruptKeys []byte
}

Options controls the behavior of Read. All project-wide defaults live here — change DefaultOptions to affect every call site.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL