AI and data stack advisories

Severe, 6 weeks486Projects324

486 severe, 6 weeks · 324 projects

MLflowGHSA-m65p-9j7q-6w33

MLflow vulnerable to arbitrary file read on unauthenticated servers via prompt model version source

MLflow

CVE-2026-116690 · Published Oct 27, 2026

High7.5
Fix: upgrade to 3.17.0 or later
GitHub advisory

Summary

On MLflow tracking servers running without authentication, which is the default for mlflow server, a remote attacker can read arbitrary files from the server’s filesystem by creating a model version tagged as a prompt whose source is a schemeless local path.

The fix for CVE-2026-2614 in 3.10.0 added a prompt-specific branch to the CreateModelVersion handler that rejects file:// URIs and absolute paths but runs path validation only when the source carries a URL scheme. A schemeless value such as ../../etc, a plain relative name, ., a percent-encoded path, or a Windows drive-letter path is therefore stored unchanged as the model version’s artifact location, and the model-versions/get-artifact endpoint later serves files from it with the privileges of the server process. The tag check also keyed on the presence of the mlflow.prompt.is_prompt tag rather than its value, so a false value still selected the weaker branch.

Servers using --app-name basic-auth are also affected: any authenticated user can create a registered model, so a low-privilege account can use this to read the authentication database, including the admin password hash.

Affected: MLflow 3.10.0 through 3.16.1. Fixed in 3.17.0 by [mlflow/mlflow#25870](https://github.com/mlflow/mlflow/pull/25870), which accepts only MLflow’s own placeholder sources for prompt versions and requires the...

Affected versions

PackageAffectedFixed in
mlflow
PyPI
>= 3.10.0, < 3.17.03.17.0
Details and references

### Summary On MLflow tracking servers running without authentication, which is the default for mlflow server, a remote attacker can read arbitrary files from the server’s filesystem by creating a model version tagged as a prompt whose source is a schemeless local path. The fix for CVE-2026-2614 in 3.10.0 added a prompt-specific branch to the `CreateModelVersion` handler that rejects `file://` URIs and absolute paths but runs path validation only when the source carries a URL scheme. A schemeless value such as `../../etc`, a plain relative name, `.`, a percent-encoded path, or a Windows drive-letter path is therefore stored unchanged as the model version’s artifact location, and the `model-versions/get-artifact endpoint` later serves files from it with the privileges of the server process. The tag check also keyed on the presence of the `mlflow.prompt.is_prompt` tag rather than its value, so a false value still selected the weaker branch. Servers using `--app-name basic-auth` are also affected: any authenticated user can create a registered model, so a low-privilege account can use this to read the authentication database, including the admin password hash. Affected: MLflow 3.10.0 through 3.16.1. Fixed in 3.17.0 by [mlflow/mlflow#25870](https://github.com/mlflow/mlflow/pull/25870), which accepts only MLflow’s own placeholder sources for prompt versions and requires the prompt tag to be `true`. ### Details When creating a model version with the `mlflow.prompt.is_prompt` tag set to true, the request is handled by the prompt branch in `_create_model_version()`. `mlflow/server/handlers.py:2674-2690`: ```python is_prompt = _is_prompt_request(request_message) if is_prompt: source = request_message.source parsed = urllib.parse.urlparse(source) if parsed.scheme == "file" or (parsed.scheme == "" and source.startswith("/")): raise MlflowException(...) if parsed.scheme: _validate_non_local_source_contains_relative_paths(source) else: if request_message.model_id: _validate_source_model(request_message.source, request_message.model_id) else: _validate_source_run(request_message.source, request_message.run_id) ``` The prompt branch does not call `_validate_source_run()`, `_validate_source_model()`, or `_validate_non_local_source_contains_relative_paths()` for schemeless relative paths. The artifact-read sink later trusts the stored source: `mlflow/server/handlers.py:2765-2782`: ```python def get_model_version_artifact_handler(): name = request.args.get("name") version = request.args.get("version") path = request.args["path"] path = validate_path_is_safe(path) artifact_uri = _get_model_registry_store().get_model_version_download_uri(name, version) ... else: artifact_repo = get_artifact_repository(artifact_uri) artifact_path = path return _send_artifact(artifact_repo, artifact_path) ``` `_send_artifact()` calls `artifact_repository.download_artifacts(path)` and serves the resulting local file with `send_file()`. ### PoC Prerequisite: A reachable MLflow tracking server with model registry endpoints enabled. 1. Create a sentinel file on the server: ```bash printf 'MLFLOW_PWNED_SENTINEL_REPORT' > /tmp/sentinel-mlflow.txt ``` 2. Send the following requests to the MLflow server to exploit the traversal: First, create a registered model: ```http POST /api/2.0/mlflow/registered-models/create HTTP/1.1 Host: 127.0.0.1:5000 Content-Type: application/json {"name": "pwned-model"} ``` Next, create a prompt-tagged model version with the malicious relative source `../../../tmp`: ```http POST /api/2.0/mlflow/model-versions/create HTTP/1.1 Host: 127.0.0.1:5000 Content-Type: application/json { "name": "pwned-model", "source": "../../../tmp", "tags": [{"key": "mlflow.prompt.is_prompt", "value": "true"}] } ``` Finally, retrieve the arbitrary file via the artifact download endpoint by supplying the target filename in the `path` parameter:

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Severity from
GitHub (reviewed advisory)
Weakness
CWE-22
Also known as
CVE-2026-116690

More MLflow advisories

All MLflow