From ec79be9c839f488d93c297f10bd056aaad130d8f Mon Sep 17 00:00:00 2001 From: pax Date: Sat, 11 Apr 2026 16:10:50 -0500 Subject: [PATCH] =?UTF-8?q?security:=20fix=20#1=20=E2=80=94=20wire=20SSRF?= =?UTF-8?q?=20hook=20into=20cache=20download=20client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds validate_public_request to the cache module's shared httpx client event_hooks. Covers image/video/thumbnail downloads, which are the most likely exfil path — file_url comes straight from the booru JSON response and previously followed any 3xx that landed, so a hostile booru could point downloads at a private IP. Every redirect hop is now rejected if the target is non-public. The import is lazy inside _get_shared_client because core.api.base imports log_connection from this module; a top-level `from .api._safety import ...` would circular-import through api/__init__.py during cache.py load. By the time _get_shared_client is called the api package is fully loaded. Audit-Ref: SECURITY_AUDIT.md finding #1 Severity: High --- booru_viewer/core/cache.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/booru_viewer/core/cache.py b/booru_viewer/core/cache.py index eb23b86..e317435 100644 --- a/booru_viewer/core/cache.py +++ b/booru_viewer/core/cache.py @@ -79,6 +79,10 @@ def _get_shared_client(referer: str = "") -> httpx.AsyncClient: c = _shared_client if c is not None and not c.is_closed: return c + # Lazy import: core.api.base imports log_connection from this + # module, so a top-level `from .api._safety import ...` would + # circular-import through api/__init__.py during cache.py load. + from .api._safety import validate_public_request with _shared_client_lock: c = _shared_client if c is None or c.is_closed: @@ -89,6 +93,7 @@ def _get_shared_client(referer: str = "") -> httpx.AsyncClient: }, follow_redirects=True, timeout=60.0, + event_hooks={"request": [validate_public_request]}, limits=httpx.Limits(max_connections=10, max_keepalive_connections=5), ) _shared_client = c