
Inyección de código (RCE) en datamodel-code-generator mediante customBasePath sin validar (CVE-2026-63720)
Gravedad: Alta, CVSS 3.1 7.5 / CVSS 4.0 7.5 (asignada por VulnCheck, la CNA)
Techo ambiental (despliegue como servicio de red): hasta 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
Afectado: datamodel-code-generator < 0.70.0
Corregido en: 0.70.0
CWE: CWE-94 (Control inadecuado de la generación de código, 'Inyección de código')
Reportado por: Rahul Karne
CNA: VulnCheck
Publicado: 26 de julio de 2026
datamodel-code-generator validaba todas las cadenas de importación controladas por el esquema que pudieran transportar una carga útil de inyección de código, excepto una.
La herramienta convierte un esquema de entrada (JSON Schema, OpenAPI, YAML) en código fuente de modelos Python. Varios campos del esquema se renderizan directamente en ese código generado, por lo que el proyecto los valida como identificadores Python con puntos antes de usarlos, específicamente para prevenir la inyección. El campo de extensión del esquema customBasePath es el único campo hermano que omite esta comprobación. Su valor fluye sin sanitizar hacia una sentencia from ... import ... en la salida generada. Un atacante que controle el esquema de entrada puede incrustar Python arbitrario usando saltos de línea y una expresión sin puntos, y este se ejecuta en el momento en que se importa el módulo generado, que es el siguiente paso habitual después de generar los modelos.
Se trata de una corrección incompleta de CVE-2026-55415 (GHSA-5578-w22f-pfx9), que endureció los campos hermanos customTypePath y x-python-import contra esta clase exacta de vulnerabilidad. Esa corrección no cubrió customBasePath, que llega al mismo sumidero sin validar y permaneció explotable hasta 0.68.1 y en main hasta 0.70.0.
Ejecución de código Python arbitrario en el proceso que importa o ejecuta los modelos generados: la máquina del desarrollador, un runner de CI o cualquier servicio que genere y luego cargue modelos. La confidencialidad, integridad y disponibilidad del host quedan completamente comprometidas, limitadas únicamente por los privilegios de ese proceso.
La gravedad depende enteramente de dónde se ejecute codegen sobre entradas no confiables:
Quién está afectado: cualquier uso de datamodel-code-generator < 0.70.0 que (1) genere modelos a partir de un esquema cuyo valor de customBasePath esté influenciado por el atacante y (2) importe o ejecute el módulo generado. El flujo de trabajo predeterminado de codegen y posterior importación cumple (2) de forma inherente.
Quién no está afectado:
0.70.0 o posterior, donde customBasePath se valida.| Métrica | Valor | Fuente |
|---|---|---|
| Descargas, total histórico | 194 millones | pepy.tech/projects/datamodel-code-generator |
| Descargas, últimos 30 días | 16,3 millones | pepy.tech |
| Despliegue típico | Máquinas de desarrolladores, pipelines de CI/CD y plataformas de generación de SDKs que generan código a partir de OpenAPI / JSON Schema | inherente a la función de la herramienta |
El valor del campo de esquema customBasePath se transporta al código generado sin ninguna restricción de identificador. Tres puntos del codebase son relevantes (rutas relativas a src/datamodel_code_generator/):
parser/jsonschema.py define el campo custom_base_path con alias="customBasePath" (~línea 644), consumido a través de _resolve_base_class(...) en varios puntos de llamada.parser/base.py, _resolve_base_class (~línea 1665), devuelve el valor después de solo un normalize() local (deduplicación/recorte). No se aplica ninguna validación de identificador.imports.py, Import.from_full_path() (~línea 35), emite el valor textualmente como una línea from ... import .... El valor también se usa como clase base en model/base.py set_base_class (~línea 1324) y se renderiza en bruto mediante la plantilla del modelo (class {{ class_name }}({{ base_class }}):).Debido a que el valor se escribe en el código fuente de Python sin restricción alguna, los saltos de línea incrustados y una expresión sin puntos sobreviven en la salida como líneas propias e individualmente analizables, y la línea intermedia se ejecuta al importar.
La carga útil carece de puntos por necesidad. Import.from_full_path divide el valor por ., por lo que una llamada normal os.system(...) se rompería. El uso de getattr(__import__('os'),'system')(...) evita cualquier . mientras sigue resolviendo la misma llamada, y los saltos de línea circundantes mantienen sintácticamente válidas las líneas from ... import ... emitidas, de modo que la línea intermedia inyectada se ejecuta limpiamente.
Este no es un proyecto que descuidara la inyección. El mantenedor endureció esta clase exacta repetidamente a través de múltiples avisos (GHSA-5578, m34r, 8m8r, wjv6), enrutando cada vez una cadena de importación o tipo controlada por el esquema a través de _validate_dotted_python_identifier_path antes de que llegue a la generación de código. Los campos hermanos customTypePath (validados en parser/jsonschema.py ~líneas 4956, 5202) y x-python-import (~línea 2096) pasan ambos por ese validador.
customBasePath es el único hermano sin esa llamada. Llega al mismo sumidero Import.from_full_path por una ruta diferente (_resolve_base_class) que nunca se conectó a la validación que recibieron los otros campos. El defecto sobrevivió precisamente porque la defensa circundante parecía completa: un revisor que busque cadenas de importación sin validar ve validadores en los campos que comprueba primero, y este se enruta a través de un helper que parece resolución de clase base en lugar de manejo de importaciones. Es una laguna en una corrección sistemática, no una corrección ausente, razón por la cual persistió hasta la última versión.
Un atacante necesita: