VYPR
Critical severity9.8NVD Advisory· Published Feb 20, 2026· Updated Apr 15, 2026

CVE-2026-2635

CVE-2026-2635

Description

MLflow Use of Default Password Authentication Bypass Vulnerability. This vulnerability allows remote attackers to bypass authentication on affected installations of MLflow. Authentication is not required to exploit this vulnerability.

The specific flaw exists within the basic_auth.ini file. The file contains hard-coded default credentials. An attacker can leverage this vulnerability to bypass authentication and execute arbitrary code in the context of the administrator. Was ZDI-CAN-28256.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
mlflowPyPI
< 3.8.0rc03.8.0rc0

Affected products

1

Patches

1
5bf2ec2bd422

Fix artifact path traversal vector (#19260)

https://github.com/mlflow/mlflowBen WilsonDec 10, 2025via ghsa
2 files changed · +80 3
  • mlflow/store/tracking/file_store.py+30 3 modified
    @@ -624,7 +624,10 @@ def _hard_delete_run(self, run_id):
             Permanently delete a run (metadata and metrics, tags, parameters).
             This is used by the ``mlflow gc`` command line and is not intended to be used elsewhere.
             """
    -        _, run_dir = self._find_run_root(run_id)
    +        # NB: Skip validation here since artifacts may have already been deleted
    +        # by gc before calling this method. The run_id was already validated
    +        # by search_runs/get_run before reaching this point.
    +        _, run_dir = self._find_run_root(run_id, validate_structure=False)
             shutil.rmtree(run_dir)
     
         def _get_deleted_runs(self, older_than=0):
    @@ -670,15 +673,33 @@ def _find_experiment_folder(self, run_path):
                 return get_parent_dir(parent)
             return parent
     
    -    def _find_run_root(self, run_uuid):
    +    def _is_valid_run_directory(self, run_dir):
    +        # Defense in depth: ensure we're not inside an artifacts folder
    +        path_parts = os.path.normpath(run_dir).split(os.sep)
    +        if FileStore.ARTIFACTS_FOLDER_NAME in path_parts[:-1]:
    +            return False
    +
    +        required_subdirs = [
    +            FileStore.METRICS_FOLDER_NAME,
    +            FileStore.PARAMS_FOLDER_NAME,
    +            FileStore.ARTIFACTS_FOLDER_NAME,
    +        ]
    +        return all(is_directory(os.path.join(run_dir, subdir)) for subdir in required_subdirs)
    +
    +    def _find_run_root(self, run_uuid, validate_structure=True):
             _validate_run_id(run_uuid)
             self._check_root_dir()
             all_experiments = self._get_active_experiments(True) + self._get_deleted_experiments(True)
             for experiment_dir in all_experiments:
                 runs = find(experiment_dir, run_uuid, full_path=True)
                 if len(runs) == 0:
                     continue
    -            return os.path.basename(os.path.abspath(experiment_dir)), runs[0]
    +            run_dir = runs[0]
    +            # NB: Validate run directory structure to prevent path traversal via malicious
    +            # meta.yaml in artifact folders (ZDI-CAN-26649)
    +            if validate_structure and not self._is_valid_run_directory(run_dir):
    +                continue
    +            return os.path.basename(os.path.abspath(experiment_dir)), run_dir
             return None, None
     
         def update_run_info(self, run_id, run_status, end_time, run_name):
    @@ -784,6 +805,12 @@ def _get_run_info(self, run_uuid):
                     f"Run '{run_uuid}' metadata is in invalid state.",
                     databricks_pb2.INVALID_STATE,
                 )
    +        # Defense in depth: verify run_id in meta.yaml matches the directory name
    +        if run_info.run_id != os.path.basename(run_dir):
    +            raise MlflowException(
    +                f"Run '{run_uuid}' metadata is in invalid state.",
    +                databricks_pb2.INVALID_STATE,
    +            )
             return run_info
     
         def _get_run_info_from_dir(self, run_dir):
    
  • tests/store/tracking/test_file_store.py+50 0 modified
    @@ -3987,3 +3987,53 @@ def test_get_experiment_missing_and_empty_metadata_file(tmp_path):
         # Should raise MissingConfigException about invalid metadata
         with pytest.raises(MissingConfigException, match=rf"Experiment {exp_id} is invalid with empty"):
             fs._get_experiment(exp_id)
    +
    +
    +def test_malicious_meta_yaml_in_artifact_folder_path_traversal(tmp_path):
    +    """
    +    Regression test for ZDI-CAN-26649: Directory traversal via malicious meta.yaml.
    +
    +    Attack flow that should be blocked:
    +    1. Create experiment with artifact_location pointing to FileStore root
    +    2. Create a run - artifacts go to {root}/{run_id}/artifacts/
    +    3. Plant malicious meta.yaml in artifacts folder with arbitrary artifact_uri
    +    4. Try to use "artifacts" as run_uuid to access files via the malicious artifact_uri
    +
    +    The fix validates that run directories have required subdirectories (metrics/, params/,
    +    artifacts/), which artifact folders do not have.
    +    """
    +    root_dir = tmp_path / "mlruns"
    +    root_dir.mkdir()
    +    fs = FileStore(str(root_dir))
    +
    +    exp_id = fs.create_experiment("malicious_exp", artifact_location=str(root_dir))
    +    run = fs.create_run(
    +        experiment_id=exp_id, user_id="attacker", start_time=0, tags=[], run_name=""
    +    )
    +    run_id = run.info.run_id
    +
    +    assert Path(run.info.artifact_uri) == root_dir / run_id / "artifacts"
    +
    +    artifacts_dir = root_dir / run_id / "artifacts"
    +    artifacts_dir.mkdir(parents=True, exist_ok=True)
    +
    +    target_dir = tmp_path / "sensitive_data"
    +    target_dir.mkdir()
    +
    +    malicious_meta = {
    +        "run_id": "artifacts",
    +        "run_uuid": "artifacts",
    +        "experiment_id": run_id,
    +        "user_id": "attacker",
    +        "status": 1,
    +        "start_time": 0,
    +        "end_time": None,
    +        "lifecycle_stage": "active",
    +        "artifact_uri": str(target_dir),
    +        "tags": [],
    +    }
    +    write_yaml(str(artifacts_dir), "meta.yaml", malicious_meta)
    +
    +    # The fix should prevent the artifact folder from being treated as a run directory
    +    with pytest.raises(MlflowException, match="Run 'artifacts' not found"):
    +        fs.get_run("artifacts")
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.