Fix password reader for Windows using msvcrt.getwch()

The existing password prompt uses /dev/tty and termios to read input
with echo disabled. Neither exists on Windows.

On Windows, msvcrt.getwch() reads a single character from the console
without echoing it. This adds a Windows code path that uses getwch()
in a loop, collecting characters until Enter is pressed.

The Unix path using termios and /dev/tty is unchanged.
This commit is contained in:
shitcoinsherpa 2026-03-05 17:02:03 -05:00 committed by teknium1
parent 4de5e017f1
commit 4bc32dc0f1

View file

@ -29,6 +29,7 @@ Usage:
import json import json
import logging import logging
import os import os
import platform
import signal import signal
import sys import sys
import time import time
@ -192,39 +193,48 @@ def _prompt_for_sudo_password(timeout_seconds: int = 45) -> str:
result = {"password": None, "done": False} result = {"password": None, "done": False}
def read_password_thread(): def read_password_thread():
"""Read password from /dev/tty with echo disabled.""" """Read password with echo disabled. Uses msvcrt on Windows, /dev/tty on Unix."""
tty_fd = None
old_attrs = None
try: try:
import termios if platform.system() == "Windows":
tty_fd = os.open("/dev/tty", os.O_RDONLY) import msvcrt
old_attrs = termios.tcgetattr(tty_fd) chars = []
new_attrs = termios.tcgetattr(tty_fd) while True:
new_attrs[3] = new_attrs[3] & ~termios.ECHO c = msvcrt.getwch()
termios.tcsetattr(tty_fd, termios.TCSAFLUSH, new_attrs) if c in ("\r", "\n"):
chars = [] break
while True: if c == "\x03":
b = os.read(tty_fd, 1) raise KeyboardInterrupt
if not b or b in (b"\n", b"\r"): chars.append(c)
break result["password"] = "".join(chars)
chars.append(b) else:
result["password"] = b"".join(chars).decode("utf-8", errors="replace") import termios
tty_fd = os.open("/dev/tty", os.O_RDONLY)
old_attrs = termios.tcgetattr(tty_fd)
new_attrs = termios.tcgetattr(tty_fd)
new_attrs[3] = new_attrs[3] & ~termios.ECHO
termios.tcsetattr(tty_fd, termios.TCSAFLUSH, new_attrs)
try:
chars = []
while True:
b = os.read(tty_fd, 1)
if not b or b in (b"\n", b"\r"):
break
chars.append(b)
result["password"] = b"".join(chars).decode("utf-8", errors="replace")
finally:
try:
termios.tcsetattr(tty_fd, termios.TCSAFLUSH, old_attrs)
except Exception:
pass
try:
os.close(tty_fd)
except Exception:
pass
except (EOFError, KeyboardInterrupt, OSError): except (EOFError, KeyboardInterrupt, OSError):
result["password"] = "" result["password"] = ""
except Exception: except Exception:
result["password"] = "" result["password"] = ""
finally: finally:
if tty_fd is not None and old_attrs is not None:
try:
import termios as _termios
_termios.tcsetattr(tty_fd, _termios.TCSAFLUSH, old_attrs)
except Exception:
pass
if tty_fd is not None:
try:
os.close(tty_fd)
except Exception:
pass
result["done"] = True result["done"] = True
try: try: