
Divulgación detallada de CVE-2026-41490 con PoC que demuestra una inyección SQL no autenticada en los gestores de E/S de bases de datos de Dagster mediante claves de partición dinámicas, afectando a las integraciones de DuckDB, Snowflake, BigQuery y DeltaLake.
Gravedad: Alta (CVSS 8.3) CWE: CWE-89 — Inyección SQL Afecta a: Dagster core <= 1.13.0 y paquetes de integración <= 0.29.0 Corregido en: Dagster core 1.13.1 y paquetes de integración 0.29.1 Aviso: GHSA-mjw2-v2hm-wj34 NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-41490 Crédito: Romain Deperne
Todos los gestores de E/S de base de datos de Dagster construyen su cláusula WHERE interpolando
directamente los valores de las claves de partición en SQL mediante f-strings. Cuando un asset utiliza
DynamicPartitionsDefinition, las claves de partición pueden establecerse en tiempo de ejecución a través
de la API GraphQL (addDynamicPartition), que no requiere autenticación en el despliegue predeterminado
del servidor web. Una clave de partición maliciosa fluye sin escapar tanto en las consultas SELECT
(carga) como en las DELETE (limpieza), lo que da lugar a una inyección SQL contra el almacén de datos
subyacente (Snowflake, BigQuery, DuckDB, DeltaLake, …).
El mismo helper, _static_where_clause, está copiado y pegado en cinco paquetes de gestores de E/S. Cada
uno hace lo siguiente:
def _static_where_clause(table_partition):
partitions = ", ".join(f"'{partition}'" for partition in table_partition.partitions)
return f"""{table_partition.partition_expr} in ({partitions})"""
partition está entre comillas simples sin escape. La cuestión era si partition está alguna vez
controlado por el atacante. Para las particiones estáticas está definido por el desarrollador — no es interesante. Para
DynamicPartitionsDefinition las claves se almacenan en la base de datos de metadatos de Dagster y se añaden en tiempo de ejecución
mediante la mutación GraphQL addDynamicPartition. En el despliegue predeterminado de dagster-webserver GraphQL
no está autenticado, por lo que un atacante en la red proporciona la clave de partición de extremo a extremo.
Confirmé ambos extremos de la cadena: la mutación GraphQL acepta cadenas de clave arbitrarias sin
validación, y la clave llega a _static_where_clause literalmente a través de context.asset_partition_keys.
addDynamicPartition(... partitionKey: "') UNION SELECT username, password_hash FROM secret_table; --")launchRun(...) dirigido a esa particiónSELECT/DELETE ... WHERE col in ('') UNION SELECT ... ; --')| Paquete | Archivo |
|---|---|
| dagster-duckdb | io_manager.py:340-342 |
| dagster-snowflake | snowflake_io_manager.py:434-436 |
| dagster-gcp (BigQuery) | bigquery/io_manager.py:472-474 |
| dagster-deltalake | io_manager.py:265-267 |
| dagster-snowflake-polars | snowflake_polars_type_handler.py:74 |
Tanto la ruta de carga como la de limpieza lo consumen:
query = f"SELECT {col_str} FROM {schema}.{table} WHERE\n" + _partition_where_clause(...) # read
query = f"DELETE FROM {schema}.{table} WHERE\n" + _partition_where_clause(...) # write
Las claves de partición se trataban como constantes de desarrollador confiables, pero DynamicPartitionsDefinition las convierte en entrada de tiempo de ejecución, configurable externamente. La interpolación con f-strings que era "segura" para claves estáticas se convierte en inyección para las dinámicas. Solución: consultas parametrizadas / citación adecuada de identificadores y literales en lugar de f-strings.
poc/poc_partition_sqli.py — muestra la salida vulnerable de _static_where_clause para claves benignas frente a maliciosas, ejecuta una exfiltración basada en UNION con DuckDB en vivo + DROP TABLE contra una base de datos sembrada, e imprime los payloads GraphQL exactos de addDynamicPartition / launchRun que envía un atacante.
pip install duckdb # minimal; full chain: dagster dagster-duckdb pandas
python3 poc/poc_partition_sqli.py
Inyección SQL no autenticada (configuración predeterminada) contra el almacén de datos que Dagster orquesta: lectura arbitraria de otras tablas y escrituras destructivas. Dagster se sitúa en el centro de las plataformas de datos, por lo que esto alcanza el almacén más sensible de la pila.
Divulgado de forma responsable mediante GitHub Security Advisory. PoC publicado después de la corrección.