PYSEC-2026-3566
Vulnerability from pysec - Published: 2026-08-04 11:34 - Updated: 2026-08-04 13:36Summary
datamodel-code-generator is vulnerable to code injection when a developer passes an --extra-template-data file whose comment value contains a literal \r (carriage return). The comment variable is rendered into a Python # comment in six built-in templates with no line-terminator escaping. Python's tokenizer treats a bare CR as a physical-line terminator (see Python language reference — Physical lines), so the comment ends at the \r and the text after it is parsed as Python, including, when the CR is followed by suitable indentation, as a statement within the class body that follows on the next template line.
Details
The vulnerable templates each contain # {{ comment }} with no escaping:
src/datamodel_code_generator/model/template/TypeAliasAnnotation.jinja2:12and:19src/datamodel_code_generator/model/template/TypeAliasType.jinja2:12and:19src/datamodel_code_generator/model/template/TypeStatement.jinja2:12and:19src/datamodel_code_generator/model/template/pydantic_v2/BaseModel.jinja2:4src/datamodel_code_generator/model/template/pydantic_v2/RootModel.jinja2:19src/datamodel_code_generator/model/template/pydantic_v2/RootModelTypeAlias.jinja2:13
The pydantic_v2/BaseModel.jinja2:4 site is representative:
class {{ class_name }}({{ base_class }}):{% if comment is defined %} # {{ comment }}{% endif %}
When the developer-supplied extras file populates comment for a model, the value reaches the template via DataModel.extra_template_data (set in src/datamodel_code_generator/model/base.py:736-742) and Jinja2 interpolates it raw. None of the templates use comment_safe, escape_docstring, or any other line-terminator filter.
PoC
Complete self contained POC is available at my secret gist: https://gist.github.com/thegr1ffyn/8ad6b8cb3cc2be9d3a0144aeb6896a3f
Impact
- Who's affected: any developer or CI pipeline that runs
datamodel-codegen --extra-template-data <file>where the extras file is influenced by attacker-controlled input. Realistic scenarios include: - Extras file generated from a third-party schema-annotation system.
- Extras file vendored from an upstream repository.
- Extras file produced by a script that merges multiple
commentsources. - Build pipelines that template the extras file from environment variables, ticket descriptions, or commit metadata.
- What it gains: arbitrary Python code execution in the importer's process at
importtime. - What it does NOT need: the schema itself can be entirely benign; only the extras file needs to contain the malicious
comment. - What does block it: not passing
--extra-template-data, or rejecting extras files whosecommentvalues contain\r,\x0b, or\x0cbefore invocation.
Resolution
The fix normalizes comment values from built-in --extra-template-data before template rendering. Inline comments now convert CRLF, bare CR, vertical tab, and form feed into LF and prefix continuation lines with #, so attacker-controlled text stays inside the generated Python comment block.
Remediation
Upgrade to datamodel-code-generator 0.60.2 or later.
This issue affects datamodel-code-generator versions >= 0.14.1, <= 0.60.1 and is fixed in 0.60.2.
Submitted by: Hamza Haroon (thegr1ffyn)
| Name | purl | datamodel-code-generator | pkg:pypi/datamodel-code-generator |
|---|
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "datamodel-code-generator",
"purl": "pkg:pypi/datamodel-code-generator"
},
"ranges": [
{
"events": [
{
"introduced": "0.14.1"
},
{
"fixed": "0.60.2"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"0.14.1",
"0.15.0",
"0.16.0",
"0.16.1",
"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"
]
}
],
"aliases": [
"CVE-2026-54654",
"GHSA-wjv6-jcfj-mf9r"
],
"details": "### Summary\n\n`datamodel-code-generator` is vulnerable to code injection when a developer passes an `--extra-template-data` file whose `comment` value contains a literal `\\r` (carriage return). The `comment` variable is rendered into a Python `#` comment in six built-in templates with **no** line-terminator escaping. Python\u0027s tokenizer treats a bare CR as a physical-line terminator (see [Python language reference \u2014 Physical lines](https://docs.python.org/3/reference/lexical_analysis.html#physical-lines)), so the comment ends at the `\\r` and the text after it is parsed as Python, including, when the CR is followed by suitable indentation, as a statement within the class body that follows on the next template line.\n\n### Details\n\nThe vulnerable templates each contain `# {{ comment }}` with no escaping:\n\n- `src/datamodel_code_generator/model/template/TypeAliasAnnotation.jinja2:12` and `:19`\n- `src/datamodel_code_generator/model/template/TypeAliasType.jinja2:12` and `:19`\n- `src/datamodel_code_generator/model/template/TypeStatement.jinja2:12` and `:19`\n- `src/datamodel_code_generator/model/template/pydantic_v2/BaseModel.jinja2:4`\n- `src/datamodel_code_generator/model/template/pydantic_v2/RootModel.jinja2:19`\n- `src/datamodel_code_generator/model/template/pydantic_v2/RootModelTypeAlias.jinja2:13`\n\nThe `pydantic_v2/BaseModel.jinja2:4` site is representative:\n\n```jinja2\nclass {{ class_name }}({{ base_class }}):{% if comment is defined %} # {{ comment }}{% endif %}\n```\n\nWhen the developer-supplied extras file populates `comment` for a model, the value reaches the template via `DataModel.extra_template_data` (set in `src/datamodel_code_generator/model/base.py:736-742`) and Jinja2 interpolates it raw. None of the templates use `comment_safe`, `escape_docstring`, or any other line-terminator filter.\n\n### PoC\nComplete self contained POC is available at my secret gist: https://gist.github.com/thegr1ffyn/8ad6b8cb3cc2be9d3a0144aeb6896a3f\n\n### Impact\n\n- **Who\u0027s affected**: any developer or CI pipeline that runs `datamodel-codegen --extra-template-data \u003cfile\u003e` where the extras file is influenced by attacker-controlled input. Realistic scenarios include:\n - Extras file generated from a third-party schema-annotation system.\n - Extras file vendored from an upstream repository.\n - Extras file produced by a script that merges multiple `comment` sources.\n - Build pipelines that template the extras file from environment variables, ticket descriptions, or commit metadata.\n- **What it gains**: arbitrary Python code execution in the importer\u0027s process at `import` time.\n- **What it does NOT need**: the schema itself can be entirely benign; only the extras file needs to contain the malicious `comment`.\n- **What does block it**: not passing `--extra-template-data`, or rejecting extras files whose `comment` values contain `\\r`, `\\x0b`, or `\\x0c` before invocation.\n\n### Resolution\n\nThe fix normalizes `comment` values from built-in `--extra-template-data` before template rendering. Inline comments now convert CRLF, bare CR, vertical tab, and form feed into LF and prefix continuation lines with `# `, so attacker-controlled text stays inside the generated Python comment block.\n\n### Remediation\n\nUpgrade to `datamodel-code-generator` `0.60.2` or later.\n\nThis issue affects `datamodel-code-generator` versions `\u003e= 0.14.1, \u003c= 0.60.1` and is fixed in `0.60.2`.\n\nSubmitted by: Hamza Haroon (thegr1ffyn)",
"id": "PYSEC-2026-3566",
"modified": "2026-08-04T13:36:16.259383Z",
"published": "2026-08-04T11:34:44.771231Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/koxudaxi/datamodel-code-generator/security/advisories/GHSA-wjv6-jcfj-mf9r"
},
{
"type": "WEB",
"url": "https://github.com/koxudaxi/datamodel-code-generator/commit/b73abb5cd703a50471b8950bbd3bd0b82ad71de7"
},
{
"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-wjv6-jcfj-mf9r"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54654"
}
],
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "`datamodel-code-generator` vulnerable to code injection via unescaped carriage return in `--extra-template-data` `comment` field"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.