fix(gateway): sanitize media URLs in base platform logs
This commit is contained in:
parent
b26e85bf9d
commit
1c0183ec71
@ -12,6 +12,7 @@ import random
|
|||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
@ -36,6 +37,43 @@ GATEWAY_SECRET_CAPTURE_UNSUPPORTED_MESSAGE = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_url_for_log(url: str, max_len: int = 80) -> str:
|
||||||
|
"""Return a URL string safe for logs (no query/fragment/userinfo)."""
|
||||||
|
if max_len <= 0:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if url is None:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
raw = str(url)
|
||||||
|
if not raw:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = urlsplit(raw)
|
||||||
|
except Exception:
|
||||||
|
return raw[:max_len]
|
||||||
|
|
||||||
|
if parsed.scheme and parsed.netloc:
|
||||||
|
# Strip potential embedded credentials (user:pass@host).
|
||||||
|
netloc = parsed.netloc.rsplit("@", 1)[-1]
|
||||||
|
base = f"{parsed.scheme}://{netloc}"
|
||||||
|
path = parsed.path or ""
|
||||||
|
if path and path != "/":
|
||||||
|
basename = path.rsplit("/", 1)[-1]
|
||||||
|
safe = f"{base}/.../{basename}" if basename else f"{base}/..."
|
||||||
|
else:
|
||||||
|
safe = base
|
||||||
|
else:
|
||||||
|
safe = raw
|
||||||
|
|
||||||
|
if len(safe) <= max_len:
|
||||||
|
return safe
|
||||||
|
if max_len <= 3:
|
||||||
|
return "." * max_len
|
||||||
|
return f"{safe[:max_len - 3]}..."
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Image cache utilities
|
# Image cache utilities
|
||||||
#
|
#
|
||||||
@ -112,8 +150,14 @@ async def cache_image_from_url(url: str, ext: str = ".jpg", retries: int = 2) ->
|
|||||||
raise
|
raise
|
||||||
if attempt < retries:
|
if attempt < retries:
|
||||||
wait = 1.5 * (attempt + 1)
|
wait = 1.5 * (attempt + 1)
|
||||||
_log.debug("Media cache retry %d/%d for %s (%.1fs): %s",
|
_log.debug(
|
||||||
attempt + 1, retries, url[:80], wait, exc)
|
"Media cache retry %d/%d for %s (%.1fs): %s",
|
||||||
|
attempt + 1,
|
||||||
|
retries,
|
||||||
|
_safe_url_for_log(url),
|
||||||
|
wait,
|
||||||
|
exc,
|
||||||
|
)
|
||||||
await asyncio.sleep(wait)
|
await asyncio.sleep(wait)
|
||||||
continue
|
continue
|
||||||
raise
|
raise
|
||||||
@ -214,8 +258,14 @@ async def cache_audio_from_url(url: str, ext: str = ".ogg", retries: int = 2) ->
|
|||||||
raise
|
raise
|
||||||
if attempt < retries:
|
if attempt < retries:
|
||||||
wait = 1.5 * (attempt + 1)
|
wait = 1.5 * (attempt + 1)
|
||||||
_log.debug("Audio cache retry %d/%d for %s (%.1fs): %s",
|
_log.debug(
|
||||||
attempt + 1, retries, url[:80], wait, exc)
|
"Audio cache retry %d/%d for %s (%.1fs): %s",
|
||||||
|
attempt + 1,
|
||||||
|
retries,
|
||||||
|
_safe_url_for_log(url),
|
||||||
|
wait,
|
||||||
|
exc,
|
||||||
|
)
|
||||||
await asyncio.sleep(wait)
|
await asyncio.sleep(wait)
|
||||||
continue
|
continue
|
||||||
raise
|
raise
|
||||||
@ -1266,7 +1316,12 @@ class BasePlatformAdapter(ABC):
|
|||||||
if human_delay > 0:
|
if human_delay > 0:
|
||||||
await asyncio.sleep(human_delay)
|
await asyncio.sleep(human_delay)
|
||||||
try:
|
try:
|
||||||
logger.info("[%s] Sending image: %s (alt=%s)", self.name, image_url[:80], alt_text[:30] if alt_text else "")
|
logger.info(
|
||||||
|
"[%s] Sending image: %s (alt=%s)",
|
||||||
|
self.name,
|
||||||
|
_safe_url_for_log(image_url),
|
||||||
|
alt_text[:30] if alt_text else "",
|
||||||
|
)
|
||||||
# Route animated GIFs through send_animation for proper playback
|
# Route animated GIFs through send_animation for proper playback
|
||||||
if self._is_animation_url(image_url):
|
if self._is_animation_url(image_url):
|
||||||
img_result = await self.send_animation(
|
img_result = await self.send_animation(
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from gateway.platforms.base import (
|
|||||||
GATEWAY_SECRET_CAPTURE_UNSUPPORTED_MESSAGE,
|
GATEWAY_SECRET_CAPTURE_UNSUPPORTED_MESSAGE,
|
||||||
MessageEvent,
|
MessageEvent,
|
||||||
MessageType,
|
MessageType,
|
||||||
|
_safe_url_for_log,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -18,6 +19,31 @@ class TestSecretCaptureGuidance:
|
|||||||
assert "~/.hermes/.env" in message
|
assert "~/.hermes/.env" in message
|
||||||
|
|
||||||
|
|
||||||
|
class TestSafeUrlForLog:
|
||||||
|
def test_strips_query_fragment_and_userinfo(self):
|
||||||
|
url = (
|
||||||
|
"https://user:pass@example.com/private/path/image.png"
|
||||||
|
"?X-Amz-Signature=supersecret&token=abc#frag"
|
||||||
|
)
|
||||||
|
result = _safe_url_for_log(url)
|
||||||
|
assert result == "https://example.com/.../image.png"
|
||||||
|
assert "supersecret" not in result
|
||||||
|
assert "token=abc" not in result
|
||||||
|
assert "user:pass@" not in result
|
||||||
|
|
||||||
|
def test_truncates_long_values(self):
|
||||||
|
long_url = "https://example.com/" + ("a" * 300)
|
||||||
|
result = _safe_url_for_log(long_url, max_len=40)
|
||||||
|
assert len(result) == 40
|
||||||
|
assert result.endswith("...")
|
||||||
|
|
||||||
|
def test_handles_small_and_non_positive_max_len(self):
|
||||||
|
url = "https://example.com/very/long/path/file.png?token=secret"
|
||||||
|
assert _safe_url_for_log(url, max_len=3) == "..."
|
||||||
|
assert _safe_url_for_log(url, max_len=2) == ".."
|
||||||
|
assert _safe_url_for_log(url, max_len=0) == ""
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# MessageEvent — command parsing
|
# MessageEvent — command parsing
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user