{"id":"PYSEC-2026-2977","summary":"pydantic-ai: SSRF blocklist bypass via IPv4-compatible, SIIT/IVI, and local NAT64 IPv6 addresses (incomplete fix of CVE-2026-46678)","details":"## Summary\n\nWhen 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.\n\n**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.\n\nThis 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.\n\n## Severity\n\n**MEDIUM** — `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N` = **6.8**\n\nSame 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.\n\n**CWE-918**: Server-Side Request Forgery (SSRF)\n\n## Affected Versions\n\n| Package | Vulnerable | Patched |\n|---|---|---|\n| `pydantic-ai` | `\u003e= 1.56.0, \u003c 1.102.0`; `\u003e= 2.0.0b1, \u003c 2.0.0b3` | `1.102.0`; `2.0.0b3` |\n| `pydantic-ai-slim` | `\u003e= 1.56.0, \u003c 1.102.0`; `\u003e= 2.0.0b1, \u003c 2.0.0b3` | `1.102.0`; `2.0.0b3` |\n\nThese transition forms have not been decoded since SSRF protection was introduced in `1.56.0`.\n\n## Who Is Affected\n\nUsers 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.\n\nBeyond 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.\n\nUsers 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:\n\n- `Agent.to_web` / `clai web`\n- `VercelAIAdapter`\n- `AGUIAdapter` / `Agent.to_ag_ui`\n\nApplications that only download from developer-controlled URLs are not affected.\n\n## Remediation\n\nUpgrade 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.\n\n## Workaround for Unpatched Versions\n\nAvoid 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`.\n\n## Credits\n\nReported by [@SnailSploit](https://snailsploit.com).","aliases":["CVE-2026-48782","GHSA-cg7w-rg45-pc59","PYSEC-2026-2981"],"modified":"2026-07-13T16:43:36.056155706Z","published":"2026-07-13T15:46:24.590052Z","references":[{"type":"WEB","url":"https://github.com/pydantic/pydantic-ai/security/advisories/GHSA-cg7w-rg45-pc59"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-48782"},{"type":"WEB","url":"https://github.com/pydantic/pydantic-ai/pull/5596"},{"type":"WEB","url":"https://github.com/pydantic/pydantic-ai/commit/1add06179ba4de259f7ab977620b697b7209f7e4"},{"type":"PACKAGE","url":"https://github.com/pydantic/pydantic-ai"},{"type":"WEB","url":"https://github.com/pydantic/pydantic-ai/releases/tag/v1.102.0"},{"type":"PACKAGE","url":"https://pypi.org/project/pydantic-ai"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-cg7w-rg45-pc59"}],"affected":[{"package":{"name":"pydantic-ai","ecosystem":"PyPI","purl":"pkg:pypi/pydantic-ai"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.56.0"},{"fixed":"1.102.0"},{"introduced":"2.0.0b1"},{"fixed":"2.0.0b3"}]}],"versions":["1.100.0","1.101.0","1.56.0","1.57.0","1.58.0","1.59.0","1.60.0","1.61.0","1.62.0","1.63.0","1.64.0","1.65.0","1.66.0","1.67.0","1.68.0","1.69.0","1.70.0","1.71.0","1.72.0","1.73.0","1.74.0","1.75.0","1.76.0","1.77.0","1.78.0","1.79.0","1.80.0","1.81.0","1.82.0","1.83.0","1.84.0","1.84.1","1.85.0","1.85.1","1.86.0","1.86.1","1.87.0","1.88.0","1.89.0","1.89.1","1.90.0","1.91.0","1.92.0","1.93.0","1.94.0","1.95.0","1.95.1","1.96.0","1.96.1","1.97.0","1.98.0","1.99.0","2.0.0b1","2.0.0b2"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/pydantic-ai/PYSEC-2026-2977.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N"}]}