X Tutup
The Wayback Machine - https://web.archive.org/web/20240626032224/https://github.com/python/cpython/issues/105629
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getpass should have an option to fail if it cannot input a password without hiding it #105629

Closed
callegar opened this issue Jun 10, 2023 · 13 comments
Labels
stdlib Python modules in the Lib dir type-feature A feature request or enhancement type-security A security issue

Comments

@callegar
Copy link

Bug report

Currently getpass falls back to non-hiding the user input on non-compliant terminals (this includes the QtConsole).

A warning is provided, but that will often not prevent users from entering a password in a visible way and in a terminal where a mere scroll-back can reveal it even later. The issue is that the code using getpass cannot prevent it, because there is no documented interface for forcing getpass to fail rather than asking for a password without hiding it. Having getpass fail, would let the code try other means to get the password.

From the documentation, I see that there is a warning. However, trying to convert it into an exception does not seem to work. For instance:

warnings.simplefilter('error')
password = getpass.getpass("Give me a password, please:")

does not prevent the password for being asked with non-hidden characters, as the exception appears to be generated later.

Your environment

  • CPython versions tested on: 3.11.3 (also tested with 3.10.x)
  • Operating system and architecture: Linux intel 64bit
@callegar callegar added the type-bug An unexpected behavior, bug, or error label Jun 10, 2023
@arhadthedev arhadthedev added stdlib Python modules in the Lib dir type-security A security issue labels Jun 10, 2023
@terryjreedy terryjreedy added type-feature A feature request or enhancement and removed type-bug An unexpected behavior, bug, or error labels Jun 10, 2023
@abdulsmapara
Copy link

@arhadthedev, @terryjreedy can I try to work on this issue ?

@terryjreedy
Copy link
Member

@abdulsmapara As far as I am concerned you can do what you want, but a) a PR might be premature at this point and b) I am not going to touch this issue other than to note as I did that a new API is a feature addition. I don't know if or how this function is actually used and if the change is really a good idea.

@abdulsmapara
Copy link

abdulsmapara commented Jun 11, 2023

@terryjreedy Thanks for your comment. I won't like to make a PR unless we are sure it is required.

My take on this would be to make a change to allow users to opt for an exception instead of echoing the password.

However, we should ensure that it is backward compatible (May be by adding an optional parameter to the method and defaulting it to current implementation).

@callegar
Copy link
Author

callegar commented Jun 11, 2023

A quick note to observe that, to the best of my understanding it might be possible to make getpass fail if it can get a password securely before it prompts the user for input, since the warning appears to be generated in the first line of fallback_getpass, however the warnings.simplefilter('error') is not enough for that.

So it may be just a matter of properly documenting how to do it or there may be an issue with the way in which the warning is generated. In either of these two cases a 'fix' might not result in an API change.

Further investigation shows that if you call fallback_getpass directly, the warnings.simplefilter('error') correctly turns the warning into an error before the user is prompted. So, the issue may be in the way in which unix_getpass calls fallback_getpass which prevents the warning from being managed by the warning filter as expected.

@ronaldoussoren
Copy link
Contributor

A quick note to observe that, to the best of my understanding it might be possible to make getpass fail if it can get a password securely before it prompts the user for input, since the warning appears to be generated in the first line of fallback_getpass, however the warnings.simplefilter('error') is not enough for that.

So it may be just a matter of properly documenting how to do it or there may be an issue with the way in which the warning is generated. In either of these two cases a 'fix' might not result in an API change.

>>> import getpass
>>> import warnings
>>> warnings.simplefilter("error", category=getpass.GetPassWarning)
>>> getpass.fallback_getpass("Hello:")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/getpass.py", line 121, in fallback_getpass
    warnings.warn("Can not control echo on the terminal.", GetPassWarning,
getpass.GetPassWarning: Can not control echo on the terminal.
>>> 

This appears to work for me (explicitly calling fallback_getpass to avoid having to arrange for not having a TTY available). I'm using the 3.12 beta, but the same also works with 3.9.

@callegar
Copy link
Author

callegar commented Jun 11, 2023

Yes, that works. The problem is that it does not work anymore if you use getpass or unix_getpass, that is if you try to catch the case where getpass cannot ask for the password securely. There may be a problem in the way unix_getpass calls fallback_getpass, which prevents the warning from being managed by the warning filter as expected.

@ronaldoussoren
Copy link
Contributor

A slightly more involved way to try to reproduce this without using QtConsole.

Script.py:

import os
os.setsid() # Drop controlling TTY
import getpass
import warnings
warnings.simplefilter("error", category=getpass.GetPassWarning)
value = getpass.getpass("Hello: ")
print(f"{value=}")

Usage: cat | python3.11 Script.py 2>&1 | cat

The second cat isn't strictly necessary, but ensures the proces doesn't have a reference to a TTY device. For me this goes into the fallback path and raises GetPassWarning. The first cat ensures that stdin is not a TTY, while os.setsid() ensures the process does not have a controlling TTY. The other call to the fallback implementation can be hit by replacing sys.stdin by some object that doesn't have a 'fileno' attribute (I used a proxy object that filters out the fileno attribute), and results in the same behaviour for me.

At first glance I don't see any code in unix_getpass that would cause problems with warning filters.

I don't know yet why the warning filter works for me, but doesn't work in your program using QtConsole.

BTW. I'm on a macOS system, not Linux. That shouldn't matter here though.

@callegar
Copy link
Author

callegar commented Jun 11, 2023

I have modified the previous script in this way:

import os
try:
    os.setsid() # Drop controlling TTY
except PermissionError:
    pass
import getpass
import warnings
warnings.simplefilter("error")

value="Foo"
try:
    value = getpass.getpass("Hello: ")
except getpass.GetPassWarning:
    print("Insecure terminal")
print(f"{value=}")

this lets the script be run as cat | python3.11 Script.py 2>&1 | cat or from the jupyter qtconsole as %run Script.py.

The result is surprising. In the first case, the exception is correctly generated and handled. In the second case, the password is asked in an insecure way and there is no exception. This suggests a bug in unix_getpass because the behavior should not be inconsistent in two cases. Maybe, the incompatibility in the terminal is determined via two different code paths in the two cases, causing the warning to be issued in different ways. The other option is that jupyter breaks the exceptions/warnings handling in some mysterious way.

@ronaldoussoren
Copy link
Contributor

ronaldoussoren commented Jun 12, 2023

The problem with getpass.getpass in QtConsole is a Jupyter issue:

In[4]: getpass.getpass
Out[4]: <bound method Kernel.getpass of <ipykernel.ipkernel.IPythonKernel object at 0x107f7b850>>

This shows that getpass.getpass is replaced with something from ipykernel and hence this is not something we can fix on the stdlib.

This is with Jupiter 1.0.0 (pip install jupyter in a fresh Python 3.11 venv).

@callegar
Copy link
Author

Should have noticed it, thanks, sorry for the noise then!

@abdulsmapara
Copy link

Thanks @ronaldoussoren , @callegar

@callegar
Copy link
Author

Opened ipython/ipykernel#1123 for the discussion to continue in the ipykernel tracker.

@callegar callegar closed this as not planned Won't fix, can't repro, duplicate, stale Jun 12, 2023
@ronaldoussoren
Copy link
Contributor

Should have noticed it, thanks, sorry for the noise then!

No problem, 3th-party modules monkey patching the stdlib luckily is fairly uncommon. I'm glad we found why the warnings filter doesn't work for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stdlib Python modules in the Lib dir type-feature A feature request or enhancement type-security A security issue
Projects
None yet
Development

No branches or pull requests

5 participants
X Tutup