GHSA-gqvg-gmmx-x4hm: MLflow: statsmodels flavor bypasses pickle RCE guard

GHSA-gqvg-gmmx-x4hm HIGH
Published September 1, 2026
CISO Take

MLflow's mlflow.statsmodels model flavor silently ignores the MLFLOW_ALLOW_PICKLE_DESERIALIZATION control that was specifically built to block unsafe pickle loading, so a malicious model artifact deserializes and executes arbitrary code the moment someone calls mlflow.pyfunc.load_model() on it — even with the safeguard explicitly enabled. This matters because MLflow sits at the center of most MLOps stacks (683 downstream dependents, an OpenSSF Scorecard of just 5.5/10, and 80 other CVEs already logged against the package), and on default deployments artifact upload requires no authentication, so anyone who can reach the tracking server or artifact store can plant the payload. There's no EPSS score, no CISA KEV listing, and no public exploit or Nuclei template yet, so this isn't an active-exploitation emergency, but CVSS 8.8 and a one-line root cause (a missing guard present in every other flavor) make it a trivial target for a fast weaponized PoC. Teams should upgrade to MLflow 3.15.0 immediately; until then, restrict artifact store write access and tracking server exposure, and treat MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False as insufficient protection on its own. Audit any model loaded via mlflow.pyfunc.load_model() with a statsmodels flavor from an untrusted registry or artifact path.

Sources: GitHub Advisory OpenSSF ATLAS

What is the risk?

High severity (CVSS 8.8) security control bypass rather than a novel vulnerability class — MLflow already ships a documented pickle-RCE mitigation, and this CVE shows the mitigation is incomplete. Exploitability is straightforward for anyone with artifact-store write access (no auth required by default), and constructing the payload (a hand-crafted MLmodel YAML plus a malicious pickle) is a well-documented pattern in ML security research, so a working exploit is low-effort to build even without public PoC code today. Impact is complete: arbitrary code execution with the privileges of the process loading the model — typically a training host, CI/CD runner, or model-serving instance. Blast radius is amplified by MLflow's ubiquity (683 dependents, a poor OpenSSF Scorecard of 5.5/10, 80 prior CVEs) and the false sense of security operators have because they explicitly set MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False expecting full coverage.

How does the attack unfold?

Staging
Attacker uploads a crafted MLmodel YAML (declaring mlflow.statsmodels as loader) plus a malicious model.pkl to an accessible MLflow artifact store, which requires no authentication by default.
AML.T0010.003
Trigger
A CI/CD pipeline, batch job, or engineer calls mlflow.pyfunc.load_model() against the malicious model version, believing MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False fully protects them.
Exploitation
The statsmodels flavor's _load_pyfunc skips the pickle guard entirely and calls pickle.load() directly, executing the attacker's payload.
AML.T0011.000
Impact
Arbitrary code runs with the privileges of the loading process, enabling credential theft, lateral movement, or persistence within the MLOps environment.
AML.T0018.002

What systems are affected?

Package Ecosystem Vulnerable Range Patched
MLflow pip >= 2.1.0, < 3.15.0 3.15.0
27.7K OpenSSF 5.5 683 dependents Pushed 2d ago 37% patched ~72d to patch Full package profile →

Do you use MLflow? You're affected.

How severe is it?

CVSS 3.1
8.8 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Moderate

What is the attack surface?

AV AC PR UI S C I A
AV Network
AC Low
PR None
UI Required
S Unchanged
C High
I High
A High

What should I do?

