CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-116692

CVE-2026-116692: Remote Code Execution in MLflow via Third-Party Scorer Deserialization

Amit Schendel
Amit Schendel
Senior Security Researcher

Oct 28, 2026·9 min read·5 visits

Executive Summary (TL;DR)

An insecure module prefix check in MLflow's third-party GenAI scorer deserialization allows attackers to upload malicious Python artifacts into writable package directories and execute arbitrary code via dynamic module importation on unauthenticated or authenticated MLflow servers.

CVE-2026-116692 (GHSA-26p8-2jq9-3vq9) is a remote code execution vulnerability in MLflow affecting versions 3.12.0rc0 prior to 3.17.0. The flaw stems from an overly permissive module prefix check during the deserialization of third-party GenAI evaluation scorers in mlflow/genai/scorers/base.py. By setting an experiment's artifact root to a local package directory within the Python module search path and uploading a crafted Python file as a run artifact, an attacker can invoke the /ajax-api/3.0/mlflow/scorer/invoke endpoint to dynamically import and execute arbitrary code on the MLflow tracking server.

Vulnerability Overview

MLflow is an open-source platform designed to manage the machine learning and generative AI lifecycle, including experiment tracking, model registry, and evaluation. Starting in version 3.12.0rc0, MLflow introduced support for registering and invoking third-party GenAI evaluation scorers from external libraries (ragas, deepeval, trulens, and phoenix). To support remote evaluation workflows, the MLflow tracking server exposes the /ajax-api/3.0/mlflow/scorer/invoke HTTP endpoint, which accepts serialized scorer definitions and reconstructs the corresponding Python objects on the server.

In versions 3.12.0rc0 through 3.16.1, the deserialization routine in mlflow/genai/scorers/base.py validates incoming module paths using a string prefix comparison against an allowlist of four base package namespaces (THIRD_PARTY_SCORER_ALLOWED_MODULES). Because the validation checks whether the requested module path begins with an allowed package prefix followed by a dot (module_path.startswith(m + ".")), any descendant submodule path beneath those package trees passes validation.

This validation flaw interacts directly with MLflow's experiment creation and artifact storage architecture. When creating an experiment, a caller can specify an arbitrary local file:// URI as the artifact_location. If the server process has write permissions to its own Python site-packages directory—which is the default state in official MLflow Docker images running as root—an attacker can point an experiment's artifact root to one of the allowlisted scorer package directories on the local filesystem.

After uploading a Python file as a run artifact under that experiment, the attacker submits a request to /ajax-api/3.0/mlflow/scorer/invoke referencing the dotted module path of the uploaded file. The server's Scorer.model_validate_json() method imports the attacker-placed module via importlib.import_module() and instantiates the specified class before performing any trace evaluation. This results in arbitrary code execution within the security context of the MLflow server process, classified under CWE-94 (Code Injection) and CWE-470 (Unsafe Reflection).

Root Cause Analysis

The root cause of CVE-2026-116692 is the unsafe convergence of three architectural behaviors in MLflow: package-prefix validation during dynamic module importation, unrestricted local filesystem paths for experiment artifact storage, and premature object deserialization prior to trace processing.

First, the deserialization method Scorer.model_validate() in mlflow/genai/scorers/base.py relied on THIRD_PARTY_SCORER_ALLOWED_MODULES defined in mlflow/genai/scorers/scorer_utils.py. Instead of restricting imports to a closed set of exact module names, the validation logic checked module_path == m or module_path.startswith(m + "."). In Python, a dotted module path corresponds directly to the underlying directory hierarchy on the filesystem. Consequently, treating a package prefix as a trust boundary fails if an attacker can write files into any subdirectory beneath that package's filesystem root.

Second, MLflow's experiment creation endpoint allows callers to define the artifact_location parameter using local file:// URIs without restricting the target path to a dedicated storage sandbox. When a run is created within a file-backed experiment, MLflow constructs the artifact storage path using the template <artifact_location>/<run_id>/artifacts/. By setting artifact_location to file:///usr/local/lib/python3.10/site-packages/mlflow/genai/scorers/trulens, any file uploaded via the artifact upload API is written directly into the trulens scorer package directory tree on disk.

Third, in mlflow/server/handlers.py, the _invoke_scorer_handler() function deserializes the user-supplied serialized_scorer JSON payload immediately upon receiving the request:

scorer = Scorer.model_validate_json(serialized_scorer)
batches = get_trace_batches_for_scorer(trace_ids, scorer, tracking_store)

