
Divulgation détaillée de CVE-2026-41490 avec PoC démontrant une injection SQL non authentifiée dans les gestionnaires d'E/S de base de données de Dagster via des clés de partition dynamiques, affectant les intégrations DuckDB, Snowflake, BigQuery et DeltaLake.
Gravité : Élevée (CVSS 8.3) CWE : CWE-89 — Injection SQL Affecté : Dagster core <= 1.13.0 et packages d'intégration <= 0.29.0 Corrigé dans : Dagster core 1.13.1 et packages d'intégration 0.29.1 Avis : GHSA-mjw2-v2hm-wj34 NVD : https://nvd.nist.gov/vuln/detail/CVE-2026-41490 Crédit : Romain Deperne
Chaque gestionnaire d'E/S de base de données de Dagster construit sa clause WHERE en interpolant
directement les valeurs de clés de partition dans le SQL via des f-strings. Lorsqu'un asset utilise
DynamicPartitionsDefinition, les clés de partition peuvent être définies à l'exécution via l'API
GraphQL (addDynamicPartition) — qui n'est pas authentifiée dans le déploiement webserver par défaut.
Une clé de partition malveillante s'écoule sans échappement dans les requêtes SELECT (chargement) et
DELETE (nettoyage), produisant une injection SQL contre l'entrepôt de données sous-jacent (Snowflake,
BigQuery, DuckDB, DeltaLake, …).
Le même helper, _static_where_clause, est copié-collé dans cinq packages de gestionnaires d'E/S.
Chacun effectue :
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 entouré de guillemets simples sans échappement. La question était de savoir si partition est un jour contrôlé par un attaquant. Pour les partitions statiques, il est défini par le développeur — sans intérêt. Pour DynamicPartitionsDefinition, les clés sont stockées dans la base de métadonnées de Dagster et ajoutées à l'exécution via la mutation GraphQL addDynamicPartition. Dans le déploiement dagster-webserver par défaut, GraphQL n'est pas authentifié, donc un attaquant sur le réseau fournit la clé de partition de bout en bout.
J'ai confirmé les deux extrémités de la chaîne : la mutation GraphQL accepte des chaînes de clé arbitraires sans aucune validation, et la clé atteint _static_where_clause telle quelle via context.asset_partition_keys.
addDynamicPartition(... partitionKey: "') UNION SELECT username, password_hash FROM secret_table; --")launchRun(...) ciblant cette partitionSELECT/DELETE ... WHERE col in ('') UNION SELECT ... ; --')| Package | Fichier |
|---|---|
| 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 |
Les chemins de chargement et de nettoyage le consomment tous les deux :
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
Les clés de partition étaient traitées comme des constantes de développeur dignes de confiance, mais DynamicPartitionsDefinition les transforme en entrées d'exécution définissables de l'extérieur. L'interpolation f-string qui était « sûre » pour les clés statiques devient une injection pour les clés dynamiques. Correctif : requêtes paramétrées / citation correcte des identifiants et des littéraux au lieu des f-strings.
poc/poc_partition_sqli.py — montre la sortie vulnérable de _static_where_clause pour des clés bénignes vs. malveillantes, exécute une exfiltration basée sur UNION sur un DuckDB en direct + DROP TABLE contre une base de données préremplie, et affiche les charges utiles GraphQL exactes addDynamicPartition / launchRun qu'un attaquant envoie.
pip install duckdb # minimal; full chain: dagster dagster-duckdb pandas
python3 poc/poc_partition_sqli.py
Injection SQL non authentifiée (configuration par défaut) contre l'entrepôt de données orchestré par Dagster — lecture arbitraire d'autres tables et écritures destructrices. Dagster se situe au centre des plateformes de données, ce qui permet d'atteindre le stockage le plus sensible de la pile.
Divulgué de manière responsable via GitHub Security Advisory. PoC publié après le correctif.