CVE Patch Review

CVE-2026-50127 Weblate SSRF Filter Hardening for IPv6 Transition Prefix Bypass

CVE-2026-50127 · Updated 2026-07-08 Root-cause

Summary

Weblate’s outbound target validation relied on Python ipaddress globalness checks that can misclassify IPv6 transition forms as publicly routable even when they encapsulate private or metadata-service IPv4 destinations. The patch introduces explicit unwrapping for IPv4-mapped, IPv4-compatible, 6to4, and well-known NAT64 addresses before evaluating reachability, and adds a compatibility guard for the local-use NAT64 discovery prefix. Overall this addresses the stated SSRF bypass class with targeted tests for both direct IP validation and hostname resolution paths.

Analysis

Vulnerability

CVE-2026-50127 describes an SSRF bypass in Weblate’s outbound URL validation where IPv6 transition addresses could evade checks intended to block internal or non-public destinations. The vulnerable logic depended on ipaddress.is_global for the resolved address, but some IPv6 forms are globally routable at the IPv6 layer while still embedding an IPv4 destination that is private, loopback, or metadata-service reachable once translated by the host network stack.

The patch discussion and code in the upstream pull request show the affected cases explicitly: IPv4-mapped IPv6, IPv4-compatible IPv6, 6to4 (2002::/16), and NAT64 forms. This matters because a hostname resolving to an AAAA record such as 2002:a9fe:a9fe:: can encode 169.254.169.254, allowing requests toward cloud metadata endpoints if the environment routes that transition mechanism. The issue summary is also reflected in the public records at CVE.org and NVD.

if address is None:
    return False
address = _unwrap_ipv6_transition(address)
return address.is_global

...
if not _unwrap_ipv6_transition(ip_address).is_global:

That pattern was insufficient because the unwrap behavior and globalness decision did not fully account for all relevant transition prefixes and Python-version-specific classification behavior.

Patch

The patch in weblateorg/weblate#19768 introduces a dedicated transition-address normalization path and centralizes the final policy decision in a helper:

_NAT64_PREFIX = ipaddress.IPv6Network("64:ff9b::/96")
_NAT64_LOCAL_USE_PREFIX = ipaddress.IPv6Network("64:ff9b:1::/48")
_IPV4_COMPAT = ipaddress.IPv6Network("::0.0.0.0/96")

def _unwrap_ipv6_transition(address):
    if not isinstance(address, ipaddress.IPv6Address):
        return address
    if address.ipv4_mapped is not None:
        return address.ipv4_mapped
    if address.sixtofour is not None:
        return address.sixtofour
    if address in _NAT64_PREFIX:
        return ipaddress.IPv4Address(address.packed[-4:])
    if address in _IPV4_COMPAT:
        embedded = ipaddress.IPv4Address(address.packed[-4:])
        if int(embedded) != 0:
            return embedded
    return address

def _is_global_address(address) -> bool:
    if isinstance(address, ipaddress.IPv6Address) and address in _NAT64_LOCAL_USE_PREFIX:
        return False
    return _unwrap_ipv6_transition(address).is_global

There are two notable implementation choices. First, the patch unwraps only the well-known NAT64 prefix 64:ff9b::/96 into its embedded IPv4 destination. Second, it separately treats the local-use NAT64 discovery prefix 64:ff9b:1::/48 as non-global for compatibility with older Python versions, rather than decoding an embedded IPv4 from that range. The test suite was expanded to reject wrapped internal targets for direct IP validation and URL resolution, including 6to4 metadata wrappers and a local-use NAT64 case. The changelog entry states that outbound URL validation now rejects NAT64-wrapped internal addresses.

Review

Pros

  • The patch addresses the actual root mechanism: classification based on the outer IPv6 address instead of the effective embedded IPv4 destination. Unwrapping before policy evaluation is the correct security model for 6to4, IPv4-mapped, IPv4-compatible, and well-known NAT64 addresses.
  • The new _is_global_address() helper reduces duplicated policy logic and makes the compatibility exception for 64:ff9b:1::/48 explicit.
  • Tests are source-grounded and meaningful. They cover direct validation of wrapped loopback, RFC1918, and metadata-service addresses, plus a hostname-resolution path using a mocked AAAA record. This is important because SSRF filters often fail at the DNS-to-socket boundary.
  • The patch includes a non-regression check for legitimate public IPv6 addresses, which lowers the risk of over-blocking.
  • The comments document why Python’s ipaddress behavior is not sufficient on its own, which is useful for future maintainers and aligns with the vulnerability description in NVD.

Cons

  • The fix is tightly scoped to known transition formats. That is appropriate for this CVE, but it still depends on enumerating wrapper families rather than enforcing a more general post-resolution network policy at connect time.
  • The local-use NAT64 handling is split: 64:ff9b::/96 is unwrapped, while 64:ff9b:1::/48 is only force-classified as non-global. This is safe for blocking, but it means the code path is semantically inconsistent across NAT64-related prefixes.
  • The compatibility comment indicates behavior varies by Python version. That is handled here, but it also means the security property partly depends on runtime-library semantics, which should remain under regression test as supported Python versions change.
  • Based on the provided diff, the patch does not demonstrate broader defenses against DNS rebinding or TOCTOU gaps between validation and actual connection establishment. Those may be out of scope for this CVE, but they remain common SSRF hardening concerns.

Verdict

Root-cause.

This patch fixes the core validation flaw for the reported bypass class by normalizing transition-encoded IPv4 destinations before applying the public-address policy, and by explicitly denying the local-use NAT64 prefix where Python compatibility could otherwise misclassify it. The added tests directly exercise the vulnerable representations cited in the patch, including 6to4 and NAT64 forms that can reach internal-only IPv4 endpoints. For the scope evidenced in the upstream patch, this is a technically sound and sufficiently complete remediation.

Sources