# datamodel-code-generator vulnerable to arbitrary local file read via JSON-Schema `$ref` (`file://` and `../` traversal), bypassing `--no-allow-remote-refs`

datamodel-code-generator resolves JSON-Schema $ref file:// and ../ paths without containment, letting an attacker-controlled schema read arbitrary local files and leak secrets into generated code.

- Payout: Research
- Severity: high
- Program: datamodel-code-generator
- Found by: thegr1ffyn
- Disclosed: 2026-07-28
- CVE: CVE-2026-55389
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-8359-h9fx-j6v9
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-8359-h9fx-j6v9
- Tags: area:oss domain:oss impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:python technique:incomplete-fix technique:source-review vuln:info-disclosure vuln:misconfiguration vuln:path-traversal vuln:secrets-leak

**Target:** JSON-Schema $ref resolution in datamodel-code-generator CLI/library
**Root cause:** is_url() classifies file:// as a URL but the remote-ref gate explicitly exempts file://, and both _get_ref_body_from_url and _get_ref_body_from_remote read paths without an is_relative_to(base_path) containment check, unlike the HTTP-local-ref branch.
**Steps:**
1. Create probe.json with `{"type":"object","properties":{"x":{"$ref":"file:///etc/passwd"}}}`
2. Run `datamodel-codegen --input probe.json --input-file-type jsonschema --output o.py`
3. Observe `TypeError: Expected dict, got str` confirming /etc/passwd was opened and parsed
4. Swap $ref for missing/unreadable paths to distinguish FileNotFoundError vs PermissionError as a filesystem oracle
5. Point $ref at a schema-shaped file (const/default/enum/description) to have values emitted verbatim into generated Python
**How found:** Code review of reference resolution: noticed is_url() treats file:// as a URL, then saw the remote-ref gate explicitly skips file://, and that the two filesystem branches lack the is_relative_to(base_path) check present in the HTTP-local-ref branch.
**Exploitation:** Attacker submits a schema containing a file:// absolute URI or ../-escaped relative $ref to any service/CI that runs datamodel-code-generator on untrusted input and returns/logs/commits the generated code. The generator opens the target path, parses it, and either leaks existence/permission/absolute-path via distinct errors or emits schema-shaped values verbatim into output, exfiltrating secrets. Works even with --no-allow-remote-refs.
**Escalation:** Filesystem oracle (exists/missing/unreadable + absolute paths) combined with verbatim emission of const/default/enum/description values from schema-shaped files enables targeted secret exfiltration (service-account JSON, k8s manifests, default credentials).
**Insight:** When a security gate is implemented as a scheme allowlist/denylist, check every branch that handles a scheme the gate exempts; compare sibling code paths (HTTP-local-ref had containment, filesystem ones didn't) to spot missing checks. Also treat error-message differentials (TypeError vs FileNotFoundError vs PermissionError) as a filesystem oracle.

## Proof of concept (copied exactly from the original report)

**Manual repro reading /etc/passwd via file:// $ref**

```
printf '{"type":"object","properties":{"x":{"$ref":"file:///etc/passwd"}}}' > probe.json
datamodel-codegen --input probe.json --input-file-type jsonschema --output o.py
```