1 step
  1. 1) Upgrade to MLflow >= 3.15.0, which patches the missing guard in mlflow.statsmodels (GHSA-gqvg-gmmx-x4hm, PR #24686). 2) Until patched, do not rely on MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False alone — restrict write access to the artifact store and model registry to trusted CI/CD identities only, and enable MLflow's basic-auth (--app-name basic-auth) if not already running with authentication. 3) Audit existing registered models for statsmodels-flavored MLmodel files from unknown or external sources before loading them. 4) Detection: monitor for unexpected child processes or outbound connections spawned around mlflow.pyfunc.load_model() calls, and flag any MLmodel artifact whose loader_module is mlflow.statsmodels combined with an unrecognized statsmodels_version or an unexpected artifact-store path. 5) Network-isolate and least-privilege any host that calls load_model() against externally-writable artifact stores.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Article 15 - Accuracy, robustness and cybersecurity
ISO 42001
Annex A.6.2.3 - AI system security
NIST AI RMF
MEASURE 2.7 - AI system security and resilience are evaluated and documented
OWASP LLM Top 10
LLM05:2025 - Supply Chain Vulnerabilities

Frequently Asked Questions

What is GHSA-gqvg-gmmx-x4hm?

MLflow's mlflow.statsmodels model flavor silently ignores the MLFLOW_ALLOW_PICKLE_DESERIALIZATION control that was specifically built to block unsafe pickle loading, so a malicious model artifact deserializes and executes arbitrary code the moment someone calls mlflow.pyfunc.load_model() on it — even with the safeguard explicitly enabled. This matters because MLflow sits at the center of most MLOps stacks (683 downstream dependents, an OpenSSF Scorecard of just 5.5/10, and 80 other CVEs already logged against the package), and on default deployments artifact upload requires no authentication, so anyone who can reach the tracking server or artifact store can plant the payload. There's no EPSS score, no CISA KEV listing, and no public exploit or Nuclei template yet, so this isn't an active-exploitation emergency, but CVSS 8.8 and a one-line root cause (a missing guard present in every other flavor) make it a trivial target for a fast weaponized PoC. Teams should upgrade to MLflow 3.15.0 immediately; until then, restrict artifact store write access and tracking server exposure, and treat MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False as insufficient protection on its own. Audit any model loaded via mlflow.pyfunc.load_model() with a statsmodels flavor from an untrusted registry or artifact path.

Is GHSA-gqvg-gmmx-x4hm actively exploited?

No confirmed active exploitation of GHSA-gqvg-gmmx-x4hm has been reported, but organizations should still patch proactively.

How to fix GHSA-gqvg-gmmx-x4hm?

1) Upgrade to MLflow >= 3.15.0, which patches the missing guard in mlflow.statsmodels (GHSA-gqvg-gmmx-x4hm, PR #24686). 2) Until patched, do not rely on MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False alone — restrict write access to the artifact store and model registry to trusted CI/CD identities only, and enable MLflow's basic-auth (--app-name basic-auth) if not already running with authentication. 3) Audit existing registered models for statsmodels-flavored MLmodel files from unknown or external sources before loading them. 4) Detection: monitor for unexpected child processes or outbound connections spawned around mlflow.pyfunc.load_model() calls, and flag any MLmodel artifact whose loader_module is mlflow.statsmodels combined with an unrecognized statsmodels_version or an unexpected artifact-store path. 5) Network-isolate and least-privilege any host that calls load_model() against externally-writable artifact stores.

What systems are affected by GHSA-gqvg-gmmx-x4hm?

This vulnerability affects the following AI/ML architecture patterns: model serving, training pipelines, MLOps CI/CD pipelines.

What is the CVSS score for GHSA-gqvg-gmmx-x4hm?

GHSA-gqvg-gmmx-x4hm has a CVSS v3.1 base score of 8.8 (HIGH).

What is the AI security impact?

Affected AI Architectures

model servingtraining pipelinesMLOps CI/CD pipelines

MITRE ATLAS Techniques

AML.T0010.003 Model
AML.T0011.000 Unsafe AI Artifacts
AML.T0018.002 Embed Malware
AML.T0079 Stage Capabilities

Compliance Controls Affected

EU AI Act: Article 15
ISO 42001: Annex A.6.2.3
NIST AI RMF: MEASURE 2.7
OWASP LLM Top 10: LLM05:2025

What are the technical details?

Original Advisory

## 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) ```

Exploitation Scenario

A data science team runs a shared MLflow tracking server backed by an S3 bucket that any authenticated engineer — or, in a misconfigured deployment, any network-adjacent user — can write to. An attacker with write access to that artifact store registers a new model version named to look legitimate (e.g., 'fraud-detector-v3'), with an MLmodel file declaring mlflow.statsmodels as its pyfunc loader module and a model.pkl containing a malicious __reduce__ payload. A CI/CD retraining pipeline or scheduled batch job later calls mlflow.pyfunc.load_model('models:/fraud-detector-v3/Production') to promote or evaluate the model — even though the environment has MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False set precisely to prevent this class of attack. The statsmodels loader ignores the guard, pickle.load() executes the attacker's payload, and the attacker gains code execution with the privileges of the pipeline runner — potentially exfiltrating cloud credentials, pivoting to the training data store, or planting a backdoor in the next model artifact published.

Weaknesses (CWE)

CWE-502 — Deserialization of Untrusted Data: The product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid.

  • [Architecture and Design, Implementation] If available, use the signing/sealing features of the programming language to assure that deserialized data has not been tainted. For example, a hash-based message authentication code (HMAC) could be used to ensure that data has not been modified.
  • [Implementation] When deserializing data, populate a new object rather than just deserializing. The result is that the data flows through safe input validation and that the functions are safe.

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Timeline

Published
September 1, 2026
Last Modified
September 1, 2026
First Seen
September 1, 2026

Related Vulnerabilities