GHSA-954P-556P-R752
Vulnerability from github – Published: 2026-07-28 21:42 – Updated: 2026-07-28 21:42Summary
JSON-Schema $ref values pointing at HTTP or HTTPS URLs are silently dereferenced by datamodel-code-generator with no IP/host validation, no scheme allow-list, and redirects followed unconditionally. The --allow-remote-refs gate added in 0.56.0 defaults to None, which only emits a deprecation warning and then fetches the URL anyway; only explicit --allow-remote-refs=false blocks the request. The fetched body is parsed as a sub-schema and reflected verbatim into the generated .py source. As a result, any JSON-Schema document the developer feeds to datamodel-codegen — including documents authored by an attacker — can pivot to arbitrary internal addresses and leak the response into the generated code, with no developer cooperation beyond running the tool.
Details
Sink: src/datamodel_code_generator/parser/jsonschema.py, _get_ref_body (lines 4776–4793, at tag 0.60.1 / commit a321547e):
def _get_ref_body(self, resolved_ref: str) -> dict[str, YamlValue]:
if is_url(resolved_ref):
if not resolved_ref.startswith("file://") and self.http_local_ref_path is None:
if self.allow_remote_refs is False:
raise Error(f"Fetching remote $ref is disabled: {resolved_ref}...")
if self.allow_remote_refs is None:
warn_deprecated( # (A) warn only
"behavior.remote-ref-default",
details=f"Reference: {resolved_ref}",
stacklevel=2,
)
return self._get_ref_body_from_url(resolved_ref) # (B) fetch fires
return self._get_ref_body_from_remote(resolved_ref)
- (A) emits a deprecation warning when
allow_remote_refsis its default (None); execution falls through to (B). - (B) routes the URL through
_get_text_from_url→get_body, the same fetcher described in other report — no IP validation, redirects followed.
The fetched body is then parsed as a sub-schema and merged into the model graph, so description, title, properties, etc. from the remote document end up in the generated .py source.
Only affects users who installed the [http] extra (pip install 'datamodel-code-generator[http]').
PoC
A self-contained one-file PoC is available here: https://gist.github.com/thegr1ffyn/562a6972d7dc3f2869458ae93fc608c0
Impact
Who is impacted. Anyone running datamodel-codegen on a JSON-Schema or OpenAPI document of uncertain provenance, with the [http] extra installed. Real-world scenarios:
- Trojaned OpenAPI document. A public REST API publishes
openapi.yaml. One$refpoints athttp://169.254.169.254/latest/meta-data/iam/security-credentials/<role>; runningdatamodel-codegenagainst the spec from an EC2 instance leaks the IAM credentials into the generated client. - Customer-supplied JSON Schema. A B2B SaaS auto-generates client code from customer-uploaded schemas. The customer adds an HTTP
$reftohttp://internal-admin:8080/users.json; the response (e.g. JSON user list) ends up in the generated Python the SaaS hands back to the customer. - CI on a private network. A PR adds
schemas/inbound.jsonwith an HTTP$refpointed at a service reachable only from the CI cluster's VPC; the CI runner fetches it and the generated.pyleaks the response in PR artifacts.
Higher real-world risk than the sibling CLI-flag SSRF (other SSRF in this report bundle) because the schema author chooses the destination — the developer doesn't have to type any URL.
Suggested fix.
- Flip the default in
parser/base.py:allow_remote_refs: bool = False, and remove the silent-fetch-with-warning fallback atparser/jsonschema.py:4786-4791. - When fetching is allowed, apply the same IP/host validation proposed for other submitted SSRF report to both the initial
$refURL and every redirect target. - Document HTTP
$refin a third-party schema as equivalent to runningcurlon the developer's host.
Maintainer resolution
This $ref report is fixed by the same shared HTTP fetcher hardening that resolved GHSA-rfr2-mq9m-x2qx. The code landed through the GHSA-rfr2 private security PR koxudaxi/datamodel-code-generator-ghsa-rfr2-mq9m-x2qx#1 and was 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.
No separate net code diff remains in the GHSA-954 private PR because the shared HTTP fetcher patch is already present on main. This advisory remains separate because the affected entry point is remote JSON Schema/OpenAPI $ref resolution rather than direct CLI --url input.
The fix does not flip the --allow-remote-refs compatibility default in this patch. Instead, it mitigates the SSRF issue by blocking localhost, loopback, private, link-local, reserved, and other non-public network targets by default, validating every redirect target before it is fetched, and requiring --allow-private-network / allow_private_network=True 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)
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.60.2"
},
"package": {
"ecosystem": "PyPI",
"name": "datamodel-code-generator"
},
"ranges": [
{
"events": [
{
"introduced": "0.9.1"
},
{
"fixed": "0.61.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-54690"
],
"database_specific": {
"cwe_ids": [
"CWE-918"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-28T21:42:22Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\n\nJSON-Schema `$ref` values pointing at HTTP or HTTPS URLs are silently dereferenced by `datamodel-code-generator` with no IP/host validation, no scheme allow-list, and redirects followed unconditionally. The `--allow-remote-refs` gate added in 0.56.0 defaults to `None`, which only emits a deprecation warning and then fetches the URL anyway; only explicit `--allow-remote-refs=false` blocks the request. The fetched body is parsed as a sub-schema and reflected verbatim into the generated `.py` source. As a result, any JSON-Schema document the developer feeds to `datamodel-codegen` \u2014 including documents authored by an attacker \u2014 can pivot to arbitrary internal addresses and leak the response into the generated code, with no developer cooperation beyond running the tool.\n\n### Details\n\nSink: `src/datamodel_code_generator/parser/jsonschema.py`, `_get_ref_body` (lines 4776\u20134793, at tag `0.60.1` / commit `a321547e`):\n\n```python\ndef _get_ref_body(self, resolved_ref: str) -\u003e dict[str, YamlValue]:\n if is_url(resolved_ref):\n if not resolved_ref.startswith(\"file://\") and self.http_local_ref_path is None:\n if self.allow_remote_refs is False:\n raise Error(f\"Fetching remote $ref is disabled: {resolved_ref}...\")\n if self.allow_remote_refs is None:\n warn_deprecated( # (A) warn only\n \"behavior.remote-ref-default\",\n details=f\"Reference: {resolved_ref}\",\n stacklevel=2,\n )\n return self._get_ref_body_from_url(resolved_ref) # (B) fetch fires\n return self._get_ref_body_from_remote(resolved_ref)\n```\n\n- (A) emits a deprecation warning when `allow_remote_refs` is its default (`None`); execution falls through to (B).\n- (B) routes the URL through `_get_text_from_url` \u2192 `get_body`, the same fetcher described in other report \u2014 no IP validation, redirects followed.\n\nThe fetched body is then parsed as a sub-schema and merged into the model graph, so `description`, `title`, `properties`, etc. from the remote document end up in the generated `.py` source.\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 is available here: https://gist.github.com/thegr1ffyn/562a6972d7dc3f2869458ae93fc608c0\n\n### Impact\n\n**Who is impacted.** Anyone running `datamodel-codegen` on a JSON-Schema or OpenAPI document of uncertain provenance, with the `[http]` extra installed. Real-world scenarios:\n\n1. **Trojaned OpenAPI document.** A public REST API publishes `openapi.yaml`. One `$ref` points at `http://169.254.169.254/latest/meta-data/iam/security-credentials/\u003crole\u003e`; running `datamodel-codegen` against the spec from an EC2 instance leaks the IAM credentials into the generated client.\n2. **Customer-supplied JSON Schema.** A B2B SaaS auto-generates client code from customer-uploaded schemas. The customer adds an HTTP `$ref` to `http://internal-admin:8080/users.json`; the response (e.g. JSON user list) ends up in the generated Python the SaaS hands back to the customer.\n3. **CI on a private network.** A PR adds `schemas/inbound.json` with an HTTP `$ref` pointed at a service reachable only from the CI cluster\u0027s VPC; the CI runner fetches it and the generated `.py` leaks the response in PR artifacts.\n\nHigher real-world risk than the sibling CLI-flag SSRF (other SSRF in this report bundle) because the *schema author* chooses the destination \u2014 the developer doesn\u0027t have to type any URL.\n\n**Suggested fix.**\n\n1. Flip the default in `parser/base.py`: `allow_remote_refs: bool = False`, and remove the silent-fetch-with-warning fallback at `parser/jsonschema.py:4786-4791`.\n2. When fetching is allowed, apply the same IP/host validation proposed for other submitted SSRF report to both the initial `$ref` URL and every redirect target.\n3. Document HTTP `$ref` in a third-party schema as equivalent to running `curl` on the developer\u0027s host.\n\n### Maintainer resolution\n\nThis `$ref` report is fixed by the same shared HTTP fetcher hardening that resolved GHSA-rfr2-mq9m-x2qx. The code landed through the GHSA-rfr2 private security PR koxudaxi/datamodel-code-generator-ghsa-rfr2-mq9m-x2qx#1 and was 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\nNo separate net code diff remains in the GHSA-954 private PR because the shared HTTP fetcher patch is already present on `main`. This advisory remains separate because the affected entry point is remote JSON Schema/OpenAPI `$ref` resolution rather than direct CLI `--url` input.\n\nThe fix does not flip the `--allow-remote-refs` compatibility default in this patch. Instead, it mitigates the SSRF issue by blocking localhost, loopback, private, link-local, reserved, and other non-public network targets by default, validating every redirect target before it is fetched, and requiring `--allow-private-network` / `allow_private_network=True` for trusted internal schema endpoints. Remote `$ref` fetching remains controlled by `--allow-remote-refs`; non-public/internal targets additionally require `--allow-private-network`.\n\nSubmitted by: Hamza Haroon (thegr1ffyn)",
"id": "GHSA-954p-556p-r752",
"modified": "2026-07-28T21:42:22Z",
"published": "2026-07-28T21:42:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/koxudaxi/datamodel-code-generator/security/advisories/GHSA-954p-556p-r752"
},
{
"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"
}
],
"schema_version": "1.4.0",
"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 JSON-Schema `$ref` to HTTP URL (silent by default)"
}
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.