blitz_api/tests/test_change_password.py
fusion44 c4afca50e9
fix(system): stop leaking passwords via change-password endpoint
The change-password endpoint accepted old_password/new_password as bare
str parameters, i.e. query parameters, so the passwords ended up in
access logs, proxy logs and browser history. Accept them in a
ChangePasswordInput request body instead.

Also mark the RaspiBlitz blitz.passwords.sh check/set invocations
sensitive=True so the plaintext passwords are not written to the debug
log, and guard against a missing password type (was an AttributeError
-> 500).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 21:42:25 +02:00

50 lines
1.9 KiB
Python

"""
Regression tests for the change-password endpoint.
- Passwords were accepted as query parameters, so they leaked into access
logs, proxy logs and browser history. They must travel in the request body.
- The RaspiBlitz password scripts were called without sensitive=True, so the
plaintext passwords were written to the debug log.
"""
from app.api.models import ProcessResult
from app.external.result_type.src.result.result import Ok
def test_change_password_uses_request_body_not_query():
from app.main import app
spec = app.openapi()
path = next(p for p in spec["paths"] if p.endswith("change-password"))
operation = spec["paths"][path]["post"]
param_names = {p["name"] for p in operation.get("parameters", [])}
assert "old_password" not in param_names, "password must not be a query param"
assert "new_password" not in param_names, "password must not be a query param"
assert "requestBody" in operation, "passwords must be sent in the request body"
async def test_raspiblitz_change_password_calls_are_sensitive(monkeypatch):
from app.system.impl import raspiblitz as rb
# the shell-script existence check would exit(1) in the test env
monkeypatch.setattr(
rb.RaspiBlitzSystem, "_check_shell_scripts_status", lambda self: None
)
system = rb.RaspiBlitzSystem()
calls = []
async def fake_exec(command, **kwargs):
calls.append(kwargs)
# 'check' -> correct=1; 'set' -> no error key
return Ok(ProcessResult(0, "correct=1\n", ""))
monkeypatch.setattr(rb, "exec_bash_command", fake_exec)
monkeypatch.setattr(rb, "password_valid", lambda p: True)
await system.change_password("a", "oldpass12", "newpass12")
assert len(calls) == 2, "expected a check call and a set call"
assert all(c.get("sensitive") is True for c in calls), (
"password script calls must be marked sensitive so they aren't logged"
)