GHSA-gqvg-gmmx-x4hm: MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False safety control bypassed by mlflow.statsmodels flavor — RCE via crafted model artifact
MLflow's `mlflow.statsmodels` flavor ignores the `MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False` control. A crafted model artifact makes `mlflow.pyfunc.load_model()` run arbitrary code through `pickle.load`, even with the control off. Versions 2.1.0 up to 3.15.0 are affected. Upgrade to 3.15.0.
Published Sep 1, 2026 · Analysis Oct 24, 2026
What an attacker can do
MLflow added MLFLOW_ALLOW_PICKLE_DESERIALIZATION after the CVE-2024-37052 through CVE-2024-37060 pickle flaws. The sklearn flavor checks it before loading. In mlflow/statsmodels/__init__.py, _load_model calls statsmodels.iolib.api.load_pickle, a thin wrapper over pickle.load, and _load_pyfunc calls it with no guard at all.
The attacker writes an MLmodel file whose python_function flavor sets loader_module: mlflow.statsmodels, and places a malicious model.pkl next to it in an artifact store the victim can read. When any process calls mlflow.pyfunc.load_model() on it, the pickle runs with that process's privileges. This includes a model-serving process or a data scientist's notebook.
Operators who set the variable to False believe pickle RCE is blocked, so this is a silent bypass. On default MLflow deployments, authentication is off (no --app-name basic-auth), so uploading an artifact needs no credentials. The CVSS vector shows user interaction: someone must load the model. The result is full code execution on the loading host, with access to its credentials, data and network.
Who is affected
The mlflow pip package from 2.1.0 up to but not including 3.15.0. The exposed case is any process that loads models with mlflow.pyfunc.load_model() from an artifact store or registry that others can write to, including when MLFLOW_ALLOW_PICKLE_DESERIALIZATION is False.
pip show mlflowCheck registered models for a loader_module of mlflow.statsmodels that you did not create.
mlflow
- Ecosystem
- PyPI
- Affected
- >= 2.1.0, < 3.15.0
- Fixed in
- 3.15.0
- Check
pip show mlflow
How to fix it
Upgrade to 3.15.0.
pip install --upgrade mlflowThe fix is in pull request 24686 (commit 3861528). It adds the same guard the sklearn flavor uses: refuse to load when MLFLOW_ALLOW_PICKLE_DESERIALIZATION is false and the code is not in a Databricks runtime or model-serving environment. The advisory lists no other workaround. Until you upgrade, limit write access to artifact stores and the model registry, and turn on MLflow authentication.
How to tell if you were hit
Inspect MLmodel files in your artifact store and registry for loader_module: mlflow.statsmodels on models nobody on your team trained, and for unexpected model.pkl files. On hosts that load models, look for child processes spawned by the Python process during load_model(), and for outbound connections at load time. Check artifact store access logs for uploads from unknown identities.
Original advisory
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.
Questions
- Does setting MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False protect me?
Not for the statsmodels flavor before 3.15.0. That flavor never checks the variable and calls
pickle.loaddirectly.- Does the attacker need credentials?
Not on a default deployment. Without
--app-name basic-auth, MLflow authentication is off, so uploading an artifact needs no login.- Which versions are affected?
2.1.0 up to but not including 3.15.0. The fix is in 3.15.0.
- Is there a CVE for this?
The data lists only the GHSA id, GHSA-gqvg-gmmx-x4hm. No CVE id is assigned in the advisory.
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. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology
More MLflow vulnerabilities
All MLflow entriesCVE-2026-115886
MLflow: SSRF on unauthenticated MLflow servers via third-party scorer kwargs in the scorer invoke, register, and GenAI evaluate endpoints
medium6.5Oct 23, 2026CVE-2026-53995
MLflow FileStore trace IDs allow path traversal and arbitrary file writes
high8.2Oct 20, 2026CVE-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, 2026
