Keep the session token out of the HTTP client's debug log (#1835)

Camera logins over `SslAesTransport` send their requests to `/stok=<token>/ds`, and `HttpClient` logs every URL it posts to at debug, so the session token ends up in the debug logs people paste into issues.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZeliardM
2026-11-04 12:56:22 -05:00
committed by GitHub
parent a68f2b3304
commit 1c3be98e67
2 changed files with 41 additions and 1 deletions

View File

@@ -5,6 +5,7 @@ from __future__ import annotations
import asyncio
import builtins
import logging
import re
import ssl
import time
from typing import Any
@@ -22,6 +23,9 @@ from .json import loads as json_loads
_LOGGER = logging.getLogger(__name__)
# The session token in /stok=<token>/ds URLs, e.g. after a camera's secure login
_SESSION_TOKEN = re.compile(r"(stok=)[^/?#]+")
def get_cookie_jar() -> aiohttp.CookieJar:
"""Return a new cookie jar with the correct options for device communication."""
@@ -86,7 +90,8 @@ class HttpClient:
)
await asyncio.sleep(sleep)
_LOGGER.debug("Posting to %s", url)
if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug("Posting to %s", _SESSION_TOKEN.sub(r"\1REDACTED", str(url)))
response_data = None
self._last_url = url
self.client.cookie_jar.clear()

View File

@@ -1,4 +1,5 @@
import builtins
import logging
import re
import aiohttp
@@ -114,3 +115,37 @@ async def test_httpclient_errors(
assert mock_response.call_count == 1
else:
assert conn.call_count == 1
class _OkResponse:
status = 200
connection = None
async def __aenter__(self):
return self
async def __aexit__(self, exc_t, exc_v, exc_tb) -> None:
pass
async def read(self) -> bytes:
return b"{}"
async def test_httpclient_debug_log_hides_session_token(
mocker: MockerFixture, caplog: pytest.LogCaptureFixture
) -> None:
"""The /stok=<token>/ds session token of a camera login is not logged."""
async def _post(url, *_, **__):
return _OkResponse()
mocker.patch.object(aiohttp.ClientSession, "post", side_effect=_post)
client = HttpClient(DeviceConfig("127.0.0.1"))
with caplog.at_level(logging.DEBUG, logger="kasa.httpclient"):
await client.post(URL("https://127.0.0.1/stok=S3cr3tT0k3n/ds"), data=b"x")
await client.post(URL("http://127.0.0.1/app/handshake1"), data=b"x")
assert "S3cr3tT0k3n" not in caplog.text
assert "Posting to https://127.0.0.1/stok=REDACTED/ds" in caplog.text
assert "Posting to http://127.0.0.1/app/handshake1" in caplog.text