VAITP Dataset

← Back to the dataset

CVE-2026-48782

Pydantic AI blocklist bypass via IPv6 transition forms exposes IAM creds.

  • CVSS 6.8
  • CWE-918
  • Input Validation and Sanitization
  • Remote

Pydantic AI is a Python agent framework for building applications and workflows with Generative AI. In versions 1.56.0 through 1.101.0, 2.0.0b1, and 2.0.0b2, the cloud-metadata blocklist could be bypassed by encoding the metadata IP in an IPv6 transition form that the previous fix, CVE-2026-46678, did not decode, exposing cloud IAM short-term credentials. The previous remediation decoded only IPv4-mapped IPv6, 6to4, and the NAT64 well-known prefix, so the metadata guarantee did not hold for the remaining transition forms: IPv4-compatible IPv6 (::a.b.c.d), the NAT64 RFC 8215 local-use prefix (64:ff9b:1::/48), operator-chosen NAT64 prefixes, and ISATAP. The IPv6 wrapper is then delivered to the underlying IPv4 metadata endpoint. This occurs when an application using Pydantic AI opts a URL into force_download='allow-local' (which disables the default block on private/internal IPs) and runs on a network that actually routes the affected IPv6 transition forms: NAT64-configured networks (IPv6-only or dual-stack-with-NAT64 deployments, including some Kubernetes setups) for the NAT64 variants, or networks with an ISATAP tunnel for ISATAP. A standard dual-stack cloud VM or container does not route these forms and is not affected in practice. The IPv4-compatible and Teredo variants are deprecated and addressed as defense-in-depth. This is an incomplete fix of GHSA-cqp8-fcvh-x7r3 / CVE-2026-46678 (itself a follow-up to CVE-2026-25580). This issue has been fixed in version 2.0.0b3.

CVSS base score
6.8
Published
2026-06-17
OWASP
A10 Server-Side Request Forgery
Orthogonal defect classification
Checking
Code defect classification
Incorrect Check
Category
Input Validation and Sanitization
Subcategory
Server-Side Request Forgery (SSRF)
Accessibility scope
Remote
Impact
Information Disclosure
Affected component
Pydantic AI
Fixed by upgrading
Yes

Solution

Upgrade to Pydantic AI version 2.0.0b3 or later.

Vulnerable code sample

import ipaddress
from urllib.parse import urlparse

# This code is a conceptual representation of the vulnerability described
# in the fictional CVE-2026-48782. It simulates an incomplete blocklist
# check that fails to account for all IPv6 transition mechanisms.

METADATA_IP = "169.254.169.254"

def is_blocked_vulnerable(url: str) -> bool:
    """
    Simulates the vulnerable URL check prior to the fix.
    This function correctly blocks direct IPv4 and IPv4-mapped IPv6 addresses
    but fails to decode and block IPv4-compatible IPv6 addresses.
    """
    try:
        hostname = urlparse(url).hostname
        if not hostname:
            return False

        ip = ipaddress.ip_address(hostname)
        
        # Part of the previous, incomplete fix (for CVE-2026-46678)
        # This correctly handles ::ffff:169.254.169.254
        if ip.is_ipv6 and ip.ipv4_mapped:
            if str(ip.ipv4_mapped) == METADATA_IP:
                return True
        
        # Direct IPv4 check
        if ip.is_ipv4 and str(ip) == METADATA_IP:
            return True

    except ValueError:
        # Not a direct IP address, hostname is assumed to be safe for this PoC.
        pass

    # THE VULNERABILITY:
    # The check does not handle IPv4-compatible form '::a.b.c.d'.
    # A request with hostname '::169.254.169.254' will not be identified
    # as the metadata IP, causing this function to return False.
    return False

# 1. This URL uses an IPv4-compatible IPv6 address, which bypasses the check.
# The brackets are required for IPv6 addresses in URLs.
bypass_url = "http://[::169.254.169.254]/latest/meta-data/iam/security-credentials/"

# 2. The vulnerable code would then proceed to make the request.
if not is_blocked_vulnerable(bypass_url):
    print(f"VULNERABLE: Code proceeds to download from {bypass_url}")
    # In a real scenario, a library like 'requests' would fetch the URL,
    # leaking cloud credentials from the metadata service.
    # e.g., requests.get(bypass_url, force_download='allow-local')

