DEV Community

CVE Reports
CVE Reports

Posted on Originally published at cvereports.com

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

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

Vulnerability ID: CVE-2026-116690
CVSS Score: 7.5
Published: 2026-10-27

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.

TL;DR

A path traversal flaw in MLflow 3.10.0 to 3.16.1 allows remote attackers to read arbitrary local files from the tracking server by registering a prompt-tagged model version with a schemeless relative source path and downloading artifacts from it.


⚠️ Exploit Status: POC

Technical Details

  • CVE ID: CVE-2026-116690
  • CWE Classification: CWE-22 (Path Traversal)
  • CVSS v3.1 Score: 7.5 HIGH (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)
  • Affected Versions: MLflow >= 3.10.0, < 3.17.0
  • Patched Version: MLflow 3.17.0
  • Attack Vector: Network (HTTP REST API /api/2.0/mlflow/model-versions/create)
  • Impact: Arbitrary Local File Read
  • Exploit Status: Public Proof-of-Concept (GHSA-m65p-9j7q-6w33)

Affected Systems

  • MLflow Tracking Server (mlflow PyPI package)
  • mlflow: >= 3.10.0, < 3.17.0 (Fixed in: 3.17.0)

Code Analysis

Commit: 81730a2

Reject schemeless path sources for prompt model versions in _create_model_version (#25870)

--- a/mlflow/prompt/constants.py
+++ b/mlflow/prompt/constants.py
@@ -5,6 +5,17 @@
 # A special tag in ModelVersion to store the prompt text
 PROMPT_TEXT_TAG_KEY = "mlflow.prompt.text"

+_CLIENT_PROMPT_SOURCE_PLACEHOLDER = "dummy-source"
+_STORE_PROMPT_SOURCE_PLACEHOLDER = "prompt-template"
+_PROMPT_SOURCE_PLACEHOLDERS = frozenset({
+    _CLIENT_PROMPT_SOURCE_PLACEHOLDER,
+    _STORE_PROMPT_SOURCE_PLACEHOLDER,
+})
+
 # Unity Catalog tags cannot contain dots
 PROMPT_TYPE_TAG_KEY = "_mlflow_prompt_type"
 RESPONSE_FORMAT_TAG_KEY = "_mlflow_prompt_response_format"
--- a/mlflow/server/handlers.py
+++ b/mlflow/server/handlers.py
+def _validate_prompt_source(source: str) -> None:
+    scheme = get_uri_scheme(source)
+    if scheme and scheme != "file":
+        _validate_non_local_source_contains_relative_paths(source)
+        return
+    if source not in _PROMPT_SOURCE_PLACEHOLDERS:
+        raise MlflowException(
+            f"Invalid prompt source: '{source}'. Local source paths are not allowed for prompts.",
+            INVALID_PARAMETER_VALUE,
+        )
+
@@ -3134,21 +3163,7 @@ def _create_model_version():
     is_prompt = _is_prompt_request(request_message)
     if is_prompt:
-        source = request_message.source
-        parsed = urllib.parse.urlparse(source)
-        if parsed.scheme == "file" or (parsed.scheme == "" and source.startswith("/")):
-            raise MlflowException(
-                f"Invalid prompt source: '{source}'. "
-                "Local source paths are not allowed for prompts.",
-                INVALID_PARAMETER_VALUE,
-            )
-        if parsed.scheme:
-            _validate_non_local_source_contains_relative_paths(source)
+        _validate_prompt_source(request_message.source)
     else:
         if request_message.model_id:
             _validate_source_model(request_message.source, request_message.model_id)
@@ -3213,7 +3228,10 @@ def _create_model_version():
 def _is_prompt_request(request_message):
-    return any(tag.key == IS_PROMPT_TAG_KEY for tag in request_message.tags)
+    tags = {tag.key: tag.value for tag in request_message.tags}
+    return tags.get(IS_PROMPT_TAG_KEY, "false").lower() == "true"
Enter fullscreen mode Exit fullscreen mode

Exploit Details

  • GitHub: Official GitHub Security Advisory detailing the HTTP reproduction steps and vulnerable code path.

Mitigation Strategies

  • Upgrade MLflow to version 3.17.0 or later to enforce strict placeholder validation on prompt model version sources.
  • Deploy an authenticating reverse proxy or API gateway in front of MLflow tracking servers to prevent unauthenticated network access.
  • Implement WAF or reverse proxy inspection rules to block POST requests to /api/2.0/mlflow/model-versions/create containing relative path traversal sequences (..) in the source field.
  • Run the MLflow server process under a dedicated, least-privilege operating system user account with restricted filesystem read permissions.

Remediation Steps:

  1. Update the MLflow Python package via pip: pip install --upgrade "mlflow>=3.17.0".
  2. Restart all running mlflow server instances and worker processes to load the patched handler code.
  3. Audit the MLflow backend registry database (model_versions table) for existing entries with anomalous or relative source paths and delete malicious model versions.
  4. Rotate any sensitive credentials, API keys, or authentication database secrets stored on the server filesystem if unauthorized access is suspected.

References


Read the full report for CVE-2026-116690 on our website for more details including interactive diagrams and full exploit analysis.

Top comments (0)