
Detailed CVE-2026-41490 disclosure with PoC demonstrating unauthenticated SQL injection in Dagster database I/O managers via dynamic partition keys, affecting DuckDB, Snowflake, BigQuery, and DeltaLake integrations.
Schweregrad: Hoch (CVSS 8.x — AV:N/AC:L/PR:L/UI:N + C:H/I:H/A:H)
CWE: CWE-89 — SQL-Injection
Betroffen: dagster Datenbank-I/O-Manager-Integrationen — dagster-duckdb, dagster-snowflake, dagster-gcp (BigQuery), dagster-deltalake, dagster-snowflake-polars (≤ 1.12.20)
Sicherheitshinweis: GHSA-mjw2-v2hm-wj34
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-41490
Credit: Romain Deperne
Jeder Dagster-Datenbank-I/O-Manager erstellt seine WHERE-Klausel durch f-String-Interpolation von Partitionsschlüsselwerten direkt in SQL. Wenn ein Asset DynamicPartitionsDefinition verwendet, können Partitionsschlüssel zur Laufzeit über die GraphQL-API (addDynamicPartition) gesetzt werden – die in der Standard-Webserver-Bereitstellung nicht authentifiziert ist. Ein bösartiger Partitionsschlüssel gelangt unescaped in sowohl SELECT- (Laden) als auch DELETE- (Bereinigen) Abfragen, was zu einer SQL-Injection gegen das zugrundeliegende Data Warehouse (Snowflake, BigQuery, DuckDB, DeltaLake, …) führt.
Der gleiche Helfer, _static_where_clause, wird über fünf I/O-Manager-Pakete kopiert. Jeder tut:
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 wird in einfache Anführungszeichen gesetzt, ohne Escape. Die Frage war, ob partition jemals angreifergesteuert ist. Bei statischen Partitionen ist es vom Entwickler definiert – nicht interessant. Bei DynamicPartitionsDefinition werden die Schlüssel in Dagsters Metadaten-DB gespeichert und zur Laufzeit über die addDynamicPartition GraphQL-Mutation hinzugefügt. In der Standard-dagster-webserver-Bereitstellung ist GraphQL nicht authentifiziert, sodass ein Angreifer im Netzwerk den Partitionsschlüssel durchgängig bereitstellt.
Ich habe beide Enden der Kette bestätigt: Die GraphQL-Mutation akzeptiert beliebige Schlüsselstrings ohne Validierung, und der Schlüssel erreicht _static_where_clause unverändert über context.asset_partition_keys.
addDynamicPartition(... partitionKey: "') UNION SELECT username, password_hash FROM secret_table; --")launchRun(...) targeting that partitionSELECT/DELETE ... WHERE col in ('') UNION SELECT ... ; --')| Paket | Datei |
|---|---|
| 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 |
Sowohl der Lade- als auch der Bereinigungspfad verwenden es:
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
Partitionsschlüssel wurden als vertrauenswürdige Entwicklerkonstanten behandelt, aber DynamicPartitionsDefinition verwandelt sie in Laufzeit-, extern setzbare Eingaben. Die f-String-Interpolation, die für statische Schlüssel "sicher" war, wird für dynamische zu einer Injection. Fix: Parametrisierte Abfragen / korrektes Identifier+Literal-Encoding anstelle von f-Strings.
poc/poc_partition_sqli.py – zeigt die anfällige Ausgabe von _static_where_clause für gutartige vs. bösartige Schlüssel, führt eine Live-DuckDB UNION-basierte Exfiltration + DROP TABLE gegen eine mit Seed-Daten gefüllte Datenbank aus und gibt die genauen addDynamicPartition / launchRun GraphQL-Payloads aus, die ein Angreifer sendet.
pip install duckdb # minimal; full chain: dagster dagster-duckdb pandas
python3 poc/poc_partition_sqli.py
Nicht authentifizierte (Standardkonfiguration) SQL-Injection gegen das Data Warehouse, das Dagster orchestriert – beliebiges Lesen anderer Tabellen und destruktive Schreibzugriffe. Dagster steht im Zentrum von Datenplattformen, daher erreicht dies den sensibelsten Speicher im Stack.
Verantwortungsvoll über GitHub Security Advisory offengelegt. PoC nach dem Fix veröffentlicht.