Coverage for astrocyte/_log_safety.py: 100%
6 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-04 05:24 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-04 05:24 +0000
1"""Log-injection sanitization helpers.
3CodeQL's ``py/log-injection`` query flags any log statement that
4substitutes a user-controlled value, because newline / carriage-return
5characters in the value can forge fake log entries (CWE-117).
7The fix is to strip CRLF (and ASCII control characters more broadly)
8from user-controlled values before they reach the formatter. We do this
9with a tiny ``safe()`` helper rather than reaching for a structured
10logger — Astrocyte's existing log call sites are scattered and we want
11the smallest possible change at each one:
13 logger.info("retain bank=%s", safe(bank_id))
15``safe`` accepts any value (str, int, UUID, dict, ...). For strings, it
16replaces every control character (0x00-0x1F, 0x7F) with a single space.
17For non-strings it falls back to ``str(value)`` first, then sanitizes
18the result. This keeps surrounding log context intact while preventing
19log forgery and terminal-escape injection.
21The double-pipeline (``str(value)`` then sanitize) means an attacker
22controlling a ``__str__`` method on a custom class cannot bypass the
23filter by returning bytes with embedded newlines.
24"""
26from __future__ import annotations
28from typing import Any
30_CONTROL_TRANSLATE = {c: 0x20 for c in range(0x20)}
31_CONTROL_TRANSLATE[0x7F] = 0x20
34def safe(value: Any) -> str:
35 """Return a log-safe string representation of ``value``.
37 Replaces ASCII control characters (``\\x00``-``\\x1F`` and ``\\x7F``)
38 with a single space. Intended for substitution into log-format
39 arguments where the value originates from user input — request
40 bodies, metadata dicts, query parameters, etc.
42 >>> safe("hello\\nworld")
43 'hello world'
44 >>> safe("bank-42")
45 'bank-42'
46 >>> safe(None)
47 'None'
48 """
49 return str(value).translate(_CONTROL_TRANSLATE)