{"id":"PYSEC-2026-3558","summary":"datamodel-code-generator vulnerable to arbitrary local file read via JSON-Schema `$ref` (`file://` and `../` traversal), bypassing `--no-allow-remote-refs`","details":"### Summary\n\n`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.\n\nThis is an unauthenticated path-traversal / information-disclosure issue (CWE-22 / CWE-200) plus a bypass of a documented security control.\n\n### Details\n\n`is_url()` classifies `file://` as a URL (`reference.py:1249`):\n\n```python\ndef is_url(ref: str) -\u003e bool:\n    return ref.startswith((\"https://\", \"http://\", \"file://\"))\n```\n\nThe remote-ref gate then explicitly exempts `file://`, so `--no-allow-remote-refs` never applies to it (`parser/jsonschema.py`, `_get_ref_body`):\n\n```python\nif is_url(resolved_ref):\n    if not resolved_ref.startswith(\"file://\") and self.http_local_ref_path is None:\n        if self.allow_remote_refs is False:\n            raise Error(...)        # \u003c-- skipped for file://\n        ...\n    return self._get_ref_body_from_url(resolved_ref)\nreturn self._get_ref_body_from_remote(resolved_ref)\n```\n\nBoth local-file branches read the target with **no containment check**. The `file://` branch reads any absolute path:\n\n```python\n# _get_ref_body_from_url\nif ref.startswith(\"file://\"):\n    path = url2pathname(urlparse(ref).path)     # absolute path, anywhere on disk\n    return self.remote_object_cache.get_or_put(\n        ref, default_factory=lambda _: load_data_from_path(Path(path), self.encoding))\n```\n\nand the plain relative branch lets `../` escape the base directory:\n\n```python\n# _get_ref_body_from_remote\nfull_path = self.base_path / resolved_ref       # no is_relative_to(base_path) check\nreturn self.remote_object_cache.get_or_put(\n    str(full_path), default_factory=lambda _: load_data_from_path(full_path, self.encoding))\n```\n\nFor 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`.)\n\nThe fetched file is read and parsed, yielding two impacts:\n\n1. **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: '\u003cabs path\u003e'` = missing; `PermissionError: '\u003cabs path\u003e'` = exists but unreadable. An attacker uses this to probe arbitrary paths and recover the operator's absolute filesystem layout.\n\n2. **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).\n\nScope 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.\n\n### PoC\n\nSelf-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). \nMinimal manual reproduction of the arbitrary read (any path works):\n\n```bash\nprintf '{\"type\":\"object\",\"properties\":{\"x\":{\"$ref\":\"file:///etc/passwd\"}}}' \u003e probe.json\ndatamodel-codegen --input probe.json --input-file-type jsonschema --output o.py\n# -\u003e \"TypeError: Expected dict, got str\"  == /etc/passwd was opened and fully parsed.\n# Swap the $ref for a missing/unreadable path to see the existence/permission oracle.\n```\n\n### Impact\n\nArbitrary 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.\n\n### Suggested remediation\n\n1. Do not exempt `file://` from the `allow_remote_refs` gate, treat it as an external scheme.\n2. 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`.\n\n### Maintainer status\n\nConfirmed 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\n\nFix 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`.\n\nRelease status: fixed in `0.62.0` for the documented `--no-allow-remote-refs` bypass, with a compatibility warning for the default behavior.\n\nValidation: `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.\n\nSubmitted by: Hamza Haroon (thegr1ffyn)","aliases":["CVE-2026-55389","GHSA-8359-h9fx-j6v9"],"modified":"2026-08-04T14:30:14.591556950Z","published":"2026-08-04T11:34:44.697205Z","references":[{"type":"WEB","url":"https://github.com/koxudaxi/datamodel-code-generator/security/advisories/GHSA-8359-h9fx-j6v9"},{"type":"WEB","url":"https://github.com/koxudaxi/datamodel-code-generator/commit/2ff4a72b4550a2b2069754c5b075b1655067e5fb"},{"type":"PACKAGE","url":"https://github.com/koxudaxi/datamodel-code-generator"},{"type":"WEB","url":"https://github.com/koxudaxi/datamodel-code-generator/releases/tag/0.62.0"},{"type":"PACKAGE","url":"https://pypi.org/project/datamodel-code-generator"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-8359-h9fx-j6v9"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55389"}],"affected":[{"package":{"name":"datamodel-code-generator","ecosystem":"PyPI","purl":"pkg:pypi/datamodel-code-generator"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.62.0"}]}],"versions":["0.0.1","0.0.2","0.0.3","0.0.4","0.0.5","0.0.6","0.1.0","0.10.0","0.10.1","0.10.2","0.10.3","0.11.0","0.11.1","0.11.10","0.11.11","0.11.12","0.11.13","0.11.14","0.11.15","0.11.16","0.11.17","0.11.18","0.11.19","0.11.2","0.11.20","0.11.3","0.11.4","0.11.5","0.11.6","0.11.7","0.11.8","0.11.9","0.12.0","0.12.1","0.12.2","0.12.3","0.13.0","0.13.1","0.13.2","0.13.3","0.13.4","0.13.5","0.14.0","0.14.1","0.15.0","0.16.0","0.16.1","0.17.0","0.17.1","0.17.2","0.18.0","0.18.1","0.19.0","0.2.0","0.2.1","0.2.10","0.2.11","0.2.12","0.2.13","0.2.14","0.2.15","0.2.16","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.2.7","0.2.8","0.2.9","0.20.0","0.21.0","0.21.1","0.21.2","0.21.3","0.21.4","0.21.5","0.22.0","0.22.1","0.23.0","0.24.0","0.24.1","0.24.2","0.25.0","0.25.1","0.25.2","0.25.3","0.25.4","0.25.5","0.25.6","0.25.7","0.25.8","0.25.9","0.26.0","0.26.1","0.26.2","0.26.3","0.26.4","0.26.5","0.27.0","0.27.1","0.27.2","0.27.3","0.28.0","0.28.1","0.28.2","0.28.3","0.28.4","0.28.5","0.29.0","0.3.0","0.3.1","0.3.2","0.3.3","0.30.0","0.30.1","0.30.2","0.31.0","0.31.1","0.31.2","0.32.0","0.33.0","0.34.0","0.35.0","0.36.0","0.37.0","0.38.0","0.39.0","0.4.0","0.4.1","0.4.10","0.4.11","0.4.2","0.4.3","0.4.4","0.4.5","0.4.6","0.4.7","0.4.8","0.4.9","0.40.0","0.41.0","0.42.0","0.42.1","0.42.2","0.43.0","0.43.1","0.44.0","0.45.0","0.46.0","0.47.0","0.48.0","0.49.0","0.5.0","0.5.1","0.5.10","0.5.11","0.5.12","0.5.13","0.5.14","0.5.15","0.5.16","0.5.17","0.5.18","0.5.19","0.5.2","0.5.20","0.5.21","0.5.22","0.5.23","0.5.24","0.5.25","0.5.26","0.5.27","0.5.28","0.5.29","0.5.3","0.5.30","0.5.31","0.5.32","0.5.33","0.5.34","0.5.35","0.5.36","0.5.37","0.5.38","0.5.39","0.5.4","0.5.5","0.5.6","0.5.7","0.5.8","0.5.9","0.50.0","0.51.0","0.52.0","0.52.1","0.52.2","0.53.0","0.54.0","0.54.1","0.55.0","0.56.0","0.56.1","0.57.0","0.58.0","0.59.0","0.59.1","0.6.0","0.6.1","0.6.10","0.6.11","0.6.12","0.6.13","0.6.14","0.6.15","0.6.16","0.6.17","0.6.18","0.6.19","0.6.2","0.6.20","0.6.21","0.6.22","0.6.23","0.6.24","0.6.25","0.6.26","0.6.3","0.6.4","0.6.5","0.6.6","0.6.7","0.6.8","0.6.9","0.60.0","0.60.1","0.60.2","0.61.0","0.7.0","0.7.1","0.7.2","0.7.3","0.8.0","0.8.1","0.8.2","0.8.3","0.9.0","0.9.1","0.9.2","0.9.3","0.9.4"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/datamodel-code-generator/PYSEC-2026-3558.yaml"}}],"schema_version":"1.8.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"}]}