diff --git a/umbratrans_web/tests/test_form_safety.py b/umbratrans_web/tests/test_form_safety.py new file mode 100644 index 0000000..4fcccf8 --- /dev/null +++ b/umbratrans_web/tests/test_form_safety.py @@ -0,0 +1,120 @@ +"""A broken script must not be able to submit a password in a URL. + +This exists because it happened. A syntax error I introduced into `app.js` meant the +submit handler which calls `preventDefault()` never ran, and both password fields were +submitted as a native form GET: + + GET /?email=umbra%40bdmurphy.me&password=...& + +which the server logged in plain text, three times, and which would also have sat in +browser history and any proxy's access log. + +Two defences, and this file checks both: + +1. **Every form declares `method="post"`.** A form with no method attribute defaults to + GET. The script intercepts and submits via fetch; `method` only decides what happens + when the script does not run, which is exactly the case that failed. + +2. **Every script parses.** A syntax error is silent in a browser - the console shows it, + the page just does not work - so it is checked here against a real parser rather than + by counting braces. A hand-written brace counter was used first and gave two false + readings, one of them "balanced" for a file that did not parse at all. + +The parser is imported lazily and the file skips when it is absent, so this does not +become a hard dependency on a package that may not be installed. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +WEB = Path(__file__).resolve().parents[2] / "web" + +PAGES = sorted(WEB.glob("*.html")) + + +def _forms(html: str) -> list[str]: + import re + + return re.findall(r"
The link from your email opens this page automatically. You can also paste just the token.