SA-26-559 High

MLflow: MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False safety control bypassed by mlflow.statsmodels flavor…

MLflow

Severity
High, CVSS 8.8
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Record
GHSA-gqvg-gmmx-x4hm
Affects
MLflow
Known exploited
Not listed in the CISA Known Exploited Vulnerabilities catalog
Published
Record first published by the source
September 1, 2026
Source last modified
September 1, 2026

Description

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.0

With 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__.py L307-310: _load_model — calls smio.load_pickle without checking MLFLOW_ALLOW_PICKLE_DESERIALIZATION - mlflow/statsmodels/__init__.py L313-320: _load_pyfunc — dispatches to _load_model without 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)

Description quoted from the GitHub Advisory Database.

Who is affected

MLflow, made by LF Projects, is a platform for building, tracking and running machine learning models. This flaw is in the part of MLflow that loads models made with statsmodels, a statistics package. No affected versions are named for this flaw. Someone who can place a specially crafted model in the storage area MLflow reads models from can run any code on any computer that loads that model. This works even when the safety setting meant to block unsafe model loading is turned on. On a default MLflow setup without the basic login feature, no password is needed to upload such a model. This flaw is rated High.

What to do

  1. Find out who installed or runs MLflow for your organization: a staff member, or an IT provider or vendor who manages it for you.
  2. If an IT provider or vendor runs MLflow for you, send them this advisory and ask when the update will be done.
  3. No fixed version is named for this flaw yet. The person who runs MLflow should check the vendor advisory linked on this page for a fixed release and install it as soon as one is available.
  4. Until a fix is installed, do not count on the setting that blocks unsafe model loading to protect you from statsmodels models, because this flaw lets those models get past it.

Analysis written from the source record as of September 1, 2026 and reviewed before publishing.

Sources

MORE ADVISORIES

See all