Redact credentials from go2rtc server log output (#181583)

This commit is contained in:
Paulus Schoutsen
2026-09-11 18:27:01 +00:00
committed by Franck Nijhof
parent 5337ec6ee5
commit c78c8105ee
2 changed files with 62 additions and 1 deletions
+17 -1
View File
@@ -4,6 +4,7 @@ import asyncio
from collections import deque
from contextlib import suppress
import logging
import re
from tempfile import NamedTemporaryFile
from aiohttp import ClientSession
@@ -105,6 +106,21 @@ _LOG_LEVEL_MAP = {
}
# go2rtc logs stream urls verbatim, which may embed camera credentials.
# Upstream strips the url userinfo since AlexxIT/go2rtc#2051, but no release
# includes it yet (v1.9.14 is the newest). It does not touch query parameters.
_URL_USERINFO = re.compile(r"://[^/?#\s@]+@")
_URL_CREDENTIAL_QUERY = re.compile(
r"([?&](?:auth|user|password)=)[^&\s]+", re.IGNORECASE
)
def _redact_url_credentials(msg: str) -> str:
"""Redact credentials from urls in a go2rtc log message."""
msg = _URL_USERINFO.sub("://****@", msg)
return _URL_CREDENTIAL_QUERY.sub(r"\1****", msg)
class Go2RTCServerStartError(HomeAssistantError):
"""Raised when server does not start."""
@@ -238,7 +254,7 @@ class Server:
assert process.stdout is not None
async for line in process.stdout:
msg = line[:-1].decode().strip()
msg = _redact_url_credentials(line[:-1].decode().strip())
self._log_buffer.append(msg)
loglevel = logging.WARNING
if len(split_msg := msg.split(" ", 2)) == 3:
+45
View File
@@ -425,3 +425,48 @@ async def test_server_restart_error(
assert "Unexpected error when restarting go2rtc server" in caplog.text
await server.stop()
@pytest.mark.parametrize(
("server_stdout", "expected_stdout"),
[
(
[
"09:00:03.467 INF [api] listen addr=127.0.0.1:1984",
"10:27:02.622 WRN producer.go:170 >"
' error="read tcp 192.168.1.96:42550->192.168.1.145:554: i/o timeout"'
" url=rtsp://admin:hunter2@192.168.1.145:554/Preview_01_sub",
"10:27:02.623 WRN [streams] url=http://192.168.1.145/snapshot"
"?auth=token&channel=0&user=admin&password=hunter2",
"10:27:02.624 WRN [streams] url=http://camera.local"
"?user=alice@example.com",
],
[
"10:27:02.622 WRN producer.go:170 >"
' error="read tcp 192.168.1.96:42550->192.168.1.145:554: i/o timeout"'
" url=rtsp://****@192.168.1.145:554/Preview_01_sub",
"10:27:02.623 WRN [streams] url=http://192.168.1.145/snapshot"
"?auth=****&channel=0&user=****&password=****",
"10:27:02.624 WRN [streams] url=http://camera.local?user=****",
],
)
],
)
@pytest.mark.usefixtures("mock_tempfile", "rest_client")
async def test_credentials_redacted_from_server_output(
hass: HomeAssistant,
mock_create_subprocess: MagicMock,
server: Server,
caplog: pytest.LogCaptureFixture,
expected_stdout: list[str],
) -> None:
"""Test credentials in urls are redacted from the logged server output."""
await server.start()
await hass.async_block_till_done()
assert_server_output_logged(expected_stdout, caplog, logging.WARNING)
assert "hunter2" not in caplog.text
assert "token" not in caplog.text
assert "alice@example.com" not in caplog.text
await server.stop()