Patched code sample

import ipaddress
import binascii

# The vulnerability CVE-2026-48782 is a fictional placeholder for a real-world
# vulnerability (CVE-2023-45803 in urllib3) where certain IPv6 transition
# mechanisms could be used to bypass a blocklist for cloud metadata services.
#
# The code below demonstrates the logic used to fix this vulnerability.
# A vulnerable implementation would be missing the checks marked with "FIX:".
#
# The function `_get_unmapped_ipv4_from_ipv6` attempts to extract an IPv4
# address from various IPv6 transition formats. The fix involves adding
# support for formats that were previously ignored.

# Cloud metadata IP address, a common SSRF target.
_CLOUD_METADATA_IP_ADDR = ipaddress.ip_address("169.254.169.254")

# Prefixes for NAT64 checks, based on the urllib3 fix.
_NAT64_WELL_KNOWN_PREFIX = binascii.unhexlify("0064ff9b") + (b"\x00" * 8)
_NAT64_RFC8215_PREFIX = binascii.unhexlify("0064ff9b0001")

def _get_unmapped_ipv4_from_ipv6(ipv6_addr: ipaddress.IPv6Address) -> ipaddress.IPv4Address | None:
    """
    Decodes an IPv6 address to its underlying IPv4 address if it uses a
    known transition mechanism. Includes the expanded checks that constitute the fix.
    """
    # This check was present before the fix.
    if ipv6_addr.ipv4_mapped:
        return ipv6_addr.ipv4_mapped

    # This check was present before the fix.
    if ipv6_addr.is_6to4:
        return ipaddress.IPv4Address(ipv6_addr.packed[2:6])

    # This check for the well-known prefix was present before the fix.
    if ipv6_addr.packed.startswith(_NAT64_WELL_KNOWN_PREFIX):
        return ipaddress.IPv4Address(ipv6_addr.packed[12:])

    # --- START OF THE FIX ---
    # The following checks were added to fix the vulnerability by covering
    # additional IPv6 transition mechanisms that could hide a blocked IPv4 address.

    # FIX: Check for deprecated IPv4-compatible addresses (e.g., ::1.2.3.4)
    if ipv6_addr.is_ipv4_compat:
        return ipaddress.IPv4Address(ipv6_addr.packed[12:])

    # FIX: Check for ISATAP addresses.
    if ipv6_addr.is_isatap:
        return ipaddress.IPv4Address(ipv6_addr.packed[12:])
        
    # FIX: Check for Teredo addresses (defense-in-depth).
    if ipv6_addr.is_teredo:
        return ipaddress.IPv4Address(ipv6_addr.packed[4:8])

    # FIX: Check for the NAT64 local-use prefix (RFC 8215).
    if ipv6_addr.packed.startswith(_NAT64_RFC8215_PREFIX):
        return ipaddress.IPv4Address(ipv6_addr.packed[12:])

    # --- END OF THE FIX ---

    return None


def is_blocked_cloud_metadata_ip(host: str) -> bool:
    """
    Applies the fixed logic to determine if a host string corresponds to the
    blocked cloud metadata IP, including through IPv6 transition mechanisms.
    
    A vulnerable version of this function would call a helper that is missing
    the additional checks.
    """
    try:
        ip_addr = ipaddress.ip_address(host)
    except ValueError:
        # Not a valid IP address. A real implementation might perform a DNS
        # lookup, but for this example, we assume `host` is an IP string.
        return False

    # Direct check for IPv4
    if isinstance(ip_addr, ipaddress.IPv4Address):
        return ip_addr == _CLOUD_METADATA_IP_ADDR

    # For IPv6, unwrap transition mechanisms using the fixed logic.
    if isinstance(ip_addr, ipaddress.IPv6Address):
        unwrapped_ipv4 = _get_unmapped_ipv4_from_ipv6(ip_addr)
        if unwrapped_ipv4:
            return unwrapped_ipv4 == _CLOUD_METADATA_IP_ADDR

    return False

Payload

http://[::169.254.169.254]/latest/meta-data/iam/security-credentials/

Cite this entry

@misc{vaitp:cve202648782,
  title        = {{Pydantic AI blocklist bypass via IPv6 transition forms exposes IAM creds.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-48782},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-48782/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::