From e326ef3b16275bd73810f5d67bdb75de386b3d91 Mon Sep 17 00:00:00 2001 From: Talmaj Marinc Date: Tue, 30 Jun 2026 17:25:53 +0200 Subject: [PATCH] Fix IPv4-mapped IPv6 addresses --- app/model_downloader/security/ssrf.py | 7 +++++++ tests-unit/model_downloader_test/test_security.py | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/app/model_downloader/security/ssrf.py b/app/model_downloader/security/ssrf.py index 5c8bcd64d..1d8cd86e9 100644 --- a/app/model_downloader/security/ssrf.py +++ b/app/model_downloader/security/ssrf.py @@ -64,6 +64,13 @@ def is_blocked_ip(ip_str: str) -> bool: ip = ipaddress.ip_address(ip_str) except ValueError: return True # unparseable -> refuse + # On CPython before the gh-113171 fix (backported to 3.12.4/3.11.9/ + # 3.10.14/3.9.19) the is_* properties don't see through IPv4-mapped IPv6 + # (e.g. ::ffff:169.254.169.254), so resolve and re-check the embedded IPv4 + # to keep mapped metadata/private addresses from slipping past the filter. + mapped = getattr(ip, "ipv4_mapped", None) + if mapped is not None: + ip = mapped return ( ip.is_private or ip.is_loopback diff --git a/tests-unit/model_downloader_test/test_security.py b/tests-unit/model_downloader_test/test_security.py index 01a210bed..851f91cd3 100644 --- a/tests-unit/model_downloader_test/test_security.py +++ b/tests-unit/model_downloader_test/test_security.py @@ -56,6 +56,12 @@ def test_allow_any_extension_relaxes_extension_only(): ("172.16.0.1", True), ("::1", True), ("0.0.0.0", True), + # IPv4-mapped IPv6: must see through the mapping even on CPython + # versions predating the gh-113171 is_* property fix. + ("::ffff:169.254.169.254", True), # mapped cloud metadata + ("::ffff:127.0.0.1", True), # mapped loopback + ("::ffff:10.0.0.1", True), # mapped RFC1918 + ("::ffff:8.8.8.8", False), # mapped public address stays allowed ("8.8.8.8", False), ("1.1.1.1", False), ("not-an-ip", True), # unparseable -> refuse