"""Every request has a name, and its log lines carry it (OBS-005). Before this, a 500 in errors.log and the lines in app.log that led to it were related only by their timestamps — which is not a relation once the server is handling more than one request at a time. And a user saying "it broke when I clicked save" gave nobody anything to grep for. The id is deliberately generated, never read from an inbound header. That is the test worth reading in this file: accepting one would be convenient for tracing through nginx, and would also let any caller write arbitrary text — newlines included — into the log, which is how a log stops being evidence. """ import logging import re import pytest from app.logging_config import NO_REQUEST, RequestIdFilter ID_PATTERN = re.compile(r'^[0-9a-f]{16}$') class TestTheHeader: def test_every_response_carries_one(self, client): response = client.get('/auth/login') assert ID_PATTERN.match(response.headers['X-Request-Id']) def test_two_requests_get_different_ids(self, client): first = client.get('/auth/login').headers['X-Request-Id'] second = client.get('/auth/login').headers['X-Request-Id'] assert first != second def test_an_error_response_carries_one_too(self, client): """The case it exists for.""" response = client.get('/no-such-page') assert response.status_code == 404 assert ID_PATTERN.match(response.headers['X-Request-Id']) class TestInboundHeadersAreIgnored: """The security property, not a convenience. Waitress currently runs with trusted_proxy='*' (OPS-002), so anything in an inbound header comes from whoever sent the request. """ def test_a_supplied_id_is_not_adopted(self, client): response = client.get('/auth/login', headers={'X-Request-Id': 'chosen-by-the-caller'}) assert response.headers['X-Request-Id'] != 'chosen-by-the-caller' assert ID_PATTERN.match(response.headers['X-Request-Id']) def test_the_id_is_always_hex_so_it_cannot_forge_a_log_line(self, client): """The property that makes log injection impossible. A value carrying a newline writes a second line that looks exactly like a real log entry — that is how a log stops being evidence. Since the id is generated from a fixed alphabet rather than taken from the request, no input reaches the log through this field at all. Asserted on the alphabet rather than by sending a newline: Werkzeug's test client refuses to send such a header, so the attack cannot even be constructed through it — which proves nothing about the app. """ for supplied in ('../../etc/passwd', 'a b c', '