PT-2026-65648 · Pypi · Datamodel-Code-Generator

Published

2026-07-28

·

Updated

2026-07-28

CVSS v3.1

7.5

High

VectorAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Summary

datamodel-code-generator resolves JSON-Schema $ref targets that point at the local filesystem without restricting them to the input/base directory and without honoring the remote-reference security control. In the default configuration, an attacker who controls an input schema (a "paste your OpenAPI/JSON-Schema" service, a CI job that generates models from a submitted spec, or any multi-tenant codegen platform) can read any file the process user can read and map the host filesystem. This works via either a file:// absolute URI or a ../-escaped relative reference, and it succeeds even when --no-allow-remote-refs is set.
This is an unauthenticated path-traversal / information-disclosure issue (CWE-22 / CWE-200) plus a bypass of a documented security control.

Details

is url() classifies file:// as a URL (reference.py:1249):
python
def is url(ref: str) -> bool:
  return ref.startswith(("https://", "http://", "file://"))
The remote-ref gate then explicitly exempts file://, so --no-allow-remote-refs never applies to it (parser/jsonschema.py, get ref body):
python
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(...)    # <-- skipped for file://
    ...
  return self. get ref body from url(resolved ref)
return self. get ref body from remote(resolved ref)
Both local-file branches read the target with no containment check. The file:// branch reads any absolute path:
python
# get ref body from url
if ref.startswith("file://"):
  path = url2pathname(urlparse(ref).path)   # absolute path, anywhere on disk
  return self.remote object cache.get or put(
    ref, default factory=lambda : load data from path(Path(path), self.encoding))
and the plain relative branch lets ../ escape the base directory:
python
# get ref body from remote
full path = self.base path / resolved ref    # no is relative to(base path) check
return self.remote object cache.get or put(
  str(full path), default factory=lambda : load data from path(full path, self.encoding))
For contrast, the HTTP-local-ref branch ( get ref body from local http path) does enforce is relative to(base path); these two filesystem branches do not. (This is distinct from the previously reported HTTP $ref/--url SSRF hardened in http.py these code paths never enter http.py.)
The fetched file is read and parsed, yielding two impacts:
  1. Arbitrary file read + filesystem oracle (any file). The process opens any readable path, and the three distinguishable outcomes leak filesystem structure: Expected dict, got str/list = the file exists and was read into the process; FileNotFoundError: '<abs path>' = missing; PermissionError: '<abs path>' = exists but unreadable. An attacker uses this to probe arbitrary paths and recover the operator's absolute filesystem layout.
  2. Verbatim secret disclosure into the generated code. When the referenced node is JSON-Schema-shaped, values in const/default/enum/description positions are emitted verbatim into the generated Python that is returned to the attacker — i.e. exactly the internal/private schema and config files a codegen host stores (specs with example/default tokens, default credentials, internal hostnames, service-account JSON, k8s secret manifests).
Scope note (to avoid overstating): the raw bytes of unstructured files such as /etc/passwd or a PEM id rsa are read into the process but are not echoed verbatim into the output, because they parse to a single scalar and are rejected as "not a schema". For those files the impact is the read itself plus the existence/permission/absolute-path oracle; verbatim content disclosure applies to schema-shaped nodes.

PoC

Self-contained reproducer: https://gist.github.com/thegr1ffyn/2a87e81f985883acc30d0118c52da4d3 / (poc.py creates everything in a temp dir, runs the generator, asserts read + leak + gate bypass, and cleans up). Minimal manual reproduction of the arbitrary read (any path works):
bash
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 fully parsed.
# Swap the $ref for a missing/unreadable path to see the existence/permission oracle.

Impact

Arbitrary local file read / path traversal (CWE-22) → information disclosure (CWE-200), plus bypass of --no-allow-remote-refs. Any application, CI pipeline, or multi-tenant service that runs datamodel-code-generator on an untrusted schema and exposes (returns, logs, commits, renders) the generated code is affected. The attacker can: open and read any file the process user can read (demonstrated: /etc/passwd, a PEM id rsa, paths under /root/); map the host filesystem and recover absolute paths; and exfiltrate secret values verbatim from any schema-shaped node. Attacker controls only the input schema; no authentication or special privileges required.

Suggested remediation

  1. Do not exempt file:// from the allow remote refs gate, treat it as an external scheme.
  2. In both get ref body from url (the file:// case) and get ref body from remote, reject targets that are not resolved.is relative to(self.base path), mirroring the existing check in get ref body from local http path.

Maintainer status

Confirmed by maintainer review and regression tests. The private fix PR was merged and released in 0.62.0: https://github.com/koxudaxi/datamodel-code-generator-ghsa-8359-h9fx-j6v9/pull/1
Fix summary: apply the remote-ref gate to file:// refs and local JSON Schema refs outside the input base path. In 0.62.0, --no-allow-remote-refs blocks these references; the default compatibility mode emits a FutureWarning and users who intentionally rely on trusted external local refs can pass --allow-remote-refs.
Release status: fixed in 0.62.0 for the documented --no-allow-remote-refs bypass, with a compatibility warning for the default behavior.
Validation: uv run --group test --extra http pytest tests/main/jsonschema/test main jsonschema.py passed locally; uv run --group fix ruff check src/datamodel code generator/parser/jsonschema.py tests/main/jsonschema/test main jsonschema.py passed.
Submitted by: Hamza Haroon (thegr1ffyn)

Fix

Path traversal

Information Disclosure

Found an issue in the description? Have something to add? Feel free to write us 👾

Weakness Enumeration

Related Identifiers

GHSA-8359-H9FX-J6V9

Affected Products

Datamodel-Code-Generator