Medium severity4.3NVD Advisory· Published Apr 7, 2026· Updated Apr 20, 2026
CVE-2026-33866
CVE-2026-33866
Description
MLflow is vulnerable to an authorization bypass affecting the AJAX endpoint used to download saved model artifacts. Due to missing access‑control validation, a user without permissions to a given experiment can directly query this endpoint and retrieve model artifacts they are not authorized to access.
This issue affects MLflow version through 3.10.1
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
mlflowPyPI | <= 3.10.1 | — |
Affected products
1Patches
1005b959cacdaEnforce auth on logged model artifact download AJAX endpoint (#21708)
2 files changed · +95 −1
mlflow/server/auth/__init__.py+11 −0 modified@@ -121,6 +121,7 @@ GetWorkspace, ListArtifacts, ListGatewayEndpointBindings, + ListLoggedModelArtifacts, ListScorers, ListScorerVersions, ListWorkspaces, @@ -237,6 +238,7 @@ from mlflow.server.fastapi_app import create_fastapi_app from mlflow.server.handlers import ( _disable_if_workspaces_disabled, + _get_ajax_path, _get_model_registry_store, _get_request_message, _get_tracking_store, @@ -1652,6 +1654,7 @@ def _re_compile_path(path: str) -> re.Pattern: FinalizeLoggedModel: validate_can_update_logged_model, DeleteLoggedModelTag: validate_can_delete_logged_model, SetLoggedModelTags: validate_can_update_logged_model, + ListLoggedModelArtifacts: validate_can_read_logged_model, LogLoggedModelParamsRequest: validate_can_update_logged_model, } @@ -1666,6 +1669,14 @@ def get_logged_model_before_request_handler(request_class): for http_path, handler, methods in get_endpoints(get_logged_model_before_request_handler) for method in methods } +# The AJAX artifact download endpoint is a plain Flask route with a path parameter, so it +# can't go in routes.py/BEFORE_REQUEST_VALIDATORS (exact match) and must be added here. +LOGGED_MODEL_BEFORE_REQUEST_VALIDATORS[ + ( + _re_compile_path(_get_ajax_path("/mlflow/logged-models/<model_id>/artifacts/files")), + "GET", + ) +] = validate_can_read_logged_model WEBHOOK_BEFORE_REQUEST_HANDLERS = { CreateWebhook: sender_is_admin,
tests/server/auth/test_auth.py+84 −1 modified@@ -32,14 +32,15 @@ ErrorCode, ) from mlflow.server import auth as auth_module -from mlflow.server.auth import _authenticate_fastapi_request +from mlflow.server.auth import _authenticate_fastapi_request, _re_compile_path from mlflow.server.auth.routes import ( AJAX_LIST_USERS, CREATE_REGISTERED_MODEL_PERMISSION, GET_REGISTERED_MODEL_PERMISSION, GET_SCORER_PERMISSION, LIST_USERS, ) +from mlflow.server.handlers import STATIC_PREFIX_ENV_VAR, _get_ajax_path from mlflow.utils.os import is_windows from mlflow.utils.workspace_utils import DEFAULT_WORKSPACE_NAME @@ -783,6 +784,88 @@ def predict(self, context, model_input): client.delete_logged_model(model_id=model.model_id) +@pytest.mark.parametrize( + "client", + [{"MLFLOW_AUTH_CONFIG_PATH": "tests/server/auth/fixtures/no_permission_auth.ini"}], + indirect=True, +) +def test_logged_model_artifact_authorization(client: MlflowClient, monkeypatch: pytest.MonkeyPatch): + username1, password1 = create_user(client.tracking_uri) + username2, password2 = create_user(client.tracking_uri) + + with User(username1, password1, monkeypatch): + exp_id = client.create_experiment("logged-model-artifact-authz-test") + model = client.create_logged_model(experiment_id=exp_id) + + # user1 (owner) should be able to access the artifact endpoint (404 since no artifact + # exists, but should NOT be 403) + response = requests.get( + url=( + client.tracking_uri + + f"/ajax-api/2.0/mlflow/logged-models/{model.model_id}/artifacts/files" + ), + params={"artifact_file_path": "test.txt"}, + auth=(username1, password1), + ) + assert response.status_code != 403 + + # user2 has no permission on the experiment — expect 403 + response = requests.get( + url=( + client.tracking_uri + + f"/ajax-api/2.0/mlflow/logged-models/{model.model_id}/artifacts/files" + ), + params={"artifact_file_path": "test.txt"}, + auth=(username2, password2), + ) + assert response.status_code == 403 + + # Also verify the list-artifacts (directories) endpoint + # user1 (owner) should be able to list artifacts + response = requests.get( + url=( + client.tracking_uri + + f"/api/2.0/mlflow/logged-models/{model.model_id}/artifacts/directories" + ), + auth=(username1, password1), + ) + assert response.status_code != 403 + + # user2 has no permission — expect 403 + response = requests.get( + url=( + client.tracking_uri + + f"/api/2.0/mlflow/logged-models/{model.model_id}/artifacts/directories" + ), + auth=(username2, password2), + ) + assert response.status_code == 403 + + +def test_logged_model_artifact_validator_respects_static_prefix( + monkeypatch: pytest.MonkeyPatch, +): + base = "/mlflow/logged-models/<model_id>/artifacts/files" + + # Without prefix — should match the bare path + pat_no_prefix = _re_compile_path(_get_ajax_path(base)) + assert pat_no_prefix.fullmatch("/ajax-api/2.0/mlflow/logged-models/abc123/artifacts/files") + + # With prefix — should match the prefixed path + monkeypatch.setenv(STATIC_PREFIX_ENV_VAR, "/custom-prefix") + _re_compile_path.cache_clear() + pat_with_prefix = _re_compile_path(_get_ajax_path(base)) + assert pat_with_prefix.fullmatch( + "/custom-prefix/ajax-api/2.0/mlflow/logged-models/abc123/artifacts/files" + ) + # bare path should NOT match the prefixed pattern + assert not pat_with_prefix.fullmatch( + "/ajax-api/2.0/mlflow/logged-models/abc123/artifacts/files" + ) + + _re_compile_path.cache_clear() + + def test_search_logged_models(client: MlflowClient, monkeypatch: pytest.MonkeyPatch): username1, password1 = create_user(client.tracking_uri) username2, password2 = create_user(client.tracking_uri)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/mlflow/mlflow/pull/21708nvdIssue TrackingPatchWEB
- afine.com/blogs/attacking-mlflow-how-ml-artifacts-become-attack-vectorsnvdExploitThird Party AdvisoryWEB
- cert.pl/en/posts/2026/04/CVE-2026-33865/nvdThird Party Advisory
- github.com/advisories/GHSA-46r5-x6jq-v8g6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-33866ghsaADVISORY
- cert.pl/en/posts/2026/04/CVE-2026-33865ghsaWEB
- github.com/mlflow/mlflow/commit/005b959cacda05d1423356cfcbd9ebeda8ff96a7ghsaWEB
News mentions
0No linked articles in our index yet.