{
  "variant_id": "CVE-2026-49297-symlink-sftp-bypass",
  "created_at": "2026-07-06T00:00:00Z",
  "variant_summary": "Fixed-version bypass of GCSToSFTPOperator path containment: a lexically safe GCS object name under a symlinked subdirectory is accepted by apache-airflow-providers-google 22.2.1 and SFTP resolution writes outside destination_path.",
  "relation": "newer_version_sibling",
  "origin_kind": "pruva_variant",
  "repository": "https://github.com/apache/airflow",
  "submitted_target": {
    "target_kind": "pypi_release",
    "version": "22.1.0",
    "commit_sha": "2cd797027e9b3c30255f6b24c6ea6a276725cfa6",
    "ref": "providers-google/22.1.0",
    "display": "apache-airflow-providers-google==22.1.0"
  },
  "variant_target": {
    "target_kind": "pypi_release",
    "version": "22.2.1",
    "commit_sha": "c7bcb8d40f5fa42a98161fadb2166a0fa7fa5150",
    "ref": "providers-google/22.2.1",
    "display": "apache-airflow-providers-google==22.2.1"
  },
  "same_root_cause_confidence": "high",
  "same_surface_confidence": "medium",
  "claimed_surface": "GCS object names from bucket listing copied by Airflow Google provider operators to filesystem/SFTP destinations",
  "validated_surface": "GCSToSFTPOperator copies a GCS object listed from a bucket to an SFTP destination path that resolves through a symlink outside destination_path",
  "required_entrypoint_kind": "airflow_operator",
  "required_entrypoint_detail": "Airflow task/operator execution of airflow.providers.google.cloud.transfers.gcs_to_sftp.GCSToSFTPOperator with wildcard source_object='subdir/*', keep_directory_structure=True, and destination_path containing subdir/link symlink",
  "attacker_controlled_input": "GCS object name 'subdir/link/symlink_escape_1.txt' returned by bucket listing; object name contains no '..' path segment",
  "trigger_path": "GCSToSFTPOperator.execute() -> GCSHook.list() -> _resolve_destination_path() lexical normpath containment -> _copy_single_object() -> GCSHook.download() -> SFTPHook.store_file() -> SFTP server follows symlink outside destination_path",
  "observed_impact_class": "path_traversal_arbitrary_file_write",
  "exploitability_confidence": "high",
  "evidence_scope": "production_path",
  "runtime_manifest_present": true,
  "end_to_end_target_reached": true,
  "inferred": false,
  "blocking_mitigation": null,
  "file_path": "airflow/providers/google/cloud/transfers/gcs_to_sftp.py",
  "line_start": 173,
  "line_end": 228,
  "secondary_anchors": [
    {
      "file_path": "airflow/providers/google/cloud/operators/gcs.py",
      "line_start": 878,
      "line_end": 912
    },
    {
      "file_path": "airflow/providers/google/cloud/transfers/gcs_to_sftp.py",
      "line_start": 147,
      "line_end": 165
    }
  ],
  "review_scope_paths": [
    "airflow/providers/google/cloud/transfers/gcs_to_sftp.py",
    "airflow/providers/google/cloud/operators/gcs.py",
    "airflow/providers/google/cloud/hooks/gcs.py",
    "airflow/providers/sftp/hooks/sftp.py"
  ],
  "artifact_refs": {
    "variant_manifest": "vuln_variant/variant_manifest.json",
    "validation_verdict": "vuln_variant/validation_verdict.json",
    "runtime_manifest": "vuln_variant/runtime_manifest.json",
    "repro_log": "logs/vuln_variant_reproduction_steps.log",
    "root_cause_equivalence": "vuln_variant/root_cause_equivalence.json",
    "source_identity": "vuln_variant/source_identity.json",
    "patch_analysis": "vuln_variant/patch_analysis.md",
    "reproducer": [
      "vuln_variant/reproduction_steps.sh",
      "vuln_variant/symlink_variant_harness.py"
    ],
    "fixed_attempt": "logs/vuln_variant_attempt_22.2.1_1.json",
    "vulnerable_attempt": "logs/vuln_variant_attempt_22.1.0_1.json"
  }
}
