PYSEC-2026-3564
Vulnerability from pysec - Published: 2026-08-04 11:34 - Updated: 2026-08-04 13:36Summary
datamodel-code-generator's built-in HTTP fetcher (http.get_body) issues an httpx.GET against any URL passed to --url (or reached via a redirect chain) with no allow-list, no deny-list, no IP/host validation, and follow_redirects=True. Loopback addresses, RFC1918 ranges, link-local (169.254.169.254 cloud metadata), unique-local IPv6 and any other network-accessible target are all reachable. The JSON/YAML response body is parsed as a schema and reflected into the generated .py source, exfiltrating the response to anyone with access to that file (commonly committed to a repository).
Details
Sink: src/datamodel_code_generator/http.py, get_body (lines 31–61, at tag 0.60.1 / commit a321547e):
def get_body(url, headers=None, ignore_tls=False,
query_parameters=None, timeout=DEFAULT_HTTP_TIMEOUT) -> str:
httpx = _get_httpx()
try:
response = httpx.get(
url,
headers=headers,
verify=not ignore_tls,
follow_redirects=True, # (A)
params=query_parameters,
timeout=timeout,
)
except Exception as e:
...
if response.status_code >= 400:
...
content_type = response.headers.get("content-type", "").lower()
if "text/html" in content_type:
raise SchemaFetchError(...) # (B) — only filter
return response.text # (C) → embedded in generated.py
- (A) follows redirects unconditionally — a public URL → 302 → internal address chain works.
- (B) the only filter is rejecting
text/html. Non-HTML internal endpoints (JSON APIs, cloud metadata, admin services) pass through. - (C) the response body becomes the schema; its
title,description,properties, etc. land in the generated.pyas class attributes andField(description=...)strings.
get_body is called by parser/base.py:1326 (_get_text_from_url), which is reached from CLI argument --url <URL>. (The $ref path is a separate advisory — see GHSA-D.)
Only affects users who installed the [http] extra (pip install 'datamodel-code-generator[http]').
PoC
A self-contained one-file PoC available here: https://gist.github.com/thegr1ffyn/18de777d6c800a3b47715425e3f3e8f5
Impact
Who is impacted. Anyone who runs datamodel-codegen with a --url they didn't fully verify, or who runs it inside a network with reachable internal services. Realistic scenarios:
- Trojan documentation / README. A blog post or README example reads
datamodel-codegen --url https://schemas.example.com/user.json -o user.py. The attacker controlsexample.com, redirects tohttp://169.254.169.254/latest/meta-data/iam/security-credentials/<role>, and the IAM credentials end up as a docstring inuser.py. - Internal port scan / disclosure. Iterating
--url http://127.0.0.1:<port>/healthprobes localhost services; non-HTML, non-error responses confirm a service and leak its body into the generated file. - CI poisoning. A PR adds a Makefile rule that calls
datamodel-codegen --url $(SCHEMA_URL); the CI runner reaches every internal service in its VPC and the response lands in PR artifacts.
Suggested fix. Resolve the URL host, reject loopback / private / link-local / multicast / reserved IPs by default, disable redirects by default (follow_redirects=False), re-validate after each redirect if the user opts into following them, and add an --allow-private-network flag for opt-in legitimate use.
Maintainer resolution
This report was fixed together with GHSA-954p-556p-r752 by the private security PR koxudaxi/datamodel-code-generator-ghsa-rfr2-mq9m-x2qx#1, merged into the public repository as 5fdba4a09f2d7a9996a504975b7ef7d63e3715bb. Follow-up generated-file and coverage fixes were merged in koxudaxi/datamodel-code-generator#3279 and docs were synced in #3280. The patched release is 0.61.0.
The patch hardens the shared HTTP fetcher used by both direct CLI --url fetching and remote JSON Schema/OpenAPI $ref resolution:
- validates HTTP(S) URLs before fetching;
- blocks localhost, loopback, private, link-local, reserved, and other non-public network targets by default;
- disables automatic redirect following and validates each redirect target before requesting it;
- adds
--allow-private-network/allow_private_network=Trueas an explicit opt-in for trusted internal schema endpoints.
Remote $ref fetching remains controlled by --allow-remote-refs; non-public/internal targets additionally require --allow-private-network.
Submitted by: Hamza Haroon (thegr1ffyn)
| Name | purl | datamodel-code-generator | pkg:pypi/datamodel-code-generator |
|---|
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "datamodel-code-generator",
"purl": "pkg:pypi/datamodel-code-generator"
},
"ranges": [
{
"events": [
{
"introduced": "0.9.1"
},
{
"fixed": "0.61.0"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"0.10.0",
"0.10.1",
"0.10.2",
"0.10.3",
"0.11.0",
"0.11.1",
"0.11.10",
"0.11.11",
"0.11.12",
"0.11.13",
"0.11.14",
"0.11.15",
"0.11.16",
"0.11.17",
"0.11.18",
"0.11.19",
"0.11.2",
"0.11.20",
"0.11.3",
"0.11.4",
"0.11.5",
"0.11.6",
"0.11.7",
"0.11.8",
"0.11.9",
"0.12.0",
"0.12.1",
"0.12.2",
"0.12.3",
"0.13.0",
"0.13.1",
"0.13.2",
"0.13.3",
"0.13.4",
"0.13.5",
"0.14.0",
"0.14.1",
"0.15.0",
"0.16.0",
"0.16.1",
"0.17.0",
"0.17.1",
"0.17.2",
"0.18.0",
"0.18.1",
"0.19.0",
"0.20.0",
"0.21.0",
"0.21.1",
"0.21.2",
"0.21.3",
"0.21.4",
"0.21.5",
"0.22.0",
"0.22.1",
"0.23.0",
"0.24.0",
"0.24.1",
"0.24.2",
"0.25.0",
"0.25.1",
"0.25.2",
"0.25.3",
"0.25.4",
"0.25.5",
"0.25.6",
"0.25.7",
"0.25.8",
"0.25.9",
"0.26.0",
"0.26.1",
"0.26.2",
"0.26.3",
"0.26.4",
"0.26.5",
"0.27.0",
"0.27.1",
"0.27.2",
"0.27.3",
"0.28.0",
"0.28.1",
"0.28.2",
"0.28.3",
"0.28.4",
"0.28.5",
"0.29.0",
"0.30.0",
"0.30.1",
"0.30.2",
"0.31.0",
"0.31.1",
"0.31.2",
"0.32.0",
"0.33.0",
"0.34.0",
"0.35.0",
"0.36.0",
"0.37.0",
"0.38.0",
"0.39.0",
"0.40.0",
"0.41.0",
"0.42.0",
"0.42.1",
"0.42.2",
"0.43.0",
"0.43.1",
"0.44.0",
"0.45.0",
"0.46.0",
"0.47.0",
"0.48.0",
"0.49.0",
"0.50.0",
"0.51.0",
"0.52.0",
"0.52.1",
"0.52.2",
"0.53.0",
"0.54.0",
"0.54.1",
"0.55.0",
"0.56.0",
"0.56.1",
"0.57.0",
"0.58.0",
"0.59.0",
"0.59.1",
"0.60.0",
"0.60.1",
"0.60.2",
"0.9.1",
"0.9.2",
"0.9.3",
"0.9.4"
]
}
],
"aliases": [
"CVE-2026-54691",
"GHSA-rfr2-mq9m-x2qx"
],
"details": "### Summary\n\n`datamodel-code-generator`\u0027s built-in HTTP fetcher (`http.get_body`) issues an `httpx.GET` against any URL passed to `--url` (or reached via a redirect chain) with **no allow-list, no deny-list, no IP/host validation, and `follow_redirects=True`**. Loopback addresses, RFC1918 ranges, link-local (`169.254.169.254` cloud metadata), unique-local IPv6 and any other network-accessible target are all reachable. The JSON/YAML response body is parsed as a schema and reflected into the generated `.py` source, exfiltrating the response to anyone with access to that file (commonly committed to a repository).\n\n### Details\n\nSink: `src/datamodel_code_generator/http.py`, `get_body` (lines 31\u201361, at tag `0.60.1` / commit `a321547e`):\n\n```python\ndef get_body(url, headers=None, ignore_tls=False,\n query_parameters=None, timeout=DEFAULT_HTTP_TIMEOUT) -\u003e str:\n httpx = _get_httpx()\n try:\n response = httpx.get(\n url,\n headers=headers,\n verify=not ignore_tls,\n follow_redirects=True, # (A)\n params=query_parameters,\n timeout=timeout,\n )\n except Exception as e:\n ...\n if response.status_code \u003e= 400:\n ...\n content_type = response.headers.get(\"content-type\", \"\").lower()\n if \"text/html\" in content_type:\n raise SchemaFetchError(...) # (B) \u2014 only filter\n return response.text # (C) \u2192 embedded in generated.py\n```\n\n- (A) follows redirects unconditionally \u2014 a public URL \u2192 302 \u2192 internal address chain works.\n- (B) the only filter is rejecting `text/html`. Non-HTML internal endpoints (JSON APIs, cloud metadata, admin services) pass through.\n- (C) the response body becomes the schema; its `title`, `description`, `properties`, etc. land in the generated `.py` as class attributes and `Field(description=...)` strings.\n\n`get_body` is called by `parser/base.py:1326` (`_get_text_from_url`), which is reached from CLI argument `--url \u003cURL\u003e`. (The `$ref` path is a separate advisory \u2014 see GHSA-D.)\n\nOnly affects users who installed the `[http]` extra (`pip install \u0027datamodel-code-generator[http]\u0027`).\n\n### PoC\n\nA self-contained one-file PoC available here:\nhttps://gist.github.com/thegr1ffyn/18de777d6c800a3b47715425e3f3e8f5\n\n### Impact\n\n**Who is impacted.** Anyone who runs `datamodel-codegen` with a `--url` they didn\u0027t fully verify, or who runs it inside a network with reachable internal services. Realistic scenarios:\n\n1. **Trojan documentation / README.** A blog post or README example reads `datamodel-codegen --url https://schemas.example.com/user.json -o user.py`. The attacker controls `example.com`, redirects to `http://169.254.169.254/latest/meta-data/iam/security-credentials/\u003crole\u003e`, and the IAM credentials end up as a docstring in `user.py`.\n2. **Internal port scan / disclosure.** Iterating `--url http://127.0.0.1:\u003cport\u003e/health` probes localhost services; non-HTML, non-error responses confirm a service and leak its body into the generated file.\n3. **CI poisoning.** A PR adds a Makefile rule that calls `datamodel-codegen --url $(SCHEMA_URL)`; the CI runner reaches every internal service in its VPC and the response lands in PR artifacts.\n\n**Suggested fix.** Resolve the URL host, reject loopback / private / link-local / multicast / reserved IPs by default, disable redirects by default (`follow_redirects=False`), re-validate after each redirect if the user opts into following them, and add an `--allow-private-network` flag for opt-in legitimate use.\n\n### Maintainer resolution\n\nThis report was fixed together with GHSA-954p-556p-r752 by the private security PR koxudaxi/datamodel-code-generator-ghsa-rfr2-mq9m-x2qx#1, merged into the public repository as 5fdba4a09f2d7a9996a504975b7ef7d63e3715bb. Follow-up generated-file and coverage fixes were merged in koxudaxi/datamodel-code-generator#3279 and docs were synced in #3280. The patched release is 0.61.0.\n\nThe patch hardens the shared HTTP fetcher used by both direct CLI `--url` fetching and remote JSON Schema/OpenAPI `$ref` resolution:\n\n- validates HTTP(S) URLs before fetching;\n- blocks localhost, loopback, private, link-local, reserved, and other non-public network targets by default;\n- disables automatic redirect following and validates each redirect target before requesting it;\n- adds `--allow-private-network` / `allow_private_network=True` as an explicit opt-in for trusted internal schema endpoints.\n\nRemote `$ref` fetching remains controlled by `--allow-remote-refs`; non-public/internal targets additionally require `--allow-private-network`.\n\nSubmitted by: Hamza Haroon (thegr1ffyn)",
"id": "PYSEC-2026-3564",
"modified": "2026-08-04T13:36:16.026867Z",
"published": "2026-08-04T11:34:44.095724Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/koxudaxi/datamodel-code-generator/security/advisories/GHSA-rfr2-mq9m-x2qx"
},
{
"type": "WEB",
"url": "https://github.com/koxudaxi/datamodel-code-generator/commit/5fdba4a09f2d7a9996a504975b7ef7d63e3715bb"
},
{
"type": "PACKAGE",
"url": "https://github.com/koxudaxi/datamodel-code-generator"
},
{
"type": "WEB",
"url": "https://github.com/koxudaxi/datamodel-code-generator/releases/tag/0.61.0"
},
{
"type": "PACKAGE",
"url": "https://pypi.org/project/datamodel-code-generator"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-rfr2-mq9m-x2qx"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54691"
}
],
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "datamodel-code-generator vulnerable to SSRF via --url: no host/IP validation, follows redirects"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
How durations are calculated
Following the VTEM framework (Black Hat USA 2015), time to patch starts when the vendor or a coordinator was informed, total time to patch at discovery, and disclosure to patch at public disclosure; vendor response time runs from the report to its acknowledgement. Time to exploit and time unpatched start at the earliest discovery or report, falling back to public disclosure. Unpatched time ends at the first recorded patch, or the calculation time when no patch is recorded. Exploitation to patch shows the interval between those two events; a published proof of concept is a public exploit, not exploitation.
Each result shows its source dates. A negative interval means the end event came first; when date-only inputs leave the order open, the result says so. Date-only inputs are approximate. The earliest evidence wins: KEV report dates and sightings are labelled as such. Records describing another vulnerability are listed but never calculated from, and a PoC alone does not establish exploitation. Missing patch evidence does not prove that no patch exists. Filters below change the event list only.
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.