You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

GATED SECURITY RESEARCH PoC β€” MLflow pyfunc artifacts path traversal (arbitrary local file read at model-load time)

Access is gated. This repository contains a proof-of-concept demonstrating a path-traversal / arbitrary local file read vulnerability in MLflow's pyfunc model loader. Shared under manual-approval gating for responsible-disclosure / triage purposes (huntr "Model File Format Vulnerability" bounty program, mlflow target, run by Protect AI / Palo Alto Networks β€” huntr.com/bounties/disclose/models).

Target

  • Package: mlflow (PyPI), version 3.14.0 β€” the current latest stable release as of 2026-07-11 (confirmed via pip index versions mlflow).
  • Installed from a real venv (pip install mlflow), not a git-clone dev checkout.
  • File: mlflow/pyfunc/model.py, function _load_context_model_and_signature(), lines 1321–1325.

Root cause

mlflow.pyfunc.load_model() resolves each entry of the pyfunc flavor's artifacts dict β€” which comes straight from the attacker-supplied MLmodel YAML file shipped inside a model directory/registry artifact β€” with:

# mlflow/pyfunc/model.py, installed mlflow==3.14.0, lines 1321-1325
artifacts[saved_artifact_name] = os.path.join(
    model_path, saved_artifact_info[CONFIG_KEY_ARTIFACT_RELATIVE_PATH]
)

There is no containment/normalization check β€” no os.path.realpath() + prefix check, no rejection of .. or absolute paths. Because os.path.join() silently discards its first argument when the second is absolute, and does not collapse .. sequences, a path: value in the MLmodel YAML such as:

../../../../../../../../../../../../tmp/victim_secret.txt

resolves to a path entirely outside the model directory. That resolved path is placed into context.artifacts[name] and handed to the model's own (attacker-authored but innocuous-looking) load_context(), which reads it β€” the contents can then be leaked via predict().

This is exploitable with zero changes to the model's Python code: a model author can write exactly the code MLflow's own "models-from-code" + artifacts workflow recommends (see model_src.py in this repo) β€” nothing suspicious in the source. The malicious part lives entirely in the declarative MLmodel metadata's path: field, which a manual code review of the Python source would never catch.

This is reachable via the "models-from-code" loading path, which MLflow's own docs recommend as the safe alternative to pickle-based python_model, and requires no MLFLOW_ALLOW_PICKLE_DESERIALIZATION opt-in and no Databricks runtime.

Sibling-code inconsistency: three lines above (model.py:1273), the adjacent model_code_path field IS defended against the identical trick, via os.path.basename(conf_model_code_path) before joining:

model_code_path = os.path.join(model_path, os.path.basename(conf_model_code_path))

The artifacts block three lines below has no equivalent protection β€” this asymmetry is the root cause and is demonstrated by the negative control below.

Dedup notes

This is a distinct bug/code path from all known MLflow path-traversal CVEs found during research, all of which are server-side, HTTP-request-driven:

  • CVE-2024-1483 β€” server artifact_location/source HTTP params
  • CVE-2024-1560, CVE-2024-1593, CVE-2024-1594, CVE-2024-2928 β€” Tracking Server handlers
  • CVE-2025-15036 / CVE-2025-15031 β€” tar-extraction in dbconnect_artifact_cache.py (fixed in 3.7.0)
  • CVE-2026-2033 / CVE-2026-2635 β€” Tracking Server RCE via artifact handler (fixed in 3.8.0, ZDI-CAN-26649)

None of these touch mlflow.pyfunc.load_model()'s local model-directory artifacts YAML parsing. mlflow==3.14.0 (current release, postdating all the fixes above) still contains this unguarded artifacts path join. This finding is purely client-side β€” triggered by loading a model directory/archive a victim downloads or pulls from a registry β€” with no server or HTTP component involved.

Impact

Arbitrary local file read at model-load time (triggered inside load_context(), before any predict() call is even required to complete the read β€” predict() here is only used to exfiltrate/display the already-read content), scoped to whatever file-system permissions the process loading the model has. In real deployments this can reach SSH keys, cloud credential files (~/.aws/credentials, service-account JSON, etc.), or any other file readable by the MLflow serving/loading process β€” all without needing arbitrary code execution, and via the format MLflow recommends as the secure one.

Files in this repository

  • model_src.py β€” the benign-looking pyfunc model implementation. Just reads context.artifacts["reference_data"] in load_context() and returns it from predict(). Nothing suspicious in this file β€” the exploit is metadata-only.
  • build_and_exploit.py β€” end-to-end driver:
    1. Builds a model with the public, documented mlflow.pyfunc.save_model(path=..., python_model=model_src.py, artifacts={...}) API.
    2. Tampers only the generated MLmodel YAML's flavors.python_function.artifacts.reference_data.path field, replacing the benign relative path with a ../../.. traversal to /tmp/victim_secret.txt (no code changes).
    3. Loads the model back with the stock, documented mlflow.pyfunc.load_model(MODEL_DIR) API β€” no special env vars β€” exactly the flow used for any model a user downloads and loads.
    4. Shows predict() returning the exact contents of /tmp/victim_secret.txt, a file entirely outside the model directory.
    5. Runs a negative control: applies the identical traversal trick to the sibling model_code_path field instead. This one correctly fails with an MlflowException, because that field IS sanitized via os.path.basename() before joining β€” proving the vulnerability is specific to the unguarded artifacts resolution, and that traversal isn't simply failing/succeeding for unrelated environmental reasons.
  • example_run_output.txt β€” real captured output from running the driver against the actual PyPI-released mlflow==3.14.0 package (not a git-HEAD dev checkout), including the source-line confirmation from the installed package and the negative-control result.

Reproduction

python3 -m venv venv && source venv/bin/activate
pip install mlflow cloudpickle pyyaml pandas
python build_and_exploit.py

Expected: the script prints the contents of /tmp/victim_secret.txt (auto-created by the script if absent), proving the model-loading code read a file entirely outside the model directory β€” followed by the negative control demonstrating model_code_path is, by contrast, correctly guarded.

Contact

Filed as part of authorized security research on the huntr AI/ML bug bounty platform (Model File Format Vulnerability Form, mlflow target).

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support