datamodel-code-generator vulnerable to code injection via `x-python-import` / `customTypePath` in generated import statements
Published Jul 28, 2026 · Updated Nov 3, 2026
Original summary
Normalized public record · Updated Nov 3, 2026
datamodel-code-generator vulnerable to code injection via `x-python-import` / `customTypePath` in generated import statements
Publisher and source text is retained separately from QFinch interpretation.
QFinch assessment
A malicious input schema (OpenAPI / JSON Schema) can execute arbitrary Python code on the machine that imports the generated model. The x-python-import and customTypePath schema extensions flow, unsanitized, into the import statements datamodel-code-generator emits. A newline embedded in the extension value breaks out of the from … import … line and injects an attacker-controlled statement at module scope, which runs at import time. This is an unauthenticated, schema-content–driven remote code execution against any consumer of the generated code (e.g. arbitrary file read,the PoC exfiltrates /etc/passwd). It survives the v0.61.0 security release that fixed the related x-python-type, defaultfactory, GraphQL-union-description, and validators sinks those fixes did not cover this sibling path. Details The sink is Import.fromfullpath and Imports.createline: - src/datamodelcodegenerator/imports.py:35 — fromfullpath() only does classpath.split(".") and preserves every other character, including newlines: - src/datamodelcodegenerator/imports.py:64 — createline() renders the result verbatim: Two schema-controlled, default-config entry points reach this sink: 1. x-python-import — src/datamodelcodegenerator/parser/jsonschema.py:1851-1858 (getrefdatatype): Mechanism. With name = "getcwd\nprint(...)", fullpath = "os.getcwd\nprint(...)". fromfullpath splits only on ., so (provided the injected statement contains no .) it yields from="os" and import="getcwd\nprint(...)". createline then emits: The dot-split is the only constraint on the payload; it is trivially satisfied with attribute-free builtins (e.g. print(open('/etc/passwd'), file=open('/tmp/loot','w'), sep='', end='') reads and exfiltrates a file using no .). None of the six v0.61.0 fix commits (aec47bc4, b73abb5c, 17fc235e, 2c93c9b7, a43d0290, 5fdba4a0) touched x-python-import, customTypePath, or imports.py; imports.py was last modified ~5 months before the release. This is therefore an incomplete fix: the maintainer hardened sibling schema-controlled type/extension sinks but missed these two paths into the same import-generation code. PoC Self contained POC available here: https://gist.github.com/thegr1ffyn/c3abb41bb89c164daa0d5f2c60b5328b Default invocation, no special flags, on the patched release (commit 227ffe85ee2dcfc79336fbb14ad64c02a166b65a, v0.61.0). payloada.json: Generate and import: Generated model.py (verbatim, the breakout sits at module scope): Importing model reads /etc/passwd and writes an exact copy to /tmp/dmcgxpiloot: Confirmed under both the default output (pydantic v1) and --output-model-type pydanticv2.BaseModel. The customTypePath variant reproduces identically: A benign control (x-python-import: {"module":"decimal","name":"Decimal"}) produces clean from decimal import Decimal and no execution. A complete self-contained validation harness, run.sh plus control.json, payloada.json, payloadb.json, is included alongside this advisory (CONTROL clean + three payloads firing + verdict + cleanup). Suggested fix. Validate every dotted segment of module, name, and customTypePath as a Python identifier before building the import (reuse validators.validatedottedpythonidentifierpath), and/or reject non-identifier paths centrally inside Import.fromfullpath. Impact Arbitrary code execution at model-import time, driven by attacker-controlled schema content under the default configuration. Anyone who runs datamodel-code-generator on an untrusted or third-party schema, multi-tenant code-generation services, CI pipelines that ingest external specs, or a developer generating models from a public/vendor OpenAPI/JSON-Schema document, and then imports (or whose tooling imports) the generated module, executes the attacker's code with the importing process's privileges. The PoC demonstrates arbitrary local file read (/etc/passwd); the same primitive yields full RCE.
Potential impact
Arbitrary code execution at model-import time, driven by attacker-controlled schema content under the default configuration. Anyone who runs datamodel-code-generator on an untrusted or third-party schema, multi-tenant code-generation services, CI pipelines that ingest external specs, or a developer generating models from a public/vendor OpenAPI/JSON-Schema document, and then imports (or whose tooling imports) the generated module, executes the attacker's code with the importing process's privileges. The PoC demonstrates arbitrary local file read (/etc/passwd); the same primitive yields full RCE.
Technical context
The sink is Import.fromfullpath and Imports.createline: - src/datamodelcodegenerator/imports.py:35 — fromfullpath() only does classpath.split(".") and preserves every other character, including newlines: - src/datamodelcodegenerator/imports.py:64 — createline() renders the result verbatim: Two schema-controlled, default-config entry points reach this sink: 1. x-python-import — src/datamodelcodegenerator/parser/jsonschema.py:1851-1858 (getrefdatatype): Mechanism. With name = "getcwd\nprint(...)", fullpath = "os.getcwd\nprint(...)". fromfullpath splits only on ., so (provided the injected statement contains no .) it yields from="os" and import="getcwd\nprint(...)". createline then emits: The dot-split is the only constraint on the payload; it is trivially satisfied with attribute-free builtins (e.g. print(open('/etc/passwd'), file=open('/tmp/loot','w'), sep='', end='') reads and exfiltrates a file using no .). None of the six v0.61.0 fix commits (aec47bc4, b73abb5c, 17fc235e, 2c93c9b7, a43d0290, 5fdba4a0) touched x-python-import, customTypePath, or imports.py; imports.py was last modified ~5 months before the release. This is therefore an incomplete fix: the maintainer hardened sibling schema-controlled type/extension sinks but missed these two paths into the same import-generation code. PoC Self contained POC available here: https://gist.github.com/thegr1ffyn/c3abb41bb89c164daa0d5f2c60b5328b Default invocation, no special flags, on the patched release (commit 227ffe85ee2dcfc79336fbb14ad64c02a166b65a, v0.61.0). payloada.json: Generate and import: Generated model.py (verbatim, the breakout sits at module scope): Importing model reads /etc/passwd and writes an exact copy to /tmp/dmcgxpiloot: Confirmed under both the default output (pydantic v1) and --output-model-type pydanticv2.BaseModel. The customTypePath variant reproduces identically: A benign control (x-python-import: {"module":"decimal","name":"Decimal"}) produces clean from decimal import Decimal and no execution. A complete self-contained validation harness, run.sh plus control.json, payloada.json, payloadb.json, is included alongside this advisory (CONTROL clean + three payloads firing + verdict + cleanup). Suggested fix. Validate every dotted segment of module, name, and customTypePath as a Python identifier before building the import (reuse validators.validatedottedpythonidentifierpath), and/or reject non-identifier paths centrally inside Import.fromfullpath. Impact Arbitrary code execution at model-import time, driven by attacker-controlled schema content under the default configuration. Anyone who runs datamodel-code-generator on an untrusted or third-party schema, multi-tenant code-generation services, CI pipelines that ingest external specs, or a developer generating models from a public/vendor OpenAPI/JSON-Schema document, and then imports (or whose tooling imports) the generated module, executes the attacker's code with the importing process's privileges. The PoC demonstrates arbitrary local file read (/etc/passwd); the same primitive yields full RCE.
Evidence: public source recordsAffected products
| Affected package, product or range | Fix status |
|---|---|
| Consult the referenced vendor advisory for affected versions. | Consult source advisory |
Matching a version is only one part of assessing exposure. QFinch has not assessed your environment on this public page.
Remediation
Apply the vendor-supported fixed release or mitigation, then verify the affected component is no longer reachable.
- Confirm the affected product and reachable code path exist in your environment.
- Apply the vendor-supported patch or mitigation from the primary advisory.
- Rescan the exact deployed revision and retain verification evidence.
Evidence coverage
- SSVC priority
- MEDIUM
- Confidence
- Not available
- Aliases
- None reported
Scores appear only when corresponding evidence exists. Missing data is never interpreted as zero risk.
Sources and record history
Record history
Published Jul 28, 2026 · Last refreshed Nov 3, 2026