During Scorer.model_validate_json(), the server calls importlib.import_module(module_path), looks up the caller-specified class_name via getattr(), and invokes scorer_class(**init_kwargs) without first verifying that the target class inherits from Scorer. Even if the resulting object later fails trace evaluation and causes the endpoint to return an HTTP 500 error, both top-level module code and the class constructor (__init__) have already executed synchronously.

Code Analysis

The vulnerability was introduced in commit 02eea26035b9d8e65af31e6e8f058ca195f62cdd (Pull Request #22634) and remediated in commit a732443e695c3c677680c54320efc394c81654ab (Pull Request #25906). The patch modifies mlflow/genai/scorers/base.py and mlflow/genai/scorers/scorer_utils.py to enforce exact module matching, validate metric identifiers, block keyword argument overrides, and verify class inheritance before instantiation.

In mlflow/genai/scorers/scorer_utils.py, the allowlist was expanded from four top-level package names to an explicit enumeration of all valid submodules that define supported scorer classes:

# mlflow/genai/scorers/scorer_utils.py (Patched)
THIRD_PARTY_SCORER_ALLOWED_MODULES = frozenset({
    "mlflow.genai.scorers.ragas",
    "mlflow.genai.scorers.ragas.scorers",
    "mlflow.genai.scorers.ragas.scorers.agentic_metrics",
    "mlflow.genai.scorers.ragas.scorers.comparison_metrics",
    "mlflow.genai.scorers.ragas.scorers.rag_metrics",
    "mlflow.genai.scorers.deepeval",
    "mlflow.genai.scorers.deepeval.scorers",
    "mlflow.genai.scorers.deepeval.scorers.agentic_metrics",
    "mlflow.genai.scorers.deepeval.scorers.conversational_metrics",
    "mlflow.genai.scorers.deepeval.scorers.rag_metrics",
    "mlflow.genai.scorers.deepeval.scorers.safety_metrics",
    "mlflow.genai.scorers.trulens",
    "mlflow.genai.scorers.trulens.scorers.agent_trace",
    "mlflow.genai.scorers.phoenix",
})

In mlflow/genai/scorers/base.py, the deserialization routine in Scorer.model_validate() was hardened across four distinct vectors:

# mlflow/genai/scorers/base.py (Vulnerable vs. Patched)
         module_path = data.get("module") or ""
         class_name = data.get("class")
         metric_name = data.get("metric_name")
-
-        # VULNERABLE: Prefix check allows arbitrary descendant paths
-        if not any(
-            module_path == m or module_path.startswith(m + ".")
-            for m in THIRD_PARTY_SCORER_ALLOWED_MODULES
-        ):
+        
+        # FIX 1: Exact membership check prevents importing descendant artifact paths
+        if module_path not in THIRD_PARTY_SCORER_ALLOWED_MODULES:
             raise MlflowException.invalid_parameter_value(
                 f"Third-party scorer '{serialized.name}': module '{module_path}' is not "
                 f"in the allow-list {sorted(THIRD_PARTY_SCORER_ALLOWED_MODULES)}."
             )
         if not class_name or not metric_name:
             raise MlflowException.invalid_parameter_value(
                 f"Third-party scorer '{serialized.name}': missing required fields in "
                 f"third_party_scorer_data (class, metric_name)."
             )
+        
+        # FIX 2: Prevent dotted paths in metric_name from triggering secondary imports
+        if not metric_name.isidentifier():
+            raise MlflowException.invalid_parameter_value(
+                f"Third-party scorer '{serialized.name}': metric_name '{metric_name}' "
+                "must be a plain identifier."
+            )
+        
+        # FIX 3: Prevent kwargs['metric_name'] from overriding the validated metric_name
+        if "metric_name" in (data.get("kwargs") or {}):
+            raise MlflowException.invalid_parameter_value(
+                f"Third-party scorer '{serialized.name}': kwargs must not contain "
+                "'metric_name'; set it at the top level of third_party_scorer_data."
+            )
         try:
             module = importlib.import_module(module_path)
         except ImportError as e:
@@ -602,6 +617,12 @@
                 f"Third-party scorer '{serialized.name}': class '{class_name}' not "
                 f"found in module '{module_path}'."
             )
