CVE-2026-116692
MLflow: Remote code execution on unauthenticated MLflow servers via third-party scorer deserialization importing caller-placed artifact modules
MLflow is an open source AI engineering platform for agents, large language models, and machine learning models. From 3.12.0rc0 until 3.17.0, third-party scorer deserialization in mlflow/genai/scorers/base.py accepts request-selected descendants of THIRD_PARTY_SCORER_ALLOWED_MODULES rather than requiring an exact allowed module. A caller can use MLflow artifact endpoints to place a Python module beneath a writable allowed package and submit its module and class through /ajax-api/3.0/mlflow/scorer/invoke. Scorer.model_validate_json() imports the module and constructs the class before trace processing. Authentication-disabled deployments permit this code execution without credentials, while basic-auth deployments require a principal capable of creating an experiment and run and uploading artifacts. In either configuration, the allowed scorer package must be writable by the server process. This issue is fixed in version 3.17.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.8
MLflow: Remote code execution on unauthenticated MLflow servers via third-party scorer deserialization importing caller-placed artifact modules
MLflow is an open source AI engineering platform for agents, large language models, and machine learning models. From 3.12.0rc0 until 3.17.0, third-party scorer deserialization in mlflow/genai/scorers/base.py accepts request-selected descendants of THIRD_PARTY_SCORER_ALLOWED_MODULES rather than requiring an exact allowed module. A caller can use MLflow artifact endpoints to place a Python module beneath a writable allowed package and submit its module and class through /ajax-api/3.0/mlflow/scorer/invoke. Scorer.model_validate_json() imports the module and constructs the class before trace processing. Authentication-disabled deployments permit this code execution without credentials, while basic-auth deployments require a principal capable of creating an experiment and run and uploading artifacts. In either configuration, the allowed scorer package must be writable by the server process. This issue is fixed in version 3.17.0.
- Record
CVE-2026-116692- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-94, CWE-470
- Updated
- 2026-11-02
EUVDEUVD-2026-111383
9.8
MLflow is an open source AI engineering platform for agents, large language models, and machine learning models. From 3.12.0rc0 until 3.17.0, third-party scorer deserialization in mlflow/genai/scorers/base.py accepts request-selected descendants of THIRD_PARTY_SCORER_ALLOWED_MODULES rather than requiring an exact allowed module. A caller can use MLflow artifact endpoints to place a Python module beneath a writable allowed package and submit its module and class through /ajax-api/3.0/mlflow/scorer/invoke. Scorer.model_validate_json() imports the module and constructs the class before trace processing. Authentication-disabled deployments permit this code execution without credentials, while basic-auth deployments require a principal capable of creating an experiment and run and uploading artifacts. In either configuration, the allowed scorer package must be writable by the server process. This issue is fixed in version 3.17.0.
- Record
EUVD-2026-111383- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-03
GitHub AdvisoriesGHSA-26p8-2jq9-3vq9
9.8
MLflow vulnerable to remote code execution on unauthenticated servers via third-party scorer deserialization importing caller-placed artifact modules
Summary
MLflow 3.12.0 introduced registration and deserialization support for third-party scorers in OSS MLflow. During scorer deserialization, the server accepts a request-controlled Python module path beneath one of four allowed package prefixes, imports that module, obtains a request-controlled class, and instantiates it synchronously.
An HTTP caller can combine this behavior with MLflow's built-in experiment, run, and artifact-upload endpoints:
- Create an experiment whose local artifact location points to a writable allowed scorer package.
- Create a run, causing MLflow to allocate
<artifact_location>/<run_id>/artifacts/. - Upload a Python module into that run artifact directory.
- Submit a serialized third-party scorer whose module path resolves to the uploaded file.
- Trigger server-side import and class construction through
/ajax-api/3.0/mlflow/scorer/invoke.
On the default no-auth server using the official MLflow v3.14.0 image, this produces unauthenticated arbitrary Python code execution with the privileges of the MLflow server process. The reproduced image runs the process as uid 0 and permits writes to its installed Python package tree.
The attached proof of concept is intentionally harmless. The uploaded constructor performs one action only: it writes a random marker beside its own uploaded run artifact. The marker is then downloaded through MLflow's run-artifact endpoint and checked for an exact match.
Technical details
1. The allow-list accepts attacker-created descendants
At release commit 86cd7f5a1dc25a1387ad07c87811bbc30f62951c, mlflow/genai/scorers/scorer_utils.py defines these allowed roots:
THIRD_PARTY_SCORER_ALLOWED_MODULES = frozenset({
"mlflow.genai.scorers.ragas",
"mlflow.genai.scorers.deepeval",
"mlflow.genai.scorers.trulens",
"mlflow.genai.scorers.phoenix",
})
In mlflow/genai/scorers/base.py, the deserializer accepts either an exact root or any descendant:
module_path == m or module_path.startswith(m + ".")
It then performs the following operations using request-controlled values:
module = importlib.import_module(module_path)
scorer_class = getattr(module, class_name, None)
scorer_instance = scorer_class(**init_kwargs)
A package-name prefix is not a provenance boundary. If the server process can write below an allowed package, an attacker-created descendant can satisfy the string-prefix check and become importable Python code.
Relevant source locations at the v3.14.0 release commit:
mlflow/genai/scorers/scorer_utils.py, allowed third-party scorer modulesmlflow/genai/scorers/base.py, third-party scorer deserialization and construction
2. Deserialization occurs before trace processing
In mlflow/server/handlers.py, _invoke_scorer_handler() parses the supplied serialized scorer and calls:
scorer = Scorer.model_validate_json(serialized_scorer)
Only after deserialization and construction does the handler call:
batches = get_trace_batches_for_scorer(trace_ids, scorer, tracking_store)
The attacker-controlled constructor therefore runs before the server validates or resolves the supplied trace IDs.
The minimal proof class is deliberately not a complete MLflow scorer. After its constructor writes the marker, later trace-batching logic accesses scorer attributes that the proof class does not implement. The resulting HTTP 500 occurs after code execution and does not invalidate the proof.
No background job execution or special job-execution server option is required to reach the constructor.
3. MLflow's artifact layout places the module under the trusted prefix
The experiment creation endpoint accepts an artifact_location. For file-backed experiments, MLflow derives each run artifact directory as:
<artifact_location>/<run_id>/artifacts/
The proof of concept selects this package directory in the official v3.14.0 image:
- Record
GHSA-26p8-2jq9-3vq9- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-94, CWE-470
- Updated
- 2026-11-02
NVD
9.8
MLflow is an open source AI engineering platform for agents, large language models, and machine learning models. From 3.12.0rc0 until 3.17.0, third-party scorer deserialization in mlflow/genai/scorers/base.py accepts request-selected descendants of THIRD_PARTY_SCORER_ALLOWED_MODULES rather than requiring an exact allowed module. A caller can use MLflow artifact endpoints to place a Python module beneath a writable allowed package and submit its module and class through /ajax-api/3.0/mlflow/scorer/invoke. Scorer.model_validate_json() imports the module and constructs the class before trace processing. Authentication-disabled deployments permit this code execution without credentials, while basic-auth deployments require a principal capable of creating an experiment and run and uploading artifacts. In either configuration, the allowed scorer package must be writable by the server process. This issue is fixed in version 3.17.0.
- Record
CVE-2026-116692- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-94, CWE-470
- Updated
- 2026-11-03
OSV
9.8
MLflow: Remote code execution on unauthenticated MLflow servers via third-party scorer deserialization importing caller-placed artifact modules
MLflow is an open source AI engineering platform for agents, large language models, and machine learning models. From 3.12.0rc0 until 3.17.0, third-party scorer deserialization in mlflow/genai/scorers/base.py accepts request-selected descendants of THIRD_PARTY_SCORER_ALLOWED_MODULES rather than requiring an exact allowed module. A caller can use MLflow artifact endpoints to place a Python module beneath a writable allowed package and submit its module and class through /ajax-api/3.0/mlflow/scorer/invoke. Scorer.model_validate_json() imports the module and constructs the class before trace processing. Authentication-disabled deployments permit this code execution without credentials, while basic-auth deployments require a principal capable of creating an experiment and run and uploading artifacts. In either configuration, the allowed scorer package must be writable by the server process. This issue is fixed in version 3.17.0.
- Record
CVE-2026-116692- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-470, CWE-94
- Updated
- 2026-11-04
OSVGHSA-26p8-2jq9-3vq9
9.8
MLflow vulnerable to remote code execution on unauthenticated servers via third-party scorer deserialization importing caller-placed artifact modules
Summary
MLflow 3.12.0 introduced registration and deserialization support for third-party scorers in OSS MLflow. During scorer deserialization, the server accepts a request-controlled Python module path beneath one of four allowed package prefixes, imports that module, obtains a request-controlled class, and instantiates it synchronously.
An HTTP caller can combine this behavior with MLflow's built-in experiment, run, and artifact-upload endpoints:
- Create an experiment whose local artifact location points to a writable allowed scorer package.
- Create a run, causing MLflow to allocate
<artifact_location>/<run_id>/artifacts/. - Upload a Python module into that run artifact directory.
- Submit a serialized third-party scorer whose module path resolves to the uploaded file.
- Trigger server-side import and class construction through
/ajax-api/3.0/mlflow/scorer/invoke.
On the default no-auth server using the official MLflow v3.14.0 image, this produces unauthenticated arbitrary Python code execution with the privileges of the MLflow server process. The reproduced image runs the process as uid 0 and permits writes to its installed Python package tree.
The attached proof of concept is intentionally harmless. The uploaded constructor performs one action only: it writes a random marker beside its own uploaded run artifact. The marker is then downloaded through MLflow's run-artifact endpoint and checked for an exact match.
Technical details
1. The allow-list accepts attacker-created descendants
At release commit 86cd7f5a1dc25a1387ad07c87811bbc30f62951c, mlflow/genai/scorers/scorer_utils.py defines these allowed roots:
THIRD_PARTY_SCORER_ALLOWED_MODULES = frozenset({
"mlflow.genai.scorers.ragas",
"mlflow.genai.scorers.deepeval",
"mlflow.genai.scorers.trulens",
"mlflow.genai.scorers.phoenix",
})
In mlflow/genai/scorers/base.py, the deserializer accepts either an exact root or any descendant:
module_path == m or module_path.startswith(m + ".")
It then performs the following operations using request-controlled values:
module = importlib.import_module(module_path)
scorer_class = getattr(module, class_name, None)
scorer_instance = scorer_class(**init_kwargs)
A package-name prefix is not a provenance boundary. If the server process can write below an allowed package, an attacker-created descendant can satisfy the string-prefix check and become importable Python code.
Relevant source locations at the v3.14.0 release commit:
mlflow/genai/scorers/scorer_utils.py, allowed third-party scorer modulesmlflow/genai/scorers/base.py, third-party scorer deserialization and construction
2. Deserialization occurs before trace processing
In mlflow/server/handlers.py, _invoke_scorer_handler() parses the supplied serialized scorer and calls:
scorer = Scorer.model_validate_json(serialized_scorer)
Only after deserialization and construction does the handler call:
batches = get_trace_batches_for_scorer(trace_ids, scorer, tracking_store)
The attacker-controlled constructor therefore runs before the server validates or resolves the supplied trace IDs.
The minimal proof class is deliberately not a complete MLflow scorer. After its constructor writes the marker, later trace-batching logic accesses scorer attributes that the proof class does not implement. The resulting HTTP 500 occurs after code execution and does not invalidate the proof.
No background job execution or special job-execution server option is required to reach the constructor.
3. MLflow's artifact layout places the module under the trusted prefix
The experiment creation endpoint accepts an artifact_location. For file-backed experiments, MLflow derives each run artifact directory as:
<artifact_location>/<run_id>/artifacts/
The proof of concept selects this package directory in the official v3.14.0 image:
- Record
GHSA-26p8-2jq9-3vq9- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-470, CWE-94
- Updated
- 2026-11-02
Affected products
Affected packages
Public exploits
Links only. Every CVE stores no exploit code.
| Source | Link | Date |
|---|---|---|
| PoC in GitHub | foxcornlab/mlflow-scorer-scanner | 2026-10-28 |
History
| Recorded | Changed | Sources |
|---|---|---|
| facts | EUVD, NVD, OSV | |
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV, PoC in GitHub |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-116692 is a critical vulnerability (CVSS 9.8) in Mlflow, published October 27, 2026. Public exploit code exists. EPSS puts the chance of exploitation in the next 30 days at 0.70%, the 53rd percentile. Fixed in mlflow 3.17.0.
References
github.com/mlflow/mlflow/security/advisories/GHSA-26p8-2jq9-3vq9
github.com/mlflow/mlflow/pull/25906
github.com/mlflow/mlflow/commit/a732443e695c3c677680c54320efc394c81654ab
github.com/mlflow/mlflow/releases/tag/v3.17.0
github.com/advisories/GHSA-26p8-2jq9-3vq9
github.com/CVEProject/cvelistV5/tree/main/cves/2026/116xxx/CVE-2026-116692.json
nvd.nist.gov/vuln/detail/CVE-2026-116692
github.com/mlflow/mlflow
Left out here: the rest of 2 source descriptions. The JSON record has them.