GHSA-RFR2-MQ9M-X2QX

Vulnerability from github – Published: 2026-07-28 21:30 – Updated: 2026-07-28 21:30
VLAI
Summary
datamodel-code-generator vulnerable to SSRF via --url: no host/IP validation, follows redirects
Details

Summary

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 .py as class attributes and Field(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:

  1. 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/<role>, and the IAM credentials end up as a docstring in user.py.
  2. Internal port scan / disclosure. Iterating --url http://127.0.0.1:<port>/health probes localhost services; non-HTML, non-error responses confirm a service and leak its body into the generated file.
  3. 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=True as 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)

Show details on source website

{
  "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-54691"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-28T21:30:18Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "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": "GHSA-rfr2-mq9m-x2qx",
  "modified": "2026-07-28T21:30:18Z",
  "published": "2026-07-28T21:30:18Z",
  "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"
    }
  ],
  "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 --url: no host/IP validation, follows redirects"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

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.

Loading…

Loading…

Loading…

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.


Loading…