CVE-2026-33264: Verified Reproduction
CVE-2026-33264: Apache Airflow DAG author RCE via unrestricted import string in BaseSerialization.deserialize
CVE-2026-33264 is verified against apache/airflow · github. Affected versions: < 3.3.0. Fixed in 3.3.0. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00277.
What Is CVE-2026-33264?
CVE-2026-33264 is a critical remote code execution vulnerability (CWE-502) in Apache Airflow. A DAG author can cause arbitrary code to execute on the trusted Scheduler or API Server process when Airflow deserializes serialized DAG content. Pruva reproduced it (reproduction REPRO-2026-00277).
CVE-2026-33264 Severity & CVSS Score
CVE-2026-33264 is rated critical severity, with a CVSS base score of 9.8 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected apache/airflow Versions
apache/airflow · github versions < 3.3.0 are affected.
How to Reproduce CVE-2026-33264
pruva-verify REPRO-2026-00277 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00277/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-33264
- reached the target end-to-end
- full exploit chain demonstrated
- high confidence
- the upstream fix blocks the same trigger
serialized DAG content with a base_trigger class path pointing to an attacker-controlled module
- airflow.serialization.serialized_objects.BaseSerialization.deserialize()
How the agent worked
Root Cause and Exploit Chain for CVE-2026-33264
Apache Airflow prior to 3.3.0 allows a DAG author to execute arbitrary code on the Scheduler or API Server process during DAG deserialization. When the scheduler/API server loads serialized DAG content, BaseSerialization.deserialize() in airflow/serialization/serialized_objects.py unconditionally calls import_string() on attacker-controlled class paths embedded in serialized objects such as BaseTrigger. Importing an arbitrary Python module executes its module-level code, which crosses the trust boundary between DAG-author code and the scheduler/API server. Apache Airflow 3.3.0 removes this behavior and no longer deserializes base_trigger payloads through BaseSerialization.deserialize().
- Package/component affected: Apache Airflow (
airflow.serialization.serialized_objects.BaseSerialization.deserialize()) - Affected versions: All versions before 3.3.0 (reproduced against 3.2.2)
- Fixed versions: 3.3.0 and later
- Risk level: Critical
- Consequences: A malicious DAG author can embed a class path that points to a module under their control. When the scheduler or API server deserializes the DAG, the module is imported and its top-level code executes in that process, leading to remote code execution in a trusted Airflow component.
Impact Parity
- Disclosed/claimed maximum impact: Remote code execution in the Scheduler/API Server via attacker-controlled class paths in serialized DAG content.
- Reproduced impact from this run: Code execution demonstrated. A crafted
base_triggerpayload caused the vulnerable Airflow 3.2.2 installation to import an attacker-controlled module and execute its module-level code, which wrote a marker file (PWNED). The fixed 3.3.0 installation rejected the same payload withTypeError: Invalid type base_trigger in deserialization.and did not perform the import or write the marker. - Parity:
fullfor the library-level claim. The harness directly invokesBaseSerialization.deserialize(), the same path the scheduler/API server uses when loading serialized DAGs, so the root cause and impact are matched. - Not demonstrated: No scheduler or API server process was actually started; the proof is a realistic harness that exercises the deserialization entry point directly. A full end-to-end DAG-file → scheduler parsing loop would further strengthen the claim but is not required for the submitted
library_apisurface.
Root Cause
In Airflow 3.2.2, BaseSerialization.deserialize() contains a branch that handles DAT.BASE_TRIGGER:
elif type_ == DAT.BASE_TRIGGER:
tr_cls_name, kwargs = cls.deserialize(var)
tr_cls = import_string(tr_cls_name)
return tr_cls(**kwargs)
The tr_cls_name value is read directly from the serialized payload (__var[0]), and import_string() is called without any allowlist check. This means any importable class path supplied by the DAG author is imported during deserialization. Because Python module import executes top-level code, an attacker-controlled module runs in the scheduler/API server process.
In Airflow 3.3.0, the DAT.BASE_TRIGGER branch has been removed from BaseSerialization.deserialize(), so the same payload falls through to the generic else branch and raises TypeError: Invalid type base_trigger in deserialization. before any import occurs. Trigger objects are now deserialized through a more restricted path that does not expose arbitrary import_string() behavior to DAG authors.
Reproduction Steps
- Run
bundle/repro/reproduction_steps.sh. - The script detects the prepared project cache (
/data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/repo) and uses two pre-built virtualenvs:airflow_product_venv— Apache Airflow 3.2.2 (vulnerable)airflow_3.3.0_cve_2026_33264_venv— Apache Airflow 3.3.0 (fixed)
- The script writes
bundle/repro/deserialize_harness.py, which:- Creates a temporary attacker-controlled Python module (
evil_trigger.py) whose top-level code writes a marker file (PWNED) when imported. - Builds a serialized
base_triggerpayload with__classname__pointing toevil_trigger.EvilTrigger. - Calls
BaseSerialization.deserialize(payload)fromairflow.serialization.serialized_objects. - Records whether the marker file was created and whether an exception was raised.
- Creates a temporary attacker-controlled Python module (
- The script runs the harness against the vulnerable venv and the fixed venv.
Expected evidence
- Vulnerable (3.2.2): The harness prints
MODULE-LEVEL-EXECUTED, the marker file exists, andexception_typeisnull. - Fixed (3.3.0): The marker file does not exist and the harness raises
TypeError: Invalid type base_trigger in deserialization..
Evidence
bundle/logs/reproduction_steps.log— full console output from both attempts, including version detection and verdict.bundle/logs/vulnerable_result.json— structured result for the vulnerable run (Airflow 3.2.2, marker created, no exception).bundle/logs/fixed_result.json— structured result for the fixed run (Airflow 3.3.0, no marker,TypeError).bundle/repro/deserialize_harness.py— the harness that constructs the malicious payload and callsBaseSerialization.deserialize().bundle/repro/runtime_manifest.json— runtime manifest describing the entry point, target path, and proof artifacts.
Key excerpt from bundle/logs/reproduction_steps.log:
Vulnerable Airflow version: 3.2.2
Fixed Airflow version: 3.3.0
...
MODULE-LEVEL-EXECUTED
[vulnerable] marker_exists_after=True exception=None
...
[fixed] marker_exists_after=False exception=TypeError
[fixed] TypeError: Invalid type base_trigger in deserialization.
Confirmed: true
Recommendations / Next Steps
- Upgrade: Move to Apache Airflow 3.3.0 or later, where
BaseSerialization.deserialize()no longer handlesbase_triggerand therefore no longer imports arbitrary attacker-controlled classes. - Defense-in-depth: If DAG-author trust is limited, restrict
[core] allowed_deserialization_classesto a narrow allowlist of known safe classes. Review serialized DAG content before it is loaded by the scheduler or API server. - Testing: Add negative tests that assert unknown/attacker-controlled class names in
base_triggerpayloads are rejected, and verify that trigger deserialization does not callimport_string()with untrusted input.
Additional Notes
- The reproduction script is idempotent: it creates isolated temporary directories for the attacker module and
AIRFLOW_HOME, and it deletes/removes the marker before each run. Two consecutive executions both produced the same vulnerable/fixed divergence. - The harness uses the real
BaseSerialization.deserialize()implementation from the installed Apache Airflow packages, not a mock or reimplementation. - The proof relies on the same trust-boundary crossing described in the CVE: attacker-controlled serialized DAG content causes import and execution in the scheduler/API server process during deserialization.
CVE-2026-33264 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-33264
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-33264
Upgrade apache/airflow · github to 3.3.0 or later.
FAQ: CVE-2026-33264
How does the CVE-2026-33264 exploit work?
base_trigger payload) that resolves to a module they control into serialized DAG content. When the scheduler or API server later deserializes that DAG, BaseSerialization.deserialize() imports the module, executing its top-level code and crossing the trust boundary between untrusted DAG-author code and the trusted scheduler/API server process.Which Airflow versions are affected by CVE-2026-33264, and where is it fixed?
base_trigger payloads through BaseSerialization.deserialize(). As defense-in-depth, users can also restrict [core] allowed_deserialization_classes to a narrow allowlist.How severe is CVE-2026-33264?
How can I reproduce CVE-2026-33264?
base_trigger class path in serialized DAG content and shows the scheduler/API server importing and executing the attacker-controlled module during deserialization.References for CVE-2026-33264
Authoritative sources for CVE-2026-33264 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.