CVE-2026-49297: Verified Reproduction
CVE-2026-49297: Apache Airflow Google provider path traversal via GCS object names
CVE-2026-49297 is verified against apache-airflow-providers-google · pip. Affected versions: before 22.2.1 (both bugs present in 22.0.0; GCSTimeSpanFileTransformOperator fixed in 22.1.0 via PR #67509; GCSToSFTPOperator fixed in 22.2.0 via PR #67667). Fixed in 22.2.1. Vulnerability class: Path Traversal. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00257.
What Is CVE-2026-49297?
CVE-2026-49297 is a medium-severity path traversal (CWE-22) in Apache Airflow's Google provider transfer operators, GCSToSFTPOperator and GCSTimeSpanFileTransformOperator, which can write downloaded blobs outside their configured destination directory. Pruva reproduced it (reproduction REPRO-2026-00257).
CVE-2026-49297 Severity & CVSS Score
CVE-2026-49297 is rated high severity, with a CVSS base score of 8.1 out of 10.
High — serious impact or readily exploitable. Prioritize remediation.
Affected apache-airflow-providers-google Versions
apache-airflow-providers-google · pip versions before 22.2.1 (both bugs present in 22.0.0; GCSTimeSpanFileTransformOperator fixed in 22.1.0 via PR #67509; GCSToSFTPOperator fixed in 22.2.0 via PR #67667) are affected.
How to Reproduce CVE-2026-49297
pruva-verify REPRO-2026-00257 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00257/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-49297
- reached the target end-to-end
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
GCS object name 'subdir/../../escaped_N.txt' returned by a bucket listing API for source_object 'subdir/*'
- airflow tasks test
- GCSToSFTPOperator.execute()
- GCSHook.list/download over GCS JSON API
- SFTPHook.store_file
How the agent worked
Root Cause and Exploit Chain for CVE-2026-49297
CVE-2026-49297 is a path traversal vulnerability in Apache Airflow's Google provider transfer operators, demonstrated here through GCSToSFTPOperator. When the operator processes a wildcard source_object, it obtains object names from the GCS bucket listing API and joins each returned object name directly to the configured SFTP destination_path. In vulnerable provider version 22.1.0, a GCS object name such as subdir/../../escaped_1.txt is accepted and copied to the SFTP server as destination_path/subdir/../../escaped_1.txt, which normalizes outside the intended destination directory. Fixed version 22.2.1 rejects the same object name before any SFTP write.
- Package/component affected:
apache-airflow-providers-google, specificallyairflow.providers.google.cloud.transfers.gcs_to_sftp.GCSToSFTPOperatorand the same object-name handling pattern described for related GCS transfer operators. - Affected versions: Reproduced in
apache-airflow-providers-google==22.1.0; the ticket states the issue is fixed in22.2.1. - Risk level and consequences: Medium. A principal who can write object names into a source GCS bucket consumed by an Airflow DAG can cause the DAG to write outside the configured SFTP destination directory. This can overwrite or create unintended files on the SFTP target host with the permissions available to the Airflow/SFTP workflow.
Impact Parity
- Disclosed/claimed maximum impact: Arbitrary file overwrite/path traversal through attacker-controlled GCS object names returned by the bucket listing API.
- Reproduced impact from this run: Full product-path file write outside the configured SFTP
destination_pathusing a real Airflow DAG task invocation, realGCSHook.list/downloadcalls against a local GCS JSON API emulator, and a real SFTP protocol server. - Parity:
full - Not demonstrated: The proof stops at creation of an attacker-controlled file outside
destination_path; it does not chain the write into code execution or privilege escalation, which were not required by the ticket.
Root Cause
In vulnerable GCSToSFTPOperator, wildcard processing calls GCSHook.list() and iterates over object names returned by the GCS API. For each object, _resolve_destination_path() returns os.path.join(self.destination_path, source_object) without canonicalizing the result or checking that it remains inside destination_path. GCS object names are attacker-controlled strings for anyone who can write to the source bucket, and object names may contain .. path segments. Therefore a listed object such as subdir/../../escaped_1.txt is transformed into a remote SFTP path under destination_path/subdir/../../escaped_1.txt, which normalizes to a sibling of destination_path.
The fixed provider release 22.2.1 adds a containment check in _resolve_destination_path(): it normalizes the joined destination and rejects paths that escape the configured base directory, raising ValueError before SFTPHook.store_file() is called. The exact upstream fix commit was not provided in the ticket; this reproduction uses the fixed release named by the ticket (apache-airflow-providers-google==22.2.1) as the negative control.
Reproduction Steps
- Run
bundle/repro/reproduction_steps.sh. - The script uses the prepared project cache when available, ensures two Python environments for
apache-airflow-providers-google==22.1.0and==22.2.1, and downloads/reusesfsouza/fake-gcs-serveras a local GCS JSON API endpoint. - For each attempt,
bundle/repro/product_harness.py:- starts
fake-gcs-serveron localhost, - creates a real GCS bucket/object via
google-cloud-storage, with object namesubdir/../../escaped_N.txt, - starts a Paramiko SFTP server on localhost,
- writes a real Airflow DAG containing
GCSToSFTPOperator, and - invokes it through
airflow tasks test cve_2026_49297_repro_dag copy_malicious_gcs_object_to_sftp.
- starts
- The script runs two vulnerable attempts and two fixed attempts. Expected evidence:
- vulnerable 22.1.0 attempts create
sftp_root/escaped_N.txtoutsidesftp_root/inbox, - fixed 22.2.1 attempts raise
ValueError: Refusing to copy GCS object ... escapes configured destination_path, and - fixed attempts perform no SFTP file write.
- vulnerable 22.1.0 attempts create
Evidence
Key runtime artifacts generated by the script:
- Main proof log:
bundle/logs/reproduction_steps.log - Vulnerable attempt summaries:
bundle/logs/attempt_22.1.0_1.jsonbundle/logs/attempt_22.1.0_2.json
- Fixed attempt summaries:
bundle/logs/attempt_22.2.1_1.jsonbundle/logs/attempt_22.2.1_2.json
- Per-attempt GCS API traces:
bundle/artifacts/product_test_22.1.0_1/gcs_api_trace.jsonbundle/artifacts/product_test_22.1.0_2/gcs_api_trace.jsonbundle/artifacts/product_test_22.2.1_1/gcs_api_trace.jsonbundle/artifacts/product_test_22.2.1_2/gcs_api_trace.json
- Per-attempt Airflow task logs:
bundle/artifacts/product_test_22.1.0_1/airflow_tasks_test.logbundle/artifacts/product_test_22.1.0_2/airflow_tasks_test.logbundle/artifacts/product_test_22.2.1_1/airflow_tasks_test.logbundle/artifacts/product_test_22.2.1_2/airflow_tasks_test.log
- Structured verdict:
bundle/repro/validation_verdict.json - Runtime manifest:
bundle/repro/runtime_manifest.json
Representative evidence from the latest successful run:
The vulnerable Airflow task log shows the real operator processing the malicious object and resolving an escaping SFTP path:
Executing copy of gs://malicious-bucket/subdir/../../escaped_1.txt to .../sftp_root/inbox/subdir/../../escaped_1.txtThe vulnerable attempt JSON records the out-of-directory write:
"escaped_file_exists": true,"file_inside_destination": false, and an SFTPopenoperation whose remote path is.../inbox/subdir/../../escaped_1.txtwhile the local file path is.../sftp_root/escaped_1.txt.The fixed Airflow task log records the negative control:
ValueError: Refusing to copy GCS object 'subdir/../../escaped_1.txt': resolved destination '.../sftp_root/escaped_1.txt' escapes configured destination_path '.../sftp_root/inbox'.The fixed attempt JSON records no escaped file and no SFTP write operations:
"escaped_file_exists": false,"exception_type": "ValueError", and"sftp_operations": [].
Environment details captured by the script include exact installed packages and source file paths for the operator and hook, for example:
apache-airflow==3.2.2apache-airflow-providers-google==22.1.0for the vulnerable runapache-airflow-providers-google==22.2.1for the fixed runapache-airflow-providers-sftp==5.8.2
Recommendations / Next Steps
- Upgrade
apache-airflow-providers-googleto22.2.1or later. - Keep the fixed containment check in
_resolve_destination_path()and apply equivalent checks to any other operator that maps GCS object names to local or remote filesystem paths. - Add regression tests that use GCS object names containing
.., absolute-looking path components, and mixed directory separators where applicable. - Prefer containment checks based on normalized/canonical paths and reject any resolved path outside the configured destination root before creating directories or opening remote files.
Additional Notes
- The final reproduction script was run successfully end-to-end and produced fresh current-run artifacts.
- The proof uses a local GCS JSON API emulator rather than monkey-patching
GCSHook; Airflow's realGCSHook.list()andGCSHook.download()cross an HTTP API boundary. - The proof uses a real Paramiko SFTP protocol server and records accepted SFTP connections and file-open operations.
- The script is idempotent: each attempt uses a clean per-attempt artifact directory, a new GCS emulator instance, and a new SFTP server instance.
CVE-2026-49297 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-49297
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-49297
Upgrade apache-airflow-providers-google · pip to 22.2.1 or later.
FAQ: CVE-2026-49297
Which apache-airflow-providers-google versions are affected by CVE-2026-49297, and where is it fixed?
How severe is CVE-2026-49297?
Does exploiting CVE-2026-49297 require compromising Airflow directly?
How can I reproduce CVE-2026-49297?
References for CVE-2026-49297
Authoritative sources for CVE-2026-49297 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.