{"id":"PYSEC-2026-3555","summary":"`datamodel-code-generator` vulnerable to code injection in via attacker-controlled `default_factory` schema field","details":"### Summary\n\n`datamodel-code-generator` is vulnerable to code injection when generating Python models from an attacker-controlled JSON Schema, OpenAPI, YAML, JSON, Avro, Protobuf, or XSD schema. When a property carries a `\"default_factory\"` key, its value is interpolated verbatim — as a raw Python expression — into the generated `Field(default_factory=...)` / `field(default_factory=...)` call. Because this assignment is evaluated at class-definition time (i.e. on `import` of the generated module), an attacker who controls the schema controls a Python expression that runs in the consumer's process. No special CLI flags are required.\n\n### Details\n\nThe vulnerable chain spans the JSON-Schema-shaped parser and three sink locations (Pydantic v2, dataclass, msgspec):\n\n**Source — schema → `extras`**:\n\n- `src/datamodel_code_generator/parser/jsonschema.py:600-614` — `DEFAULT_FIELD_KEYS` includes the literal string `\"default_factory\"`.\n- `src/datamodel_code_generator/parser/jsonschema.py:457-459` — `JsonSchemaObject.__init__` stores any non-standard key (including `default_factory`) in `self.extras`.\n- `src/datamodel_code_generator/parser/jsonschema.py:797-812` — `get_field_extras` preserves `default_factory` through to the field model.\n\n**Sinks — `extras` → generated Python expression**:\n\n1. `src/datamodel_code_generator/model/pydantic_base.py:222-249`:\n\n   ```python\n   default_factory = data.pop(\"default_factory\", None)\n   ...\n   if default_factory is not None:\n       field_arguments = [f\"default_factory={default_factory}\", *field_arguments]\n   ```\n\n   The `default_factory` value is interpolated raw (no `repr()`, no validation).\n\n2. `src/datamodel_code_generator/model/dataclass.py:211`:\n\n   ```python\n   f\"{k}={v if k == 'default_factory' else repr(v)}\"\n   ```\n\n   Explicit special-case to skip `repr()` for `default_factory`.\n\n3. `src/datamodel_code_generator/model/msgspec.py:361` — same pattern as dataclass.\n\nBecause `default_factory` is in `DEFAULT_FIELD_KEYS`, no special CLI flag is needed to reach the sink. Any input format that uses the JSON-Schema-shaped parser (`jsonschema`, `openapi`, `yaml`, `json`, `dict`, `csv`) — and any input format that converts to it (`avro`, `protobuf`, `xmlschema`) — is in scope.\n\n### Confirmed PoC matrix\n\n| Input file type | Output model type | Result |\n|---|---|---|\n| `jsonschema` | `pydantic_v2.BaseModel` | RCE on import |\n| `jsonschema` | `dataclasses.dataclass` | RCE on import |\n| `jsonschema` | `msgspec.Struct` | RCE on import |\n| `jsonschema` | `typing.TypedDict` | safe (TypedDict doesn't render `field()`; `default_factory` silently dropped) |\n| `openapi`    | `pydantic_v2.BaseModel` | RCE on import |\n\nOther JSON-Schema-shaped inputs (`yaml`, `json`, `dict`, `csv`, `avro`, `protobuf`, `xmlschema`) follow the same code path and are expected to reproduce.\n\n### PoC\nSelf contained Proof of Concept is available at my secret gist: https://gist.github.com/thegr1ffyn/9648b0fe4fcf7d569ac8e61dd11eebaf\n\n### Impact\n\n- **Who's affected**: any developer or CI pipeline that runs `datamodel-codegen` against a schema they didn't author themselves — third-party API specs, schemas pulled from a registry, vendored upstream `.json` / `.yaml` / `.avsc` / `.proto` / `.xsd` files, schemas fetched from a remote URL or introspection endpoint — *and* who imports the generated `.py`.\n- **What it gains**: arbitrary Python code execution in the importer's process at `import` time. The PoC copies `/etc/passwd` to a tmp file to demonstrate arbitrary read; the same primitive supports any operation the importing process can perform (filesystem write, environment exfiltration, secondary network calls, RCE on CI runners).\n- **What it does NOT need**: no special CLI flags, no custom templates, no `--extra-template-data`, no `--use-schema-description`. Default invocation against a malicious schema is sufficient.\n- **What does block it**: choosing `--output-model-type typing.TypedDict` (which doesn't render `field()` / `Field()` calls). All other supported output model types are vulnerable.\n\n### Resolution\n\nThe fix validates schema-provided `default_factory` values while extracting JSON Schema field extras. Only the supported factory names `dict`, `list`, and `set` are accepted; any other value now raises a generator error before code generation. Generator-created default factories for supported mutable defaults and optional nested models continue to use the existing code paths.\n\n### Remediation\n\nUpgrade to `datamodel-code-generator` `0.60.2` or later.\n\nThis issue affects `datamodel-code-generator` versions `\u003e= 0.17.0, \u003c= 0.60.1` and is fixed in `0.60.2`.\n\nSubmitted by: Hamza Haroon (thegr1ffyn)","aliases":["CVE-2026-54653","GHSA-386q-5hp3-95m9"],"modified":"2026-08-04T14:30:26.217034221Z","published":"2026-08-04T11:34:44.628538Z","references":[{"type":"WEB","url":"https://github.com/koxudaxi/datamodel-code-generator/security/advisories/GHSA-386q-5hp3-95m9"},{"type":"WEB","url":"https://github.com/koxudaxi/datamodel-code-generator/commit/17fc235e234cbcfaaadef8c74cb72c9687db0d1d"},{"type":"PACKAGE","url":"https://github.com/koxudaxi/datamodel-code-generator"},{"type":"WEB","url":"https://github.com/koxudaxi/datamodel-code-generator/releases/tag/0.60.2"},{"type":"PACKAGE","url":"https://pypi.org/project/datamodel-code-generator"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-386q-5hp3-95m9"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54653"}],"affected":[{"package":{"name":"datamodel-code-generator","ecosystem":"PyPI","purl":"pkg:pypi/datamodel-code-generator"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.17.0"},{"fixed":"0.60.2"}]}],"versions":["0.17.0","0.17.1","0.17.2","0.18.0","0.18.1","0.19.0","0.20.0","0.21.0","0.21.1","0.21.2","0.21.3","0.21.4","0.21.5","0.22.0","0.22.1","0.23.0","0.24.0","0.24.1","0.24.2","0.25.0","0.25.1","0.25.2","0.25.3","0.25.4","0.25.5","0.25.6","0.25.7","0.25.8","0.25.9","0.26.0","0.26.1","0.26.2","0.26.3","0.26.4","0.26.5","0.27.0","0.27.1","0.27.2","0.27.3","0.28.0","0.28.1","0.28.2","0.28.3","0.28.4","0.28.5","0.29.0","0.30.0","0.30.1","0.30.2","0.31.0","0.31.1","0.31.2","0.32.0","0.33.0","0.34.0","0.35.0","0.36.0","0.37.0","0.38.0","0.39.0","0.40.0","0.41.0","0.42.0","0.42.1","0.42.2","0.43.0","0.43.1","0.44.0","0.45.0","0.46.0","0.47.0","0.48.0","0.49.0","0.50.0","0.51.0","0.52.0","0.52.1","0.52.2","0.53.0","0.54.0","0.54.1","0.55.0","0.56.0","0.56.1","0.57.0","0.58.0","0.59.0","0.59.1","0.60.0","0.60.1"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/datamodel-code-generator/PYSEC-2026-3555.yaml"}}],"schema_version":"1.8.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"}]}