CVE-2026-33264: Verified Reproduction
CVE-2026-33264: Apache Airflow <3.3.0 allows deserialization of attacker-controlled class paths in BaseSerialization.deserialize , enabling DAG authors to trigger RCE in the Scheduler/API Server via malicious serialized DAGs.
CVE-2026-33264 is verified against apache/airflow · PyPI. 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-00271.
What Is CVE-2026-33264?
CVE-2026-33264 is a critical deserialization vulnerability (CWE-502, CVSS 9.8) in Apache Airflow before 3.3.0 that lets a DAG author trigger remote code execution in the Scheduler/API Server via a malicious serialized DAG. Pruva reproduced it (reproduction REPRO-2026-00271).
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 · PyPI versions < 3.3.0 are affected.
How to Reproduce CVE-2026-33264
pruva-verify REPRO-2026-00271 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00271/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
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
serialized DAG default_args containing BASE_TRIGGER class path evil_trigger_module.EvilTrigger
- HTTP GET /ui/grid/structure/{dag_id}
- SerializedDagModel.dag
- DagSerialization.from_dict
- BaseSerialization.deserialize(BASE_TRIGGER)
- import_string(attacker class path)
How the agent worked
Root Cause and Exploit Chain for CVE-2026-33264
Apache Airflow before 3.3.0 deserializes attacker-controlled serialized DAG fields with BaseSerialization.deserialize() without sufficiently constraining class-path imports. In the reproduced path, a malicious serialized DAG contains a __type="base_trigger" value whose class path points to an attacker-controlled module. When the real Airflow API server handles a UI endpoint that loads the serialized DAG (/ui/grid/structure/{dag_id}), Airflow 3.2.2 reaches SerializedDagModel.dag -> DagSerialization.from_dict -> BaseSerialization.deserialize() and calls import_string() on that class path, executing module-level code inside the API server process. Airflow 3.3.0 no longer imports the same base_trigger payload and fails closed without executing the attacker module.
- Package/component affected:
apache-airflow, specifically serialized DAG deserialization inairflow.serialization.serialized_objects.BaseSerializationas reached by API server/UI serialized DAG loading. - Affected versions: Apache Airflow before 3.3.0, reproduced here with Airflow 3.2.2.
- Fixed version: Apache Airflow 3.3.0.
- Risk level and consequences: critical/important security-boundary violation. A DAG author who can cause malicious serialized DAG data to be loaded can execute Python code in the Scheduler/API Server process, not merely in an isolated task execution context. This can expose scheduler/API credentials, metadata database access, local files, and any privileges available to those Airflow control-plane processes.
Impact Parity
- Disclosed/claimed maximum impact: code execution in Scheduler/API Server through unrestricted class-path import during serialized DAG deserialization.
- Reproduced impact from this run: code execution in the real Airflow API server process. The proof module writes
api_server_rce_marker.txtat import time and records the API server PID. The vulnerable endpoint returns HTTP 200 after deserializing the DAG, and the marker content shows it was written by the API server process. - Parity:
full. - Not demonstrated: the proof stops at controlled file creation rather than running a shell command or exfiltrating secrets. This is sufficient to demonstrate arbitrary Python module import-time code execution, but no post-exploitation chain was attempted.
Root Cause
In Airflow 3.2.2, BaseSerialization.deserialize() contains a DAT.BASE_TRIGGER case that deserializes (trigger_class_path, kwargs), calls import_string(trigger_class_path), and instantiates the returned class. The serialized class path is attacker-controlled serialized DAG data. When a server-side process such as the API server loads serialized DAG rows from the metadata database, decorated DAG fields are passed through BaseSerialization.deserialize(), allowing the import to occur in the API server process.
The reproduction stores a malicious serialized DAG with dag.default_args = {"__type": "base_trigger", "__var": ["evil_trigger_module.EvilTrigger", ...]}. Airflow 3.2.2 accepts this type and imports evil_trigger_module, whose module-level code writes a marker file. Airflow 3.3.0 rejects the historical base_trigger type during deserialization and therefore does not import the attacker module.
Known fix references from the advisory:
Reproduction Steps
- Run
bundle/repro/reproduction_steps.sh. - The script:
- Reuses the prepared project cache at
<project_cache_dir>/repowhen available. - Uses/reuses an Airflow 3.2.2 virtualenv for the vulnerable product and an Airflow 3.3.0 virtualenv for the fixed control.
- For each attempt, initializes a fresh Airflow metadata database, creates an attacker-controlled module in the DAG folder, and seeds a serialized DAG row containing a malicious
base_triggerserialized value. - Starts the real
airflow api-serveron localhost, waits for/api/v2/monitor/health, then sends an HTTP request to/ui/grid/structure/{dag_id}?offset=0&limit=100. - Runs two vulnerable attempts and two fixed attempts.
- Reuses the prepared project cache at
- Expected evidence of reproduction:
- Airflow 3.2.2 attempts: HTTP endpoint is reached, returns 200, and
api_server_rce_marker.txtis created withimport-time code execution in Airflow API server; pid=<api-server-pid>. - Airflow 3.3.0 attempts: HTTP endpoint is reached but returns a deserialization error (HTTP 500) and no marker file is created.
- Airflow 3.2.2 attempts: HTTP endpoint is reached, returns 200, and
Evidence
Primary runtime evidence is written under bundle/logs/ and bundle/artifacts/ by bundle/repro/reproduction_steps.sh.
Key files:
bundle/logs/reproduction_steps.log— full script output, including both clean successful runs in this session.bundle/logs/attempt_3.2.2_1.jsonandbundle/logs/attempt_3.2.2_2.json— vulnerable product attempts.bundle/logs/attempt_3.3.0_1.jsonandbundle/logs/attempt_3.3.0_2.json— fixed product attempts.bundle/artifacts/airflow_api_3.2.2_1/api_server_rce_marker.txtandbundle/artifacts/airflow_api_3.2.2_2/api_server_rce_marker.txt— vulnerable API-server import-time execution markers.bundle/artifacts/airflow_api_*/airflow_api_server.log— API server startup and HTTP access logs.bundle/artifacts/airflow_api_*/endpoint_headers.txtandbundle/artifacts/airflow_api_*/endpoint_response.json— captured HTTP endpoint responses.bundle/repro/runtime_manifest.json— structured runtime evidence manifest.bundle/repro/validation_verdict.json— structured verdict.
Representative vulnerable evidence from the second verified run:
attempt_3.2.2_1.json:service_started=true,healthcheck_passed=true,target_reached=true,http_status="200",import_side_effect_observed=true.- Marker content:
import-time code execution in Airflow API server; pid=17188. - API server access log shows
method=GET path=/ui/grid/structure/cve_2026_33264_probe_3_2_2_1 ... status_code=200.
Representative fixed-control evidence:
attempt_3.3.0_1.jsonandattempt_3.3.0_2.json:service_started=true,healthcheck_passed=true,target_reached=true,http_status="500",import_side_effect_observed=false.- Fixed response excerpt:
An error occurred while trying to deserialize Dag, with noapi_server_rce_marker.txtcreated.
Environment details captured:
- Vulnerable Airflow: 3.2.2 loaded from
<project_cache_dir>/repo/airflow_product_venv. - Fixed Airflow: 3.3.0 loaded from
<project_cache_dir>/repo/airflow_3.3.0_cve_2026_33264_venv. - Product entrypoint:
airflow api-server(uvicorn) reached over localhost HTTP.
Recommendations / Next Steps
- Upgrade
apache-airflowto 3.3.0 or later. - Keep
[core] allowed_deserialization_classesrestricted to a narrow allowlist as defense in depth, especially in deployments where DAG authors are not fully trusted. - Add regression tests that insert or load serialized DAG payloads containing historical/unknown serialized types such as
base_triggerand assert that API server and scheduler code reject them without importing user-controlled modules. - Audit other serialized DAG fields that call generic deserialization helpers to ensure no other user-controlled class path reaches
import_string()in control-plane processes.
Additional Notes
- Idempotency confirmation:
bundle/repro/reproduction_steps.shwas executed twice consecutively and completed successfully both times after the fixed-control payload-generation correction. - The proof uses a local HTTP API server and a local SQLite metadata database, but it exercises the real Airflow API server product path and a real HTTP endpoint rather than calling
BaseSerialization.deserialize()directly. - The seeded malicious serialized DAG row represents the advisory scenario: attacker-controlled serialized DAG data containing a class path is loaded by the API server. The exploit module is placed on the API server's DAG-folder
PYTHONPATH, matching deployments where control-plane processes can import DAG-bundle modules.
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.
ls -la /data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/ 2>/dev/null && echo "---REPO---" && ls -la /data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/repo 2>/dev/null | head -50 && echo "---MIRROR---" && ls -la /data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/repo-mirrors 2>/dev/null{"cwd":"/data/pruva/runs/e6d20256-b0e8-49c3-a908-a69cffcc1535","duration_ms":7,"exit_code":0,"stderr":"","stdout":"total 0\ndrwxr-xr-x 1 vscode vscode 44 Jul 6 05:49 .\ndrwxr-xr-x 1 vscode vscode 5710 Jul 7 21:53 ..\ndrwxr-xr-x 1 vscode vscode 22 Jul 6 05:49 .pruva\ndrwxr-xr-x 1 vscode vscode 270 Jul 6 09:29 repo\ndrwxr-xr-x 1 vscode vscode 0 Jul 6 05:14 repo-mirrors\n---REPO---\ntotal 68\ndrwxr-xr-x 1 vscode vscode 270 Jul 6 09:29 .\ndrwxr-xr-x 1 vscode vscode 44 Jul 6 05:49 ..\ndrwxr-xr-x 1 vscode vscode 76 Jul 6 05:42 airflow_product_venv\n-rw-r--r-- 1 vscode vscode 65728 Jul 6 05:25 install_22.1.0.log\ndrwxr-xr-x 1 vscode vscode 476 Jul 6 05:24 pkg\ndrwxr-xr-x 1 vscode vscode 76 Jul 6 05:34 probe_venv\ndrwxr-xr-x 1 vscode vscode 76 Jul 6 05:34 probe_venv2\ndrwxr-xr-x 1 vscode vscode 62 Jul 6 09:23 tools\ndrwxr-xr-x 1 vscode vscode 76 Jul 6 05:18 venv\ndrwxr-xr-x 1 vscode vscode 76 Jul 6 09:28 venv_google_provider_22.1.0\ndrwxr-xr-x 1 vscode vscode 76 Jul 6 09:29 venv_google_provider_22.2.1\ndrwxr-xr-x 1 vscode vscode 0 Jul 6 05:42 wheelhouse\n---MIRROR---\ntotal 0\ndrwxr-xr-x 1 vscode vscode 0 Jul 6 05:14 .\ndrwxr-xr-x … [truncated]echo "===PKG===" && ls -la /data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/repo/pkg/ && echo "===PRUVA===" && ls -la /data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/.pruva/ && find /data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/.pruva/ -maxdepth 4 -type f 2>/dev/null | head -30 && echo "===TOOLS===" && ls -la /data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/repo/tools/ && echo "===VENV===" && ls -la /data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/repo/venv/ && echo "===AIRFLOW_PRODUCT_VENV===" && ls -la /data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/repo/airflow_product_venv/{"cwd":"/data/pruva/runs/e6d20256-b0e8-49c3-a908-a69cffcc1535","duration_ms":13,"exit_code":0,"stderr":"","stdout":"===PKG===\ntotal 4244\ndrwxr-xr-x 1 vscode vscode 476 Jul 6 05:24 .\ndrwxr-xr-x 1 vscode vscode 270 Jul 6 09:29 ..\n-rw-r--r-- 1 vscode vscode 1078972 Jul 6 05:24 apache_airflow_providers_google-22.0.0-py3-none-any.whl\n-rw-r--r-- 1 vscode vscode 1082710 Jul 6 05:24 apache_airflow_providers_google-22.1.0-py3-none-any.whl\n-rw-r--r-- 1 vscode vscode 1088000 Jul 6 05:23 apache_airflow_providers_google-22.2.0-py3-none-any.whl\n-rw-r--r-- 1 vscode vscode 1088679 Jul 6 05:23 apache_airflow_providers_google-22.2.1-py3-none-any.whl\ndrwxr-xr-x 1 vscode vscode 110 Jul 6 05:24 v220\ndrwxr-xr-x 1 vscode vscode 110 Jul 6 05:24 v2200\ndrwxr-xr-x 1 vscode vscode 110 Jul 6 05:24 v221\ndrwxr-xr-x 1 vscode vscode 110 Jul 6 05:24 v2210\n===PRUVA===\ntotal 0\ndrwxr-xr-x 1 vscode vscode 22 Jul 6 05:49 .\ndrwxr-xr-x 1 vscode vscode 44 Jul 6 05:49 ..\ndrwxr-xr-x 1 vscode vscode 60 Jul 6 09:56 proof-carry\n/data/pruva/project-cache/03610ec6-e6fb-4086-9681-103dd9199da6/.pruva/proof-carry/latest_attempt/repro/reproduction_steps.sh\n/data/pruva/project-cac… [truncated]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 · PyPI to 3.3.0 or later.
FAQ: CVE-2026-33264
How does the CVE-2026-33264 deserialization RCE work?
/ui/grid/structure/{dag_id}), it reaches SerializedDagModel.dag -> DagSerialization.from_dict -> BaseSerialization.deserialize(), which calls import_string() on the attacker's base_trigger class path — executing module-level code inside the API server process rather than an isolated task worker.Which Airflow versions are affected by CVE-2026-33264, and where is it fixed?
How severe is CVE-2026-33264?
How can I reproduce CVE-2026-33264?
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.