Access log: the pairing poll's ?secret= and a socket's ?ticket= are masked too

This commit is contained in:
Melvin Strobl
2026-10-16 21:57:35 +02:00
parent 464490bc37
commit efd883d092
2 changed files with 33 additions and 5 deletions
+6 -4
View File
@@ -62,21 +62,23 @@ from fluksio.flow.workers import PythonWorkerPool
logger = logging.getLogger(__name__)
#: A credential in a query string: the live socket's and a worker's `?token=`.
_TOKEN_IN_QUERY = re.compile(r"([?&][\w-]*token=)[^&\s]+")
#: A credential in a query string: the live socket's `?token=` or `?ticket=`, a
#: worker's `?token=`, the pairing poll's `?secret=`.
_CREDENTIAL_IN_QUERY = re.compile(r"([?&](?:[\w-]*token|[\w-]*secret|ticket)=)[^&\s]+")
class _RedactTokens(logging.Filter):
"""Keep query-string credentials out of uvicorn's request lines.
uvicorn logs each request and websocket handshake with its query string,
and the live socket and a worker's attach carry their token there.
and the live socket, a worker's attach and a device polling for its
pairing carry their credential there.
"""
def filter(self, record: logging.LogRecord) -> bool:
if isinstance(record.args, tuple):
record.args = tuple(
_TOKEN_IN_QUERY.sub(r"\1***", arg) if isinstance(arg, str) else arg
_CREDENTIAL_IN_QUERY.sub(r"\1***", arg) if isinstance(arg, str) else arg
for arg in record.args
)
return True
+27 -1
View File
@@ -1,4 +1,4 @@
"""A token in a query string stays out of uvicorn's request lines."""
"""A credential in a query string stays out of uvicorn's request lines."""
import logging
@@ -26,3 +26,29 @@ def test_a_token_in_the_query_is_not_logged() -> None:
assert [record.getMessage() for record in records] == [
'192.0.2.1:0 - "WebSocket /api/v1/flows/ws?token=***&x=1" [accepted]'
]
def test_the_pairing_secret_is_not_logged() -> None:
records: list[logging.LogRecord] = []
handler = logging.Handler()
handler.emit = records.append # type: ignore[method-assign]
log = logging.getLogger("uvicorn.access")
level = log.level
log.addHandler(handler)
log.setLevel(logging.INFO)
try:
log.info(
'%s - "%s %s HTTP/%s" %d',
"192.0.2.1:0",
"GET",
"/api/v1/devices/pair/ABCD2345?secret=s3cr3t",
"1.1",
202,
)
finally:
log.removeHandler(handler)
log.setLevel(level)
assert [record.getMessage() for record in records] == [
'192.0.2.1:0 - "GET /api/v1/devices/pair/ABCD2345?secret=*** HTTP/1.1" 202'
]