GHSA-gqvg-gmmx-x4hm: Mlflow Remote Code Execution Vulnerability
HIGHGHSA-gqvg-gmmx-x4hm is a HIGH (CVSS 8.8) remote code execution vulnerability (CWE-502: Deserialization of Untrusted Data) affecting Mlflow libraries in the PyPI ecosystem. The issue involves mlflow_allow_pickle_deserialization=false safety control bypassed by mlflow.statsmodels flavor — rce via crafted model artifact. Remediation is available by upgrading affected packages to version 3.15.0.
GHSA-gqvg-gmmx-x4hm at a Glance
Security Impact
CVSS: 8.8
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H).
How to Fix GHSA-gqvg-gmmx-x4hm
To remediate this vulnerability, upgrade all affected Mlflow dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install mlflow>=3.15.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show mlflow
How to Check if You Are Affected
To determine whether your Python environment is affected by GHSA-gqvg-gmmx-x4hm, inspect your installed packages using pip:
pip show mlflow
Analyze the terminal output to locate any version of Mlflow in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 3.15.0), your application is affected.
Note: The pip show mlflow command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-502: Deserialization of Untrusted Data, where the product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid. In Mlflow, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-gqvg-gmmx-x4hm Affected Mlflow Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
mlflow
|
PyPI |
≥ 2.1.0
< 3.15.0
|
3.15.0
|
References
Frequently Asked Questions
What is GHSA-gqvg-gmmx-x4hm?
Which Mlflow versions are affected?
What version fixes GHSA-gqvg-gmmx-x4hm?
Is mlflow affected?
What is the CVSS score of GHSA-gqvg-gmmx-x4hm?
What CWE is associated with GHSA-gqvg-gmmx-x4hm?
How can I check PyPI dependencies for GHSA-gqvg-gmmx-x4hm?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-gqvg-gmmx-x4hm",
"modified" : "2026-09-01T17:04:30Z",
"published" : "2026-09-01T17:04:30Z",
"aliases" : [ ],
"summary" : "MLFLOW_ALLOW_PICKLE_DESERIALIZATION=False safety control bypassed by mlflow.statsmodels flavor — RCE via crafted model artifact",
"details" : "## Summary\n\nMLflow 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.\n\nHowever, 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`**.\n\nThis is a security control bypass. The operator believes pickle RCE is mitigated; the statsmodels flavor silently ignores the control.\n\n---\n\n## Root Cause\n\n`mlflow.pyfunc.load_model()` dispatches to flavor `_load_pyfunc` implementations via:\n\n```\n# mlflow/pyfunc/__init__.py L1170-1172\nmodel_impl = importlib.import_module(conf[MAIN])._load_pyfunc(data_path)\n```\n\nThe guarded pattern (from `mlflow/sklearn/__init__.py` L526-533, the reference implementation) is:\n\n```\nif (\n not MLFLOW_ALLOW_PICKLE_DESERIALIZATION.get()\n and not is_in_databricks_runtime()\n and not is_in_databricks_model_serving_environment()\n):\n raise MlflowException(\"Deserializing model using pickle is disallowed...\")\n```\n\n`mlflow/statsmodels/__init__.py` has **no such check**:\n\n```\n# L307-320 — no guard anywhere in this file\ndef _load_model(path):\n import statsmodels.iolib.api as smio\n return smio.load_pickle(path) # calls pickle.load() directly\n\ndef _load_pyfunc(path):\n return _StatsmodelsModelWrapper(_load_model(path))\n```\n\n`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.\"*\n\n---\n\n## Trigger\n\nAn attacker crafts an MLmodel YAML that specifies `mlflow.statsmodels` as the loader module:\n\n```\nflavors:\n python_function:\n loader_module: mlflow.statsmodels\n data: model.pkl\n statsmodels:\n data: model.pkl\n statsmodels_version: 0.14.0\n```\n\nWith a malicious `model.pkl` placed alongside it in the artifact store, any call to:\n\n```\nos.environ[\"MLFLOW_ALLOW_PICKLE_DESERIALIZATION\"] = \"False\"\nmlflow.pyfunc.load_model(\"models:/MaliciousModel/1\")\n```\n\n...deserializes the pickle file with **no guard check**, executing arbitrary code with the privileges of the calling process.\n\nOn default MLflow deployments (no `--app-name basic-auth`), authentication is disabled, so artifact upload requires no credentials.\n\n---\n\n## Affected Code\n\n- `mlflow/statsmodels/__init__.py` L307-310: `_load_model` — calls `smio.load_pickle` without checking `MLFLOW_ALLOW_PICKLE_DESERIALIZATION`\n- `mlflow/statsmodels/__init__.py` L313-320: `_load_pyfunc` — dispatches to `_load_model` without checking the control\n\nPermalink (commit `0b0c576c`):\n- https://github.com/mlflow/mlflow/blob/0b0c576c642b5b0d9496c829809c7d097403bc9f/mlflow/statsmodels/__init__.py#L307-L310\n- https://github.com/mlflow/mlflow/blob/0b0c576c642b5b0d9496c829809c7d097403bc9f/mlflow/statsmodels/__init__.py#L313-L320\n\n---\n\n## Recommended Fix\n\nAdd the missing guard to `mlflow/statsmodels/__init__.py`:\n\n```\nfrom mlflow.environment_variables import MLFLOW_ALLOW_PICKLE_DESERIALIZATION\nfrom mlflow.utils.databricks_utils import (\n is_in_databricks_model_serving_environment,\n is_in_databricks_runtime,\n)\n\ndef _load_model(path):\n if (\n not MLFLOW_ALLOW_PICKLE_DESERIALIZATION.get()\n and not is_in_databricks_runtime()\n and not is_in_databricks_model_serving_environment()\n ):\n raise MlflowException(\n \"Deserializing model using pickle is disallowed, but this statsmodels \"\n \"model requires pickle deserialization. Set environment variable \"\n \"'MLFLOW_ALLOW_PICKLE_DESERIALIZATION' to 'true' to allow this.\"\n )\n import statsmodels.iolib.api as smio\n return smio.load_pickle(path)\n```",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "mlflow"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "2.1.0"
}, {
"fixed" : "3.15.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/mlflow/mlflow/security/advisories/GHSA-gqvg-gmmx-x4hm"
}, {
"type" : "WEB",
"url" : "https://github.com/mlflow/mlflow/pull/24686"
}, {
"type" : "WEB",
"url" : "https://github.com/mlflow/mlflow/commit/38615289094a4b700a20b5d1dbfbe57bdfb0411f"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/mlflow/mlflow"
}, {
"type" : "WEB",
"url" : "https://github.com/mlflow/mlflow/releases/tag/v3.15.0"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-502" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-09-01T17:04:30Z",
"nvd_published_at" : null
}
}