Skip to content

feat(pass): prompt for the secret when STDIN is a terminal - #675

Draft
joe0BAB wants to merge 1 commit into
mainfrom
feat/ls
Draft

joe0BAB wants to merge 1 commit into
mainfrom
feat/ls

Conversation

@joe0BAB

@joe0BAB joe0BAB commented Oct 7, 2026

Copy link
Copy Markdown
Collaborator

docker pass set <id> used to block on STDIN whenever no value was given, echoing whatever the user typed. It now detects an interactive terminal and shows a masked prompt instead, so the value neither lands in shell history nor on screen, and nobody has to pipe it from a file. Pipes and redirects still read STDIN to the end as before.

The prompt is a small raw-mode line reader on top of x/term. Printable runes are appended and echoed as '*', Backspace removes the last rune, ctrl+u clears the line, and Enter or a bare LF (ctrl+j) ends it, as with getpass(3). An empty Enter is an error, and so is input ending before Enter: Ctrl-D, or Ctrl-Z on a Windows console, which the console reader in os reports as EOF. Unlike getpass(3), a line that was not entered is never stored. Ctrl-C, which raw mode
delivers as a key press rather than a signal, is reported as context.Canceled so the root exits 130 silently. Cancelling the context, as the root does on a signal, ends the read loop so its buffers are zeroed, restores the terminal and returns; the root ends the prompt line when it reports the signal. The blocked read of the tty itself cannot be interrupted and is left to end with the process. Escape sequences are skipped whole, cursor keys, mouse reports and stray terminal replies among them, so they never leak into the value; a control byte inside one ends it, so Enter and Ctrl-C get through regardless. A sequence split after its ESC gets 50 ms to arrive before ESC counts as the Escape key, as in vim and bubbletea.
The read buffer and the collected runes are zeroed when the prompt returns.

Pastes are taken through bracketed paste mode only; the prompt never reads the clipboard. A pasted trailing newline is trimmed, and a value spanning several lines, or holding control characters other than tab, is rejected with a pointer to STDIN rather than stored mangled. Ctrl-C cancels inside a paste too, the way out should its end marker never arrive. When the terminal does not bracket pastes, a multi-line paste arrives as keystrokes: the value is exactly the first line, and the terminal is restored and the input still queued in it discarded in one tcsetattr(TCSAFLUSH) ioctl (TIOCSETAF on Darwin, TCSETSF on Linux; SetConsoleMode then FlushConsoleInputBuffer on Windows), as getpass(3) does. Restoring first and flushing second would leave a window on BSD-derived ttys: turning ICANON back on sets PENDIN, and the next byte to arrive has the queued remainder retyped with ECHO on, printing the rest of the paste in plaintext. Pastes larger than the pty input queue can still reach the shell, as they do with sudo and ssh prompts.

Cobra hands the subcommands docker's stream wrappers, which hide the terminal file behind File(); unwrapFile reaches it to switch the terminal to raw mode.

`docker pass set <id>` used to block on STDIN whenever no value was
given, echoing whatever the user typed. It now detects an interactive
terminal and shows a masked prompt instead, so the value neither lands
in shell history nor on screen, and nobody has to pipe it from a file.
Pipes and redirects still read STDIN to the end as before.

The prompt is a small raw-mode line reader on top of x/term. Raw mode is
entered with tcsetattr(TCSAFLUSH), as getpass(3), sudo and readpassphrase
do, so input typed before the prompt appeared, which the still-cooked tty
echoed in plaintext, is discarded rather than taken as the secret. Printable
runes are appended and echoed as '*', Backspace removes the last rune,
ctrl+u clears the line, and Enter or a bare LF (ctrl+j) ends it, as
with getpass(3). An empty Enter is an error, and so is input ending
before Enter: Ctrl-D, or Ctrl-Z on a Windows console, which the console
reader in os reports as EOF. Unlike getpass(3), a line that was not
entered is never stored. Ctrl-C, which raw mode
delivers as a key press rather than a signal, is reported as
context.Canceled so the root exits 130 silently. Cancelling the context, as
the root does on a signal, ends the read loop so its buffers are zeroed,
restores the terminal and returns; the root ends the prompt line when it
reports the signal. The blocked read of the tty itself cannot be
interrupted and is left to end with the process. Escape sequences are
skipped whole, cursor keys, mouse reports and stray terminal replies
among them, so they never leak into the value; a control byte inside
one ends it, so Enter and Ctrl-C get through regardless. A sequence
split after its ESC gets 50 ms to arrive before ESC counts as the
Escape key, as in vim and bubbletea.
The read buffer and the collected runes are zeroed when the prompt
returns.

Pastes are taken through bracketed paste mode only; the prompt never
reads the clipboard. A pasted trailing newline is trimmed, and a value
spanning several lines, or holding control characters other than tab,
is rejected with a pointer to STDIN rather than stored mangled. Ctrl-C
cancels inside a paste too, the way out should its end marker never
arrive. When the terminal does not bracket pastes, a multi-line
paste arrives as keystrokes; bytes behind the line end that came in the
same read, faster than anyone types, give it away and it is rejected the
same way rather than stored as its first line. The terminal is restored
and the input still queued in it discarded in
one tcsetattr(TCSAFLUSH) ioctl (TIOCSETAF on Darwin, TCSETSF on Linux;
SetConsoleMode then FlushConsoleInputBuffer on Windows), as getpass(3)
does. Restoring first and flushing second would leave a window on
BSD-derived ttys: turning ICANON back on sets PENDIN, and the next byte
to arrive has the queued remainder retyped with ECHO on, printing the
rest of the paste in plaintext. Pastes larger than the pty input queue
can still reach the shell, as they do with sudo and ssh prompts.

Cobra hands the subcommands docker's stream wrappers, which hide the
terminal file behind File(); unwrapFile reaches it to switch the
terminal to raw mode.

Signed-off-by: Johannes Großmann <grossmann.johannes@t-online.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant