GHSA-cg7w-rg45-pc59
pydantic-ai: SSRF blocklist bypass via IPv4-compatible, SIIT/IVI, and local NAT64 IPv6 addresses (incomplete fix of CVE-2026-46678)
Details
## Summary
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 routes the affected IPv6 transition forms (NAT64- or ISATAP-configured networks)**, the cloud-metadata blocklist could be bypassed by encoding the metadata IP in an IPv6 transition form that the previous fix did not decode — IPv4-compatible IPv6 (`::a.b.c.d`), the NAT64 RFC 8215 local-use prefix (`64:ff9b:1::/48`), operator-chosen NAT64 prefixes, or ISATAP. The IPv6 wrapper is then delivered to the underlying IPv4 metadata endpoint, exposing cloud IAM short-term credentials.
**The bypass is exploitable only in environments whose network actually routes these 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 them 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](https://github.com/pydantic/pydantic-ai/security/advisories/GHSA-cqp8-fcvh-x7r3) / [CVE-2026-46678](https://nvd.nist.gov/vuln/detail/CVE-2026-46678) (itself a follow-up to [CVE-2026-25580](https://nvd.nist.gov/vuln/detail/CVE-2026-25580)). The prior remediation decoded only IPv4-mapped IPv6, 6to4, and the NAT64 well-known prefix; the metadata guarantee did not hold for the remaining transition forms.
## Severity
**MEDIUM** — `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N` = **6.8**
Same impact metrics and narrow attack surface as the parent advisory (AC:H): exploitation requires the application to have opted into `allow-local` on a URL influenced by untrusted input, and the NAT64/ISATAP variants additionally require the deployment network to route those forms.
**CWE-918**: Server-Side Request Forgery (SSRF)
## Affected Versions
| Package | Vulnerable | Patched | |---|---|---| | `pydantic-ai` | `>= 1.56.0, < 1.102.0`; `>= 2.0.0b1, < 2.0.0b3` | `1.102.0`; `2.0.0b3` | | `pydantic-ai-slim` | `>= 1.56.0, < 1.102.0`; `>= 2.0.0b1, < 2.0.0b3` | `1.102.0`; `2.0.0b3` |
These transition forms have not been decoded since SSRF protection was introduced in `1.56.0`.
## Who Is Affected
Users are affected **only if** their application explicitly opts a `FileUrl` (`ImageUrl`, `AudioUrl`, `VideoUrl`, `DocumentUrl`) into `force_download='allow-local'` on a URL that is, or could be, influenced by untrusted input.
Beyond that precondition, the affected encodings only reach a metadata endpoint in environments whose network actually routes them. The broadly-routable IPv4-mapped form was addressed in `1.99.0` (CVE-2026-46678); the additional forms addressed here require a **NAT64-configured network** (IPv6-only or dual-stack-with-NAT64 deployments, including some Kubernetes setups) for the NAT64 variants, or an **ISATAP tunnel** for the ISATAP variant. The IPv4-compatible and Teredo forms are deprecated and not routed by modern stacks; they are addressed as defense-in-depth. Most deployments on a standard dual-stack cloud VM or container are therefore not exploitable in practice, but the fix restores the "always blocked" guarantee for the environments that are.
Users are **not** affected if they use any of the bundled integrations to ingest user input, because they do not propagate `force_download` from external data:
- `Agent.to_web` / `clai web` - `VercelAIAdapter` - `AGUIAdapter` / `Agent.to_ag_ui`
Applications that only download from developer-controlled URLs are not affected.
## Remediation
Upgrade to `1.102.0` or later (or `2.0.0b3` or later on the 2.0 pre-release line). The cloud-metadata and private-IP blocklists now decode the embedded IPv4 of every standardized IPv6 transition form before evaluating it — IPv4-mapped, IPv4-compatible, 6to4, NAT64 across all prefix lengths (including the RFC 8215 local-use prefix and operator-chosen prefixes), ISATAP, and Teredo. The set of always-blocked cloud metadata/credential endpoints has also been expanded across providers.
## Workaround for Unpatched Versions
Avoid passing `force_download='allow-local'` on any URL that could be influenced by untrusted input. If developers must, resolve the hostname themselves and validate the result against their own metadata blocklist — including IPv6 transition forms — before constructing the `FileUrl`.
## Credits
Reported by [@SnailSploit](https://snailsploit.com).
Are you affected?
Enter the version of the package you're using.
Affected packages
1.56.0 Fixed in: 1.102.0 pip install --upgrade 'pydantic-ai-slim>=1.102.0' 1.56.0 Fixed in: 1.102.0 pip install --upgrade 'pydantic-ai>=1.102.0' 2.0.0b1 Fixed in: 2.0.0b3 pip install --upgrade 'pydantic-ai>=2.0.0b3' 2.0.0b1 Fixed in: 2.0.0b3 pip install --upgrade 'pydantic-ai-slim>=2.0.0b3' References
- https://github.com/pydantic/pydantic-ai/security/advisories/GHSA-cg7w-rg45-pc59 [WEB]
- https://nvd.nist.gov/vuln/detail/CVE-2026-48782 [ADVISORY]
- https://github.com/pydantic/pydantic-ai/pull/5596 [WEB]
- https://github.com/pydantic/pydantic-ai/commit/1add06179ba4de259f7ab977620b697b7209f7e4 [WEB]
- https://github.com/pydantic/pydantic-ai [PACKAGE]
- https://github.com/pydantic/pydantic-ai/releases/tag/v1.102.0 [WEB]