+        
+        # FIX 4: Ensure the target attribute is a valid Scorer subclass before calling __init__
+        if not (inspect.isclass(scorer_class) and issubclass(scorer_class, Scorer)):
+            raise MlflowException.invalid_parameter_value(
+                f"Third-party scorer '{serialized.name}': '{module_path}.{class_name}' "
+                "is not a Scorer subclass."
+            )
         init_kwargs: dict[str, Any] = dict(data.get("kwargs") or {})

> [!NOTE] > Fix Completeness Assessment: The patch goes beyond merely replacing the startswith() check. The authors identified a secondary bypass vector where third-party wrapper classes (such as RagasScorer and DeepEvalScorer) dynamically resolve unknown metric names by splicing metric_name into secondary importlib calls (e.g., ragas.metrics.collections.<metric_name> or deepeval.metrics.<metric_name>). By enforcing metric_name.isidentifier() and prohibiting metric_name inside kwargs, the patch prevents attackers from pivoting to an unvalidated dotted import path inside the third-party library wrappers.

Exploitation Methodology

Exploiting CVE-2026-116692 requires network access to the MLflow tracking server and write permissions for the MLflow server process on one of the allowed scorer package directories (or the third-party evaluation library directories). In default MLflow deployments, authentication is disabled, allowing unauthenticated remote attackers to execute the full chain. When basic authentication is enabled, the attacker must authenticate as a user permitted to create experiments, start runs, and upload artifacts.

The exploitation sequence consists of four distinct stages:

  1. Create a File-Backed Experiment in a Package Directory: The attacker sends a POST request to create a new experiment, specifying artifact_location as file:///usr/local/lib/python3.10/site-packages/mlflow/genai/scorers/trulens (adjusting the Python version path to match the target environment). The server registers the experiment and returns an experiment_id.
  2. Initialize a Run: The attacker sends a POST request to create a new run associated with the experiment_id. The server returns a 32-character hexadecimal run_id (for example, 9a2d665eb38940b68983f0586f623115).
  3. Upload the Malicious Module: Using MLflow's artifact upload endpoint, the attacker uploads a Python file (e.g., payload.py) to the run. Because the experiment uses a file:// artifact root, MLflow writes the file directly to /usr/local/lib/python3.10/site-packages/mlflow/genai/scorers/trulens/9a2d665eb38940b68983f0586f623115/artifacts/payload.py.
  4. Trigger Deserialization and Import: The attacker sends a POST request to /ajax-api/3.0/mlflow/scorer/invoke with a serialized scorer JSON payload referencing the uploaded file's dotted module path:
{
  "name": "eval_exploit",
  "third_party_scorer_data": {
    "module": "mlflow.genai.scorers.trulens.9a2d665eb38940b68983f0586f623115.artifacts.payload",
    "class": "ExploitClass",
    "metric_name": "ExploitClass",
    "model": null,
    "kwargs": {}
  }
}

In authoritative testing against the official ghcr.io/mlflow/mlflow:v3.14.0 container image, invoking /ajax-api/3.0/mlflow/scorer/invoke with the payload mlflow.genai.scorers.trulens.9a2d665eb38940b68983f0586f623115.artifacts.audit_1783843760683972103 caused the server to import the artifact module as uid 0 (root). Although the HTTP response returned status code 500 during subsequent trace batch retrieval, the payload had already executed and written a verification marker file into the artifact directory, which was subsequently confirmed via a 200 OK artifact download request.

Impact Assessment

Successful exploitation of CVE-2026-116692 grants the attacker arbitrary code execution within the context of the MLflow tracking server process. The vulnerability is rated 9.8 (Critical) under CVSS v3.1 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H), reflecting network-accessible exploitation with low complexity, no user interaction, and no required privileges in default configurations.

In official container deployments (such as ghcr.io/mlflow/mlflow:v3.14.0), the MLflow server runs as the root user (uid 0), and the system Python site-packages directory (/usr/local/lib/python3.10/site-packages) is writable by the running process. Consequently, an attacker compromises the entire container environment with administrative privileges.

From this position, an attacker can access sensitive machine learning assets managed by the server, including proprietary model weights, training datasets, evaluation traces, and database credentials used by the backend tracking store. Furthermore, MLflow tracking servers frequently hold cloud provider credentials (such as AWS IAM roles, GCS service account keys, or Azure storage tokens) and third-party LLM API keys (e.g., OpenAI or Anthropic credentials) configured for AI gateway and evaluation features.

