Coverage for app/log_redaction.py: 100%

4 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-28 23:33 +0000

1"""Redact secret tokens from request paths before they reach access logs. 

2 

3Three routes carry a single secret directly in the URL path: 

4 

5* ``/reset-password/<token>`` — password-reset token (single use, short TTL) 

6* ``/share/<token>`` — public share link (long-lived, read access) 

7* ``/config/users/invite/<token>`` — user-invitation token (single use) 

8 

9gunicorn's access log records the full request line, so without redaction the 

10token would be written to ``/data/logs/openhangar-access.log`` (or stdout) in 

11clear text — see security finding N-25 (CWE-532). :func:`redact_sensitive_path` 

12masks just the token segment, leaving the rest of the path intact so the access 

13log keeps its operational value (status, IP, latency, which endpoint was hit). 

14""" 

15 

16import re 

17 

18# Anchored at the start of the path so we only match the three token routes and 

19# never their siblings (e.g. ``/aircraft/<id>/share/create`` or 

20# ``/config/users/invite/<id>/revoke``). The token segment runs up to the next 

21# ``/``, ``?`` or whitespace; the lookahead keeps any query string intact. 

22_SENSITIVE_PATH_RE = re.compile( 

23 r"^(/reset-password/|/share/|/config/users/invite/)[^/?\s]+(?=$|[?\s])" 

24) 

25 

26 

27def redact_sensitive_path(path: str) -> str: 

28 """Return ``path`` with a trailing secret-token segment replaced by 

29 ``[REDACTED]``. 

30 

31 Paths that do not start with one of the sensitive token prefixes — or that 

32 have no token segment — are returned unchanged. 

33 """ 

34 

35 return _SENSITIVE_PATH_RE.sub(r"\1[REDACTED]", path)