GHSA-gqvg-gmmx-x4hm: MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False safety control bypassed by mlflow.statsmodels flavor — RCE via crafted model artifact
Summary MLflow introduced MLFLOWALLOWPICKLEDESERIALIZATION as a security control to prevent unsafe pickle.load execution during model loading, in response to CVE-2024-37052 through CVE-2024-37060. When set to False, operators expect all pickle deserialization to be blocked. The most recent related fix (#21188) patched a bypass in the pyfunc flavor. However, the mlflow.statsmodels flavor completely omits this guard.
Published Sep 1, 2026
Affected packages
mlflow
- Ecosystem
- PyPI
- Affected
- >= 2.1.0, < 3.15.0
- Fixed in
- 3.15.0
- Check
pip show mlflow
Advisory details
From the GitHub Advisory Database (CC BY 4.0).
Summary
MLflow introduced MLFLOW_ALLOW_PICKLE_DESERIALIZATION as a security control to prevent unsafe pickle.load execution during model loading, in response to CVE-2024-37052 through CVE-2024-37060. When set to False, operators expect all pickle deserialization to be blocked. The most recent related fix (#21188) patched a bypass in the pyfunc flavor.
However, the mlflow.statsmodels flavor completely omits this guard. An attacker who places a crafted MLmodel artifact into any accessible artifact store can trigger arbitrary code execution on any process that calls mlflow.pyfunc.load_model() against the malicious model — even when `MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False`.
This is a security control bypass. The operator believes pickle RCE is mitigated; the statsmodels flavor silently ignores the control.
---
Root Cause
mlflow.pyfunc.load_model() dispatches to flavor _load_pyfunc implementations via:
# mlflow/pyfunc/__init__.py L1170-1172
model_impl = importlib.import_module(conf[MAIN])._load_pyfunc(data_path)The guarded pattern (from mlflow/sklearn/__init__.py L526-533, the reference implementation) is:
if (
not MLFLOW_ALLOW_PICKLE_DESERIALIZATION.get()
and not is_in_databricks_runtime()
and not is_in_databricks_model_serving_environment()
):
raise MlflowException("Deserializing model using pickle is disallowed...")mlflow/statsmodels/__init__.py has no such check:
# L307-320 — no guard anywhere in this file
def _load_model(path):
import statsmodels.iolib.api as smio
return smio.load_pickle(path) # calls pickle.load() directly
def _load_pyfunc(path):
return _StatsmodelsModelWrapper(_load_model(path))statsmodels.iolib.api.load_pickle is a thin wrapper around pickle.load. Its own docstring warns: *"Never unpickle data received from an untrusted or unauthenticated source."*
---
Trigger
An attacker crafts an MLmodel YAML that specifies mlflow.statsmodels as the loader module:
flavors:
python_function:
loader_module: mlflow.statsmodels
data: model.pkl
statsmodels:
data: model.pkl
statsmodels_version: 0.14.0With a malicious model.pkl placed alongside it in the artifact store, any call to:
os.environ["MLFLOW_ALLOW_PICKLE_DESERIALIZATION"] = "False"
mlflow.pyfunc.load_model("models:/MaliciousModel/1")...deserializes the pickle file with no guard check, executing arbitrary code with the privileges of the calling process.
On default MLflow deployments (no --app-name basic-auth), authentication is disabled, so artifact upload requires no credentials.
---
Affected Code
mlflow/statsmodels/__init__.pyL307-310:_load_model— callssmio.load_picklewithout checkingMLFLOW_ALLOW_PICKLE_DESERIALIZATIONmlflow/statsmodels/__init__.pyL313-320:_load_pyfunc— dispatches to_load_modelwithout checking the control
Permalink (commit 0b0c576c):
- https://github.com/mlflow/mlflow/blob/0b0c576c642b5b0d9496c829809c7d097403bc9f/mlflow/statsmodels/__init__.py#L307-L310
- https://github.com/mlflow/mlflow/blob/0b0c576c642b5b0d9496c829809c7d097403bc9f/mlflow/statsmodels/__init__.py#L313-L320
---
Recommended Fix
Add the missing guard to mlflow/statsmodels/__init__.py:
from mlflow.environment_variables import MLFLOW_ALLOW_PICKLE_DESERIALIZATION
from mlflow.utils.databricks_utils import (
is_in_databricks_model_serving_environment,
is_in_databricks_runtime,
)
def _load_model(path):
if (
not MLFLOW_ALLOW_PICKLE_DESERIALIZATION.get()
and not is_in_databricks_runtime()
and not is_in_databricks_model_serving_environment()
):
raise MlflowException(
"Deserializing model using pickle is disallowed, but this statsmodels "
"model requires pickle deserialization. Set environment variable "
"'MLFLOW_ALLOW_PICKLE_DESERIALIZATION' to 'true' to allow this."
)
import statsmodels.iolib.api as smio
return smio.load_pickle(path)Credited to prasanna8585.
References
How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. No analysis has been written for this entry yet. Methodology
More MLflow vulnerabilities
All MLflow entriesCVE-2026-64849
MLflow: Unauthenticated full-read SSRF in webhook delivery: _validate_webhook_url bypassed via unvalidated HTTP redirects (and DNS rebinding)
critical9.3ExploitedAug 17, 2026CVE-2026-71211
MLflow AI Gateway permits SSRF through an unvalidated api_base
high7.1No fixAug 5, 2026CVE-2026-8147
MLflow: trace API endpoints lack proper authorization validators
high8.1Jul 2, 2026CVE-2026-10803
MLflow: Deterministic sampling in dataset digest enables predictable collisions
low3.6Jun 4, 2026CVE-2026-4035
MLflow: Environment variable injection in AI Gateway secrets enables server-side credential exfiltration
critical9.1Jun 3, 2026CVE-2026-3198
MLflow: Any authenticated user can enumerate all gateway secrets, endpoints, and model definitions
medium6.5Jun 2, 2026
