eleven / messenger Public

deploy: POSIX password prompt — dash has no 'read -s' (#1433)

The Bitwarden unlock prompt used 'read -rs', a bashism: on a Debian operator
box /bin/sh is dash, which dies with "read: Illegal option -s" mid-publish.
Hide typing with stty -echo around a plain read -r instead, restoring the
terminal on every exit including ^C at the prompt.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell c41be8ec15a144e1153af0d34aee4aa217855db4 parent 78f5c3c
1 file changed, +18 −2
  • deploy/bw-lib.sh +18 −2
diff --git a/deploy/bw-lib.sh b/deploy/bw-lib.sh
index 50bd9e4..bc606fe 100644
--- a/deploy/bw-lib.sh
+++ b/deploy/bw-lib.sh
@@ -6,7 +6,8 @@
# 1. **bw never prompts — we do.** bw's inquirer prompt writes to stdout, so
# inside `$(...)` it sits invisible and the script "hangs" until a stray
# Enter redraws it (the doctor lesson, then the release-server lesson).
-# Instead, OUR `read -rs` prompts on /dev/tty with the label on stderr —
+# Instead, OUR prompt (stty -echo + a plain `read -r`, POSIX — dash has
+# no `read -s`) reads /dev/tty with the label on stderr —
# always visible, capture-proof — and the password reaches bw through
# --passwordenv: never argv (ps would show it), never disk, unset the
# moment the unlock returns. bw is a non-interactive backend, full stop.
@@ -50,11 +51,26 @@ bw_unlock_session() {
return 1
fi
printf "Bitwarden master password (typing is hidden): " >&2
- IFS= read -rs LCHAT_BW_PASSWORD </dev/tty || {
+ # `read -s` is a bashism — Debian's /bin/sh (dash) rejects it with
+ # "Illegal option -s" (bit an operator mid-publish, 2026-08-06). Hide the
+ # typing the POSIX way: drop tty echo around a plain `read -r`. Restore on
+ # every exit from the read, including ^C at the prompt (restore, then
+ # re-raise INT so the script still dies) — a password prompt that leaves
+ # the terminal echo-less is its own papercut. The `|| true` tails keep
+ # `set -e` sourcing scripts alive when there's no stty to save (odd ttys).
+ _bw_stty="$(stty -g </dev/tty 2>/dev/null || true)"
+ _bw_restore_tty() { [ -n "$_bw_stty" ] && stty "$_bw_stty" </dev/tty 2>/dev/null || true; }
+ trap '_bw_restore_tty; trap - INT; kill -INT $$' INT
+ [ -n "$_bw_stty" ] && stty -echo </dev/tty 2>/dev/null || true
+ IFS= read -r LCHAT_BW_PASSWORD </dev/tty || {
+ _bw_restore_tty
+ trap - INT
echo >&2
unset LCHAT_BW_PASSWORD
return 1
}
+ _bw_restore_tty
+ trap - INT
echo >&2
# 90s: the KDF is seconds, but bw's first server round-trip gets headroom.
_bw_sess="$(LCHAT_BW_PASSWORD="$LCHAT_BW_PASSWORD" perl -e 'alarm 90; exec @ARGV' \