datamodel-code-generator's anti-SSRF guard validates the resolved IP of a fetch target once and then lets httpx perform its own independent DNS resolution to connect, so the validated address is never pinned. A hostname that resolves to a public IP at validation time and a private IP at connection time (DNS rebinding) bypasses the guard and reaches loopback, link-local cloud-metadata endpoints (169.254.169.254), and other internal services — even with the default allow_private_network=False. This is a server-side request forgery reachable when the tool fetches an attacker-influenced URL (remote $ref, or --url).
In src/datamodel_code_generator/http.py, get_body() calls _validate_url_for_fetch(), which resolves the host via _get_ips_from_host() (socket.getaddrinfo) and rejects non-global addresses:
ips = _get_ips_from_host(host) # resolution #1 (validation)
if not ips: return
if all(_is_safe_ip(ip) for ip in ips): return
raise SchemaFetchError(...) # blocks private/link-local/reserved
It then connects with a separate, independent resolution:
response = httpx.get(current_url, ...) # resolution #2 (connection) -- NOT pinned to #1
Nothing ties the connection to the IP that passed validation. Between the two resolutions a low-TTL attacker-controlled record can flip from a public address (passes the guard) to a private one (used by the connection). The redirect-handling loop in the same function does correctly re-validate each redirect URL, so this is specifically a TOCTOU/rebinding gap in the host-to-IP check, not a redirect issue.
Self-contained reproducer: https://gist.github.com/thegr1ffyn/c1d54dd6ff2a4c0d7d0dabe00c4985f4
It starts a loopback HTTP server standing in for an internal target and patches socket.getaddrinfo to return a public IP on the guard's lookup and 127.0.0.1 on httpx's the standard deterministic way to demonstrate this TOCTOU...
0.63.0Exploitability
AV:NAC:HPR:NUI:NScope
S:CImpact
C:HI:LA:N7.5/CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:L/A:NInput Validation