Even in environments where MLflow's built-in basic authentication is enabled, the /ajax-api/3.0/mlflow/scorer/invoke endpoint is only guarded by validate_gateway_proxy, which returns True without verifying experiment-level or scorer-level resource permissions. Any authenticated user with basic permissions to create an experiment and upload a run artifact can escalate their access to full server-side code execution.

Remediation and Mitigation

To remediate CVE-2026-116692, organizations must upgrade MLflow to version 3.17.0 or later, which incorporates commit a732443e695c3c677680c54320efc394c81654ab. The update can be applied via pip:

pip install --upgrade "mlflow>=3.17.0"

For environments where immediate upgrading is not feasible, security teams can break the exploitation chain by enforcing strict filesystem permissions. Because the attack requires the MLflow server process to write artifacts directly into Python package directories on disk, configuring the Python installation directory (site-packages and all directories in sys.path) as read-only to the user running the MLflow server prevents the artifact upload stage from succeeding.

In containerized deployments, operators should avoid running the MLflow server as root. Containers should be executed under an unprivileged UID with a read-only root filesystem (--read-only), mounting writable volumes exclusively for dedicated, isolated artifact and database staging directories outside the Python module search path.

Additionally, administrators should configure MLflow with --serve-artifacts backed by remote object storage (such as Amazon S3, Google Cloud Storage, or Azure Blob Storage) rather than allowing local file:// artifact locations. Finally, MLflow tracking servers should never be exposed to untrusted networks without strong authentication and access controls enforced via a reverse proxy.

Official Patches

mlflowOfficial fix commit enforcing exact allowlist matching and metric_name validation.
mlflowGitHub Pull Request #25906 implementing the security patch.
mlflowMLflow v3.17.0 official release containing the patch.

Fix Analysis (1)

Technical Appendix

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

Affected Systems

MLflow Tracking Server (PyPI package: mlflow)Official MLflow Docker Container Images (ghcr.io/mlflow/mlflow)

Affected Versions Detail

