The archive/CVE-2024-37052
mlflow/mlflowDisclosed 2024

CVE-2024-37052:mlflow: Deserialization of untrusted data

A reconstruction of the flaw, and the evidence path Lachesis follows to make it visible.

Historical recordv2.13.2 (affected: >= 1.1.0, <= 2.14.1; MLflow issue #12256 states >= 2.0.0rc0, <= 2.13.1)
WeaknessCWE-502 · Deserialization of untrusted data
ImpactA deserialization of untrusted data in mlflow, reachable from attacker-controlled input.
Read the reconstruction
01What happened

A crafted scikit-learn model artifact (malicious pickle) loaded via mlflow.sklearn.load_model on a victim machine

That is the shape of the failure. The rest of this case file follows the evidence behind it.

Separate the record from the run

What was known.
What Lachesis established.

01Historical record

Known before Lachesis ran

The CVE and vulnerable release were selected from public history. We are not claiming novel discovery.

CVE
CVE-2024-37052
Vulnerable target
v2.13.2 (affected: >= 1.1.0, <= 2.14.1; MLflow issue #12256 states >= 2.0.0rc0, <= 2.13.1) · vulnerable
Prior knowledge
CVE-2024-37052 · v2.13.2 (affected: >= 1.1.0, <= 2.14.1; MLflow issue #12256 states >= 2.0.0rc0, <= 2.13.1) is vulnerable
then
02Lachesis run
Verified captured run

Independent re-detection

The experiment graph was built around mlflow/models at the vulnerable release v2.13.2, not pointed at the CVE. Enumeration ran over the whole candidate registry (every family, no family hardcoded) with the pass-3 ranker ordering leads; the object-integrity deserialize family fired on its own alongside filesystem, lifecycle, and navigation families, surfacing 30 deserialize-trust obligations. Everything under sast_output is the enumerator's own emission (candidate capsule plus the sources_of value-flow cone); everything under adjudication is my reading of the source and the official advisory/fix and is labelled as such. Key honesty point: CVE-2024-37052 is the scikit-learn flavor pickle load in mlflow/sklearn/__init__.py (_load_model_from_local_file -> pickle/cloudpickle.load), which was NOT inside the mlflow/models build scope. The tool anchored the top deserialize candidate at mlflow/models/resources.py:154 -- a builtin open() inside from_yaml_file that Atropos bound to the shelve.open deserialization model. That is a same-family look-alike near the model-loading machinery, not the exact CVE-37052 load path.

Seeded inputs
None
Run timestamp
2026-09-02T13:30:20Z
Evidence artifact
mlflow_models.kuzu

The commands and outputs below come from this recorded Lachesis run.

The reconstruction, in one frame

What Lachesis reconstructed.

One historical repository. Four captured queries. A complete source-to-sink argument.

Lachesis Casefiles · Unbound ComputeVerified runCVE-2024-37052
A crafted scikit-learn model artifact (malicious pickle) loaded via mlflow.sklearn.load_model on a victim machine
01Sourcedef from_yaml_file(path: str) -> ...:
02Sinkwith open(path) as file:
03Missing guardpickle.load(f) / cloudpickle.load(f) # no trust boundary on model loading
$ lachesis sources_of → reaches → guard_dominance
mlflow/mlflow · v2.13.2 (affected: >= 1.1.0, <= 2.14.1; MLflow issue #12256 states >= 2.0.0rc0, <= 2.13.1) · vulnerableunboundcompute.com
This was known history. Your code is not.

Lachesis follows the same evidence path through unfamiliar repositories.

The Lachesis reconstruction

Here is how the path becomes visible.

These are the recorded questions Lachesis asked of the historical vulnerable code, followed by the raw result and source location each query returned.

Repositorymlflow/mlflow
Revisionv2.13.2 (affected: >= 1.1.0, <= 2.14.1; MLflow issue #12256 states >= 2.0.0rc0, <= 2.13.1) · vulnerable
EngineLachesis

Captured runA real replay of Lachesis over the graph built for this case at v2.13.2. Every command was run against the mlflow_models.kuzu graph and every result is captured output. The tool was not pointed at a file or a family; the hunt listed the whole taxonomy first and the object-integrity deserialize family came out of that with 30 obligations. No model artifact or payload was seeded; the finding was discovered from graph structure, so the mode is independent-redetection. Elapsed 45.6s.

01 · load_graph01 / 05
Lachesis ran
load_graph --path mlflow_models.kuzu --profile all && candidate_census

Load the graph and list every bug family

First we load the graph and list every bug family the tool knows, nothing chosen ahead of time. Object integrity (deserialization) is one row among eight domains -- not something we aimed at.

Captured resultCaptured · load_graph
raw captured outputload_graph
[lachesis-mcp] loaded the graph (13732 nodes, 40209 edges); taxonomy: 8 domains, 31 constructors; enumeration complete_for_observable_graph=True. object-integrity is one domain among eight.
mlflow/models/resources.pyline 0
Code location returned for this evidence step.
The repair

A small line with a large consequence.

untrusted model artifacts are unpickled with no allow-list/signature/opt-in check, so a malicious model executes code on load.

Before
pickle.load(f) / cloudpickle.load(f)  # no trust boundary on model loading
After the patch
// 2.14.2 gates pickle-based model deserialization behind MLFLOW_ALLOW_PICKLE_DESERIALIZATION (default off).
Sources & further reading

The original record.

Read the historical advisory and vulnerability record behind this reconstruction.

Open the Lachesis quickstart