lib.ec.ssh.Exec: Fix askpass script
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 4m10s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 4m21s
CI / Packaging test (pull_request) Successful in 0s
CI / Packaging - Kali Linux (push) Successful in 4m3s
CI / Packaging - OpenSUSE Tumbleweed (push) Successful in 4m32s
CI / Packaging test (push) Successful in 0s
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 4m10s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 4m21s
CI / Packaging test (pull_request) Successful in 0s
CI / Packaging - Kali Linux (push) Successful in 4m3s
CI / Packaging - OpenSUSE Tumbleweed (push) Successful in 4m32s
CI / Packaging test (push) Successful in 0s
__init_askpass() embeds the password verbatim in a generated bash script, with a newline included inside the double quotes of the echo -n. The askpass program therefore always appends a newline to the password, and any password containing quotes or shell metacharacters either breaks the script or injects commands into it. Embed the password as base64 and decode it with printf piped into base64 -d, so the script is safe for any password and prints the password exactly, byte for byte. Make __del__() idempotent: it deletes the environment variables and the script file, so a second call, e.g. an explicit one followed by garbage collection, raises KeyError and FileNotFoundError. Add a unit test that executes the generated script and compares its output with the password byte for byte. Signed-off-by: Jan Lindemann <jan@janware.com> Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.85.1
This commit is contained in:
parent
13e6ae8807
commit
dc101a5a7f
5 changed files with 62 additions and 3 deletions
4
test/unit/python/jw/pkg/lib/ec/Makefile
Normal file
4
test/unit/python/jw/pkg/lib/ec/Makefile
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
TOPDIR = ../../../../../../..
|
||||
|
||||
include $(TOPDIR)/make/proj.mk
|
||||
include $(JWBDIR)/make/dirs.mk
|
||||
7
test/unit/python/jw/pkg/lib/ec/ssh/Exec/Makefile
Normal file
7
test/unit/python/jw/pkg/lib/ec/ssh/Exec/Makefile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
TOPDIR = ../../../../../../../../..
|
||||
|
||||
include $(TOPDIR)/make/proj.mk
|
||||
include $(JWBDIR)/make/py-run.mk
|
||||
|
||||
all:
|
||||
test: run
|
||||
32
test/unit/python/jw/pkg/lib/ec/ssh/Exec/test.py
Normal file
32
test/unit/python/jw/pkg/lib/ec/ssh/Exec/test.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import subprocess
|
||||
|
||||
from jw.pkg.lib.ec.ssh.Exec import Exec
|
||||
from jw.pkg.lib.Uri import Uri
|
||||
|
||||
def askpass_output(password: str) -> bytes:
|
||||
uri = Uri('ssh://host')
|
||||
uri.set_password(password)
|
||||
ec = Exec(uri = uri)
|
||||
try:
|
||||
ec._Exec__init_askpass() # type: ignore[attr-defined]
|
||||
script = ec._Exec__askpass # type: ignore[attr-defined]
|
||||
assert script is not None
|
||||
out = subprocess.run(['bash', script], capture_output = True)
|
||||
assert out.returncode == 0, out.stderr
|
||||
return out.stdout
|
||||
finally:
|
||||
# -- Restore the environment and remove the script file
|
||||
ec.__del__()
|
||||
|
||||
# A plain password comes out byte-exact, without a trailing newline
|
||||
assert askpass_output('secret') == b'secret'
|
||||
|
||||
# Quotes, dollar signs, and backticks cannot break the script or
|
||||
# inject commands into it
|
||||
tricky = 'pa"ss$word `tick` $(dollar-paren) \'single\' \\backslash\\'
|
||||
assert askpass_output(tricky) == tricky.encode()
|
||||
|
||||
# A newline inside the password is preserved, and no extra one is added
|
||||
assert askpass_output('line1\nline2') == b'line1\nline2'
|
||||
|
||||
print('All ssh Exec askpass tests passed')
|
||||
4
test/unit/python/jw/pkg/lib/ec/ssh/Makefile
Normal file
4
test/unit/python/jw/pkg/lib/ec/ssh/Makefile
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
TOPDIR = ../../../../../../../..
|
||||
|
||||
include $(TOPDIR)/make/proj.mk
|
||||
include $(JWBDIR)/make/dirs.mk
|
||||
Loading…
Reference in a new issue