Product
Affected Versions
Fixed Version
mlflow
mlflow
>= 3.12.0rc0, < 3.17.03.17.0
AttributeDetail
CVE / GHSA IDCVE-2026-116692 / GHSA-26p8-2jq9-3vq9
CWE ClassificationCWE-94 (Code Injection) / CWE-470 (Unsafe Reflection)
CVSS v3.1 Score9.8 Critical (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
Affected VersionsMLflow >= 3.12.0rc0, < 3.17.0
Patched VersionMLflow 3.17.0
Vulnerable ComponentScorer.model_validate() in mlflow/genai/scorers/base.py
Exploit StatusProof of Concept (PoC) Published
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1059Command and Scripting Interpreter
Execution
T1203Exploitation for Client Execution
Execution
CWE-94
Improper Control of Generation of Code ('Code Injection')

The software constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.

Known Exploits & Detection

GitHub Security AdvisoryOfficial advisory documenting the 4-step reproduction chain and proof-of-concept execution against ghcr.io/mlflow/mlflow:v3.14.0.

Vulnerability Timeline

Vulnerable third-party scorer registration introduced in commit 02eea26035b9d8e65af31e6e8f058ca195f62cdd (#22634).
2026-04-24
Independent reproduction and PoC validated against MLflow 3.14.0.
2026-07-12
Fix implemented in commit a732443e695c3c677680c54320efc394c81654ab via Pull Request #25906.
2026-09-16
CVE-2026-116692 and GHSA-26p8-2jq9-3vq9 published; MLflow 3.17.0 released.
2026-10-27

References & Sources

  • [1]GitHub Security Advisory: GHSA-26p8-2jq9-3vq9
  • [2]CVE-2026-116692 Official Record
  • [3]MLflow Pull Request #25906: Match third-party scorer modules exactly
  • [4]MLflow Fix Commit a732443e695c3c677680c54320efc394c81654ab
  • [5]MLflow v3.17.0 Release Notes
  • [6]MITRE ATT&CK Framework Mapping
  • [7]CISA Known Exploited Vulnerabilities Catalog

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•41 minutes ago•CVE-2026-116693
8.7

CVE-2026-116693: Regular Expression Denial of Service in Datadog Node.js Tracer (dd-trace)

CVE-2026-116693 is a high-severity Regular Expression Denial of Service (ReDoS) vulnerability affecting the official Datadog Application Performance Monitoring (APM) client library for Node.js, dd-trace. The defect resides in the default regular expression defined by the DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP configuration parameter, which is used to sanitize incoming and outgoing HTTP URL query strings prior to recording trace span metadata. Due to overlapping sub-patterns and unanchored repetition blocks in the JSON Web Token (JWT) and URL-encoded string matching branches, the V8 JavaScript engine's regular expression subsystem (Irregexp) can be forced into catastrophic or high-degree polynomial backtracking. Because regular expression replacement occurs synchronously on the primary Node.js event loop, an unauthenticated remote attacker can send crafted HTTP requests with malicious query strings to consume 100% of the process CPU core, stalling concurrent request processing and rendering the application unavailable.

Alon Barad
Alon Barad
3 views•10 min read
•about 3 hours ago•CVE-2026-116691
6.5

CVE-2026-116691: Cleartext LLM Provider API Key Disclosure in MLflow CrewAI Autologging

CVE-2026-116691 (GHSA-j7qq-vr8c-xvw3) is a sensitive credential disclosure vulnerability in the MLflow platform affecting versions 2.19.0 through 3.16.x. When CrewAI autologging is enabled via mlflow.crewai.autolog(), the tracing instrumentation fails to sanitize nested CrewAI LLM objects before serializing them into OpenTelemetry trace spans. Consequently, plaintext LLM provider API keys are extracted from object dictionaries and string representations, then transmitted and stored in MLflow tracking servers or external OpenTelemetry Protocol (OTLP) telemetry backends.

Alon Barad
Alon Barad
4 views•12 min read
•about 4 hours ago•CVE-2026-116690
7.5

CVE-2026-116690: Arbitrary File Read via Prompt Model Version Source in MLflow

MLflow versions 3.10.0 through 3.16.1 contain a path traversal vulnerability (CWE-22) in the model version creation handler. Specifically, the prompt-specific execution branch in `_create_model_version()` fails to validate schemeless relative paths and evaluates the `mlflow.prompt.is_prompt` tag based on key presence rather than value. An unauthenticated remote attacker on default deployments, or an authenticated low-privilege user on basic-auth deployments, can register a model version with a relative local path as its artifact source and subsequently retrieve arbitrary files from the server filesystem via the `/api/2.0/mlflow/model-versions/get-artifact` endpoint.

Alon Barad
Alon Barad
10 views•9 min read
•about 5 hours ago•CVE-2026-109383
7.4

CVE-2026-109383: Improper Certificate Validation in Node.js Undici Agent Dispatcher

CVE-2026-109383 (GHSA-2m5v-pch6-74hx) is an improper certificate validation vulnerability (CWE-295) in undici, the official HTTP/1.1 and HTTP/2 client library for Node.js. In affected versions, the Agent constructor clones configuration options using a JSON-based deep clone function (util.deepClone), which executes JSON.parse(JSON.stringify(options)). Because JSON serialization omits function properties, custom certificate validation callbacks passed via the tls configuration option—such as tls.checkServerIdentity—are silently stripped before being passed to origin connection pools. Consequently, connections fall back to standard Node.js TLS validation, bypassing custom certificate pinning or hostname verification rules and allowing an Adversary-in-the-Middle (AitM) with a CA-trusted certificate to intercept and modify traffic.

Alon Barad
Alon Barad
6 views•10 min read
•about 6 hours ago•CVE-2026-109829
7.4

CVE-2026-109829: Persistent Cache Poisoning and Cross-User Information Disclosure in Node.js Undici

A cache key origin desynchronization and intermediate Cache-Control suppression vulnerability exists in the Node.js Undici HTTP client library. When an originless Agent composes the redirect and cache interceptors in the order compose(redirect(), cache()) with a shared cache store, cross-origin responses and sensitive redirected resources are stored under the initial request's cache key.

Alon Barad
Alon Barad
8 views•11 min read
•about 7 hours ago•CVE-2026-109855
7.5

CVE-2026-109855: Denial of Service via Unhandled Stream Error in Node.js Undici HTTP/2 WebSockets

CVE-2026-109855 is a high-severity Denial of Service (DoS) vulnerability in undici, the primary HTTP/1.1 and HTTP/2 client library for Node.js maintained under the OpenJS Foundation. In versions 8.11.0 and 8.11.1, the WebSocket client fails to properly terminate underlying ClientHttp2Stream instances when an HTTP/2 WebSocket handshake fails validation. When the remote server subsequently closes or resets the stream, Node.js emits an unhandled error event on the orphaned stream, crashing the client process.

Alon Barad
Alon Barad
9 views•9 min read