[3.14] gh-138514: getpass: restrict echo_char to a single ASCII character (GH-138591) (#138988)

Co-authored-by: Benjamin Johnson <benjohnson2040@gmail.com>
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Co-authored-by: Brian Schubert <brianm.schubert@gmail.com>
This commit is contained in:
Miss Islington (bot) 2025-09-17 16:20:45 +02:00 committed by GitHub
parent ce48f4c845
commit 37f8a63e39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 57 additions and 9 deletions

View file

@ -27,9 +27,9 @@ The :mod:`getpass` module provides two functions:
The *echo_char* argument controls how user input is displayed while typing.
If *echo_char* is ``None`` (default), input remains hidden. Otherwise,
*echo_char* must be a printable ASCII string and each typed character
is replaced by it. For example, ``echo_char='*'`` will display
asterisks instead of the actual input.
*echo_char* must be a single printable ASCII character and each
typed character is replaced by it. For example, ``echo_char='*'`` will
display asterisks instead of the actual input.
If echo free input is unavailable getpass() falls back to printing
a warning message to *stream* and reading from ``sys.stdin`` and

View file

@ -33,8 +33,8 @@ def unix_getpass(prompt='Password: ', stream=None, *, echo_char=None):
prompt: Written on stream to ask for the input. Default: 'Password: '
stream: A writable file object to display the prompt. Defaults to
the tty. If no tty is available defaults to sys.stderr.
echo_char: A string used to mask input (e.g., '*'). If None, input is
hidden.
echo_char: A single ASCII character to mask input (e.g., '*').
If None, input is hidden.
Returns:
The seKr3t input.
Raises:
@ -144,10 +144,19 @@ def fallback_getpass(prompt='Password: ', stream=None, *, echo_char=None):
def _check_echo_char(echo_char):
# ASCII excluding control characters
if echo_char and not (echo_char.isprintable() and echo_char.isascii()):
raise ValueError("'echo_char' must be a printable ASCII string, "
f"got: {echo_char!r}")
# Single-character ASCII excluding control characters
if echo_char is None:
return
if not isinstance(echo_char, str):
raise TypeError("'echo_char' must be a str or None, not "
f"{type(echo_char).__name__}")
if not (
len(echo_char) == 1
and echo_char.isprintable()
and echo_char.isascii()
):
raise ValueError("'echo_char' must be a single printable ASCII "
f"character, got: {echo_char!r}")
def _raw_input(prompt="", stream=None, input=None, echo_char=None):

View file

@ -201,5 +201,41 @@ class UnixGetpassTest(unittest.TestCase):
self.assertEqual('Password: *******\x08 \x08', mock_output.getvalue())
class GetpassEchoCharTest(unittest.TestCase):
def test_accept_none(self):
getpass._check_echo_char(None)
@support.subTests('echo_char', ["*", "A", " "])
def test_accept_single_printable_ascii(self, echo_char):
getpass._check_echo_char(echo_char)
def test_reject_empty_string(self):
self.assertRaises(ValueError, getpass.getpass, echo_char="")
@support.subTests('echo_char', ["***", "AA", "aA*!"])
def test_reject_multi_character_strings(self, echo_char):
self.assertRaises(ValueError, getpass.getpass, echo_char=echo_char)
@support.subTests('echo_char', [
'\N{LATIN CAPITAL LETTER AE}', # non-ASCII single character
'\N{HEAVY BLACK HEART}', # non-ASCII multibyte character
])
def test_reject_non_ascii(self, echo_char):
self.assertRaises(ValueError, getpass.getpass, echo_char=echo_char)
@support.subTests('echo_char', [
ch for ch in map(chr, range(0, 128))
if not ch.isprintable()
])
def test_reject_non_printable_characters(self, echo_char):
self.assertRaises(ValueError, getpass.getpass, echo_char=echo_char)
# TypeError Rejection
@support.subTests('echo_char', [b"*", 0, 0.0, [], {}])
def test_reject_non_string(self, echo_char):
self.assertRaises(TypeError, getpass.getpass, echo_char=echo_char)
if __name__ == "__main__":
unittest.main()

View file

@ -903,6 +903,7 @@ Jim Jewett
Pedro Diaz Jimenez
Orjan Johansen
Fredrik Johansson
Benjamin K. Johnson
Gregory K. Johnson
Kent Johnson
Michael Johnson

View file

@ -0,0 +1,2 @@
Raise :exc:`ValueError` when a multi-character string is passed to the
*echo_char* parameter of :func:`getpass.getpass`. Patch by Benjamin Johnson.