
Code injection (RCE) in datamodel-code-generator via unvalidated customBasePath (CVE-2026-63720)
Severity: High, CVSS 3.1 7.5 / CVSS 4.0 7.5 (assigned by VulnCheck, the CNA)
Environmental ceiling (network-service deployment): up to 9.8
Vector (v4.0): CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
Vector (v3.1): CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
Affected: datamodel-code-generator < 0.70.0
Fixed in: 0.70.0
CWE: CWE-94 (Improper Control of Generation of Code, 'Code Injection')
Reported by: Rahul Karne
CNA: VulnCheck
Published: July 26, 2026
datamodel-code-generator validated every schema-controlled import string that could carry a code-injection payload, except one.
The tool converts an input schema (JSON Schema, OpenAPI, YAML) into Python model
source. Several schema fields are rendered directly into that generated code, so
the project validates them as dotted Python identifiers before use, specifically
to prevent injection. The schema extension field customBasePath is the one
sibling that skips this check. Its value flows unsanitized into a
from ... import ... statement in the generated output. An attacker who controls
the input schema can embed arbitrary Python using newlines and a dot-free
expression, and it executes the moment the generated module is imported, which
is the ordinary next step after generating models.
This is an incomplete fix of CVE-2026-55415 (GHSA-5578-w22f-pfx9), which
hardened the sibling fields customTypePath and x-python-import against this
exact class. That fix did not cover customBasePath, which reaches the identical
sink unvalidated and remained exploitable through 0.68.1 and main until 0.70.0.
Arbitrary Python code execution in the process that imports or runs the generated models: the developer's machine, a CI runner, or any service that generates and then loads models. Confidentiality, integrity, and availability of the host are fully compromised, bounded only by the privileges of that process.
The severity depends entirely on where codegen runs on untrusted input:
Who is affected: Any use of datamodel-code-generator < 0.70.0 that (1)
generates models from a schema whose customBasePath value is attacker
influenced, and (2) imports or executes the generated module. The default
codegen-then-import workflow satisfies (2) inherently.
Who is not affected:
0.70.0 or later, where customBasePath is validated.| Metric | Value | Source |
|---|---|---|
| Downloads, all-time | 194 Million | pepy.tech/projects/datamodel-code-generator |
| Downloads, last 30 days | 16.3 Million | pepy.tech |
| Typical deployment | Developer machines, CI/CD pipelines, and SDK-generation platforms that codegen from OpenAPI / JSON Schema | inherent to the tool's function |
The value of the customBasePath schema field is carried into generated code
with no identifier constraint. Three points in the codebase matter (paths
relative to src/datamodel_code_generator/):
parser/jsonschema.py defines the field
custom_base_path with alias="customBasePath" (~line 644), consumed via
_resolve_base_class(...) at several call sites.parser/base.py, _resolve_base_class (~line 1665),
returns the value after only a local normalize() (dedup/strip). No identifier
validation is applied.imports.py, Import.from_full_path() (~line 35), emits the value
verbatim as a from ... import ... line. The value is also used as the class
base in model/base.py set_base_class (~line 1324) and rendered raw by the
model template (class {{ class_name }}({{ base_class }}):).Because the value is written into Python source with no constraint, embedded newlines and a dot-free expression survive into the output as their own individually parseable lines, and the middle line executes on import.
The payload is dot-free by necessity. Import.from_full_path splits the value on
., so a normal os.system(...) call would be broken apart. Using
getattr(__import__('os'),'system')(...) avoids any . while still resolving the
same call, and the surrounding newlines keep the emitted from ... import ...
lines syntactically valid so the injected middle line runs cleanly.
This is not a project that neglected injection. The maintainer hardened this
exact class repeatedly across multiple advisories (GHSA-5578, m34r, 8m8r, wjv6),
each time routing a schema-controlled import or type string through
_validate_dotted_python_identifier_path before it reaches code generation. The
sibling fields customTypePath (validated at parser/jsonschema.py ~lines 4956,
5202) and x-python-import (~line 2096) both go through that validator.
customBasePath is the one sibling with no such call. It reaches the same
Import.from_full_path sink by a different path (_resolve_base_class) that was
never wired into the validation the other fields received. The defect survived
precisely because the surrounding defense looked complete: a reviewer scanning
for unvalidated import strings sees validators on the fields they check first,
and this one routes through a helper that looks like base-class resolution rather
than import handling. It is a gap in a systematic fix, not an absent one, which
is why it persisted into the latest release.
An attacker needs:
< 0.70.0.customBasePath value in a schema the target will process,
in practice by supplying or influencing the input schema (a third-party
OpenAPI/JSON Schema document, or a schema submitted to a service).No authentication or elevated privileges are required of the attacker (PR:N).
The base score reflects that the victim performs the ordinary generate-and-import
action (UI:R in v3.1 / UI:A in v4.0); the network-service deployment removes
even that, which is where the environmental 9.8 comes from.
The following was run against the real, unmodified package. Reproduction:
pip install "datamodel-code-generator==0.68.1"
datamodel-codegen --input attack.json --input-file-type jsonschema --output generated_models.py
python -c "import generated_models"
Attacker input (attack.json):
{
"type": "object",
"title": "User",
"customBasePath": "builtins import object\ngetattr(__import__('os'),'system')('whoami > RCE_PROOF.txt')\nfrom builtins.object",
"properties": { "name": { "type": "string" } }
}
Generated generated_models.py on the vulnerable version (0.68.1):
from __future__ import annotations
from builtins import object
getattr(__import__('os'), 'system')(
'whoami > RCE_PROOF.txt'
)
from builtins import object
class User(object):
name: str | None = None
The attacker's call is emitted verbatim into the generated source.
On import: the command executes. In the verified run, the injected marker
printed to stdout and RCE_PROOF.txt was created containing the current user
(root), confirming arbitrary command execution through the ordinary
generate-and-import workflow.
On the patched version (0.70.0): the same schema is rejected before any code is generated:
Error at schema path 'attack.json': Error: customBasePath must be a dotted
Python identifier path: "builtins import object\ngetattr(__import__('os'),
'system')('whoami > RCE_PROOF.txt')\nfrom builtins.object"
No file is produced. The rejection message names the fix directly: the value is now required to be a dotted Python identifier path.
Network-service variant. An unauthenticated loopback HTTP service that
accepts a POSTed schema, generates models, and imports them was demonstrated
executing the attacker's command on the server from a single unauthenticated
curl, with no user interaction. This is the deployment shape behind the
environmental 9.8. The service and attack files are included in the PoC
repository.
Upgrade to datamodel-code-generator 0.70.0 or later:
pip install --upgrade "datamodel-code-generator>=0.70.0"
0.70.0 routes customBasePath through the same dotted-identifier validation
already applied to customTypePath and x-python-import, so a value that is not
a valid identifier path is rejected before code generation.
If you cannot upgrade immediately: do not generate models from schemas you do not fully control, and do not import or execute modules generated from untrusted schemas. There is no configuration flag that adds the missing validation in affected versions; upgrading is the reliable fix.
Note for anyone reusing the generator's internals. The defect was a missing
validation call on one code path into Import.from_full_path, not a flaw in the
sink itself. Any downstream project that renders schema-controlled strings into
generated code should validate every such field as a dotted identifier, not only
the ones that pass through the obvious import-handling path.
VulnCheck (the CNA) assigned 7.5 (High), matching the base vector the
maintainer used for the parent advisory CVE-2026-55415, because this is the same
injection class, the same Import.from_full_path sink, and the same impact.
AV:N: schemas are commonly obtained over the network (fetched or
third-party OpenAPI / JSON Schema documents).AC:H: exploitation depends on the victim generating models from the
malicious schema and then importing or running the generated code.PR:N / UI:R (v3.1): no attacker privileges; the victim performs the
ordinary codegen-and-import workflow.C:H / I:H / A:H: full arbitrary code execution on the host.The environmental ceiling is 9.8
(AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) and applies specifically to the
network-service deployment, where the generator is exposed to untrusted schemas
and no victim interaction is required. That figure is an environmental note for
that deployment, not the assigned base score. Stating both, and being explicit
about which is which, is the honest framing: the base is 7.5, and it reaches 9.8
only in the exposed-service case.
| Date | Event |
|---|---|
| July 13, 2026 | Vulnerability identified |
| July 14, 2026 | Reported (coordinated disclosure) |
| July 21, 2026 | Fix committed (545a96c5) |
| July 24, 2026 | Patched version 0.70.0 released |
| July 26, 2026 | CVE-2026-63720 published by VulnCheck |
Discovered and reported by Rahul Karne, security researcher and IEEE Senior Member. His research focuses on injection and input-handling flaws in high-dependency open-source packages, including CVE-2026-65321 (SQL injection in PyAthena) and the parent-class hardening around this finding.
Contact: [email protected] · GitHub: rahulreddykarne
545a96c5: https://github.com/koxudaxi/datamodel-code-generator/commit/545a96c5Media inquiries: [email protected]. Full PoC (attacker schema, network-service demo) and additional technical detail available on request.