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

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

Alon Barad
Alon Barad
Software Engineer

Oct 24, 2026·9 min read·7 visits

Executive Summary (TL;DR)

LiteLLM reuses the same AES-256-GCM encryption context without domain separation for both stored metadata secrets and administrative session bearer tokens. Authenticated internal users can store forged administrator JSON payloads inside API key metadata, retrieve the encrypted string, and replay it as an HTTP Bearer token to gain full proxy_admin privileges and remote command execution.

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.

Vulnerability Overview

LiteLLM is an open-source AI Gateway and proxy server developed by BerriAI that standardizes API calls across multiple Large Language Model (LLM) providers. In multi-tenant or enterprise deployments, LiteLLM manages authentication, cost tracking, logging callbacks, and role-based access control (RBAC) across low-privilege internal users (internal_user) and system administrators (proxy_admin). To protect sensitive operational parameters at rest—such as third-party API keys and logging callback credentials—LiteLLM encrypts configuration fields using a master symmetric key (LITELLM_SALT_KEY or MASTER_KEY).

Starting in version 1.87.0, LiteLLM introduced session authentication mechanisms (ExperimentalUIJWTToken) for UI and CLI workflows that issued encrypted JSON payloads as bearer tokens rather than standard signed JWTs. Instead of establishing a separate cryptographic key or domain-specific context for session tokens, the authentication handler directly invoked the general-purpose storage encryption routine encrypt_value_helper(). In versions 1.87.0 through 1.90.x, this functionality required EXPERIMENTAL_UI_LOGIN=true to be explicitly enabled, whereas versions 1.91.0 and later enabled this code path in the default configuration.

This design flaw results in a confused-deputy vulnerability (CWE-441) combined with improper privilege management (CWE-269) and insufficient verification of data authenticity (CWE-345). Because both at-rest secrets and active bearer tokens share the exact same key derivation and AES-256-GCM encryption parameters without cryptographic binding to their intended purpose, any API endpoint that encrypts user-controlled strings and returns the ciphertext acts as an encryption oracle. An authenticated low-privilege user can submit a JSON structure containing `

Root Cause Analysis

The root cause of CVE-2026-114612 is the absence of cryptographic domain separation in litellm/proxy/common_utils/encrypt_decrypt_utils.py. When encrypting values using Authenticated Encryption with Associated Data (AEAD) via AES-256-GCM, the underlying _encrypt_aes_gcm() and _decrypt_aes_gcm() functions derived a 256-bit key by taking a single SHA-256 hash of the configured signing_key (LITELLM_SALT_KEY or MASTER_KEY). Crucially, both functions hardcoded the Associated Authenticated Data (aad) parameter in AESGCM.encrypt() and AESGCM.decrypt() to None.

In AEAD constructions, the aad parameter binds ciphertext to a specific context, protocol header, or data type so that a valid ciphertext generated in one subsystem cannot be transplanted into another. Because aad was omitted, all ciphertexts produced by encrypt_value_helper() were cryptographically interchangeable across the entire application. The only differentiator applied to stored values was an outer string prefix (litellm_enc::) prepended outside the authenticated encryption boundary, while the inner ciphertext began with the version marker v2:gcm: followed by the base64url-encoded 12-byte nonce, ciphertext, and 16-byte authentication tag.

Concurrently, the session token handler in litellm/proxy/auth/auth_checks.py implemented ExperimentalUIJWTToken.get_experimental_ui_login_jwt_auth_token() and get_cli_jwt_auth_token() by serializing user session models to JSON and passing them directly to encrypt_value_helper(). When authenticating incoming HTTP requests, ExperimentalUIJWTToken.get_key_object_from_ui_hash_key() took the bearer token from the Authorization header and passed it to decrypt_value_helper(). If decryption succeeded and the resulting plaintext parsed as valid JSON containing a user_role attribute, the proxy trusted the deserialized role without verifying how or where the ciphertext was originally minted.

Because the outer litellm_enc:: marker was not authenticated by the GCM tag, an attacker could strip it from any reflected secret stored in database metadata, leaving a bare v2:gcm:... string that was byte-for-byte identical in structure to a legitimate UI or CLI session token.

Code Analysis

Prior to the patch, litellm/proxy/auth/auth_checks.py minted and validated session tokens by directly calling encrypt_value_helper and decrypt_value_helper. In litellm/proxy/common_utils/encrypt_decrypt_utils.py, the AES-256-GCM implementation passed None for the aad argument.

# Vulnerable implementation in litellm/proxy/common_utils/encrypt_decrypt_utils.py
def _derive_key(signing_key: str) -> bytes:
    return hashlib.sha256(signing_key.encode()).digest()
 
def _encrypt_aes_gcm(value: str, signing_key: str) -> str:
    """Encrypt under AES-256-GCM and return the versioned ``v2:gcm:`` string."""
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
 
    nonce: Final = os.urandom(12)
    # VULNERABILITY: Associated Authenticated Data (AAD) is set to None,
    # providing no domain separation between stored secrets and bearer tokens.
    blob: Final = AESGCM(_derive_key(signing_key)).encrypt(nonce, value.encode("utf-8"), None)
    return _V2_GCM_PREFIX + base64.urlsafe_b64encode(nonce + blob).decode("utf-8")
 
def _decrypt_aes_gcm(value: str, signing_key: str) -> str:
    """Decrypt a versioned ``v2:gcm:`` string produced by :func:`_encrypt_aes_gcm`."""
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
 
    raw: Final = base64.urlsafe_b64decode(value[len(_V2_GCM_PREFIX) :])
    nonce, blob = raw[:12], raw[12:]
    # VULNERABILITY: Decrypts any v2:gcm: payload encrypted with the same key.
    return AESGCM(_derive_key(signing_key)).decrypt(nonce, blob, None).decode("utf-8")

In commit 1d5fdc87fed66b49e1df8cf7d708ad93249eb7d0 (and its branch equivalents), the maintainers refactored the encryption utilities to support explicit AAD binding via low-level _seal_aes_gcm() and _open_aes_gcm() primitives. They introduced dedicated encrypt_bearer_token() and decrypt_bearer_token() functions that enforce a mandatory prefix (LITELLM_SESSION_TOKEN_PREFIX = "litellm_login_") both as a wire-format prefix and as the cryptographic AAD passed into AES-GCM.

# Patched implementation in litellm/proxy/common_utils/encrypt_decrypt_utils.py
def _seal_aes_gcm(value: str, signing_key: str, aad: bytes | None) -> bytes:
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
 
    nonce: Final = os.urandom(12)
    # Passes explicit AAD into AESGCM.encrypt to bind ciphertext to its context
    return nonce + AESGCM(_derive_key(signing_key)).encrypt(nonce, value.encode("utf-8"), aad)
 
def _open_aes_gcm(sealed: bytes, signing_key: str, aad: bytes | None) -> str:
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
 
    # Verifies the GCM authentication tag against the expected AAD
    return AESGCM(_derive_key(signing_key)).decrypt(sealed[:12], sealed[12:], aad).decode("utf-8")
 
def encrypt_bearer_token(value: str, prefix: str) -> str:
    """AES-256-GCM as unpadded base64url behind ``prefix``, which is also the AAD so a token can't change kind."""
    salt_key: Final = _get_salt_key()
    if not isinstance(salt_key, str):
        raise ValueError("Set LITELLM_SALT_KEY or a master key to mint bearer tokens")
    # Binds the token ciphertext to 'litellm_login_' via AAD
    sealed: Final = _seal_aes_gcm(value=value, signing_key=salt_key, aad=prefix.encode("utf-8"))
    return prefix + base64.urlsafe_b64encode(sealed).decode("ascii").rstrip("=")
 
def decrypt_bearer_token(token: str, prefix: str) -> str | None:
    """None unless ``token`` came from :func:`encrypt_bearer_token` with the same ``prefix``."""
    salt_key: Final = _get_salt_key()
    if not isinstance(salt_key, str) or not token.startswith(prefix):
        return None
    encoded: Final = token.removeprefix(prefix)
    try:
        sealed: Final = base64.b64decode(encoded + "=" * (-len(encoded) % 4), altchars=b"-_", validate=True)
        return _open_aes_gcm(sealed=sealed, signing_key=salt_key, aad=prefix.encode("utf-8"))
    except Exception:
        return None

Correspondingly, ExperimentalUIJWTToken in litellm/proxy/auth/auth_checks.py was updated to call encrypt_bearer_token(..., prefix=LITELLM_SESSION_TOKEN_PREFIX) and decrypt_bearer_token(..., prefix=LITELLM_SESSION_TOKEN_PREFIX).

> [!NOTE] > Fix Completeness Assessment: The patch completely resolves the cross-domain cipher replay vulnerability. General at-rest encryption in _encrypt_aes_gcm() continues to pass aad=None and prepend v2:gcm:, whereas bearer tokens require the litellm_login_ prefix and validate aad=b"litellm_login_" during GCM tag verification. Even if an attacker strips litellm_enc::v2:gcm: from an encrypted metadata value and prepends litellm_login_, _open_aes_gcm() will reject the payload with an InvalidTag exception because the authentication tag was computed with aad=None.

Exploitation

Exploiting CVE-2026-114612 requires network access to the LiteLLM HTTP API and a valid low-privilege credential (internal_user). In LiteLLM versions 1.91.0 and later, no special configuration flags are required; in versions 1.87.0 through 1.90.x, EXPERIMENTAL_UI_LOGIN must be enabled. The attack proceeds in four distinct phases: oracle encryption, ciphertext extraction, prefix manipulation, and administrative token replay.

First, the attacker invokes an endpoint accessible to internal_user accounts that accepts user-defined secrets and encrypts them at rest. As demonstrated in the official regression test (tests/e2e/other/test_session_token_e2e.py), the /key/generate endpoint accepts a metadata.logging array containing per-key callback configurations such as langfuse_secret_key. The attacker sets langfuse_secret_key to a serialized JSON string matching the schema expected by ExperimentalUIJWTToken.get_key_object_from_ui_hash_key():

POST /key/generate HTTP/1.1
Host: litellm-proxy.internal:4000
Authorization: Bearer sk-low-privilege-internal-user-key
Content-Type: application/json
 
{
  "key_alias": "session-oracle-key",
  "metadata": {
    "logging": [
      {
        "callback_name": "langfuse",
        "callback_vars": {
          "langfuse_secret_key": "{\"token\": \"forged-admin-session\", \"user_id\": \"attacker-admin\", \"user_role\": \"proxy_admin\", \"team_id\": \"litellm-dashboard\"}"
        }
      }
    ]
  }
}

Second, the attacker queries /key/info for the newly created key. LiteLLM retrieves the key record from the database and returns the metadata object containing the encrypted secret in the format litellm_enc::v2:gcm:<base64url-nonce-ciphertext-tag>.

Third, the attacker strips the outer storage prefix litellm_enc:: from the returned string, isolating the inner v2:gcm:<base64url-nonce-ciphertext-tag> payload. Fourth, the attacker submits this string as an HTTP Bearer token to administrative endpoints such as /user/list or the Model Context Protocol (MCP) management routes:

GET /user/list HTTP/1.1
Host: litellm-proxy.internal:4000
Authorization: Bearer v2:gcm:<base64url-nonce-ciphertext-tag>

When LiteLLM processes this request, get_key_object_from_ui_hash_key() decrypts the v2:gcm: payload using the proxy's salt key, parses the forged JSON claims, and constructs an authenticated UserAPIKeyAuth session with user_role="proxy_admin". From this position, the attacker can register an MCP server configured with the stdio transport to execute arbitrary operating system commands on the underlying host.

Impact Assessment

CVE-2026-114612 carries a CVSS v3.1 base score of 9.9 (Critical) with the vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H. The Scope metric is marked as Changed (S:C) because escalating privileges within the LiteLLM authorization layer directly enables post-exploitation code execution on the underlying host operating system via administrative features.

Upon achieving proxy_admin privileges, an attacker gains unrestricted read and write access to all resources managed by the AI Gateway. In terms of Confidentiality (C:H), the attacker can retrieve all stored upstream LLM provider API keys (OpenAI, Anthropic, AWS Bedrock, Azure, Vertex AI), read prompt and completion logs across all tenants, and extract database or callback credentials. Furthermore, because the attacker has administrative control over the proxy configuration, they can inspect or manipulate virtual keys belonging to any organization or team.

In terms of Integrity (I:H) and Availability (A:H), the attacker can modify model routing rules to redirect enterprise LLM traffic to attacker-controlled endpoints, alter spend limits, revoke legitimate API keys, or execute arbitrary system commands through the MCP stdio client configuration. Host-level command execution allows the attacker to access environment variables—including the plaintext LITELLM_SALT_KEY, MASTER_KEY, and DATABASE_URL—enabling persistent compromise of the backend PostgreSQL database and adjacent cloud infrastructure.

Remediation

Organizations running LiteLLM Proxy must upgrade to a patched release that enforces cryptographic domain separation for bearer tokens. BerriAI released patches across five active release tracks: 1.100.4, 1.101.3, 1.102.2, 1.103.1, and 1.104.0-rc.2. Upgrading immediately invalidates any v2:gcm: ciphertexts replayed as bearer tokens because decrypt_bearer_token() strictly requires the litellm_login_ prefix and verifies the corresponding AEAD Associated Authenticated Data.

For environments running versions 1.87.0 through 1.90.x where an immediate upgrade is not feasible, administrators can mitigate the vulnerability by ensuring EXPERIMENTAL_UI_LOGIN is unset or explicitly set to false in the proxy environment and configuration files. In versions 1.91.0 and later, the vulnerable code path is active by default, making version upgrades or upstream reverse-proxy filtering mandatory.

As an immediate compensating control at the network perimeter, security teams can configure a Web Application Firewall (WAF) or reverse proxy (such as NGINX or Envoy) to block incoming HTTP requests where the Authorization header contains a Bearer token beginning with v2:gcm: or litellm_enc::. Additionally, if a vulnerable LiteLLM instance was exposed to untrusted internal users, administrators should rotate LITELLM_SALT_KEY (using LiteLLM's key rotation utilities to re-encrypt database secrets), rotate MASTER_KEY, and audit database tables (LiteLLM_VerificationToken and MCP server configurations) for unauthorized administrative entries or anomalous langfuse_secret_key metadata values.

Official Patches

BerriAIOfficial GitHub Security Advisory for GHSA-7hp6-4w63-5g45 / CVE-2026-114612
BerriAILiteLLM v1.100.4 Release
BerriAILiteLLM v1.101.3 Release
BerriAILiteLLM v1.102.2 Release
BerriAILiteLLM v1.103.1 Release
BerriAILiteLLM v1.104.0-rc.2 Release

Fix Analysis (5)

Technical Appendix

CVSS Score
9.9/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
EPSS Probability
0.24%
Top 86% most exploited

Affected Systems

BerriAI LiteLLM Proxy ServerChainguard LiteLLM Container Images

Affected Versions Detail

Product
Affected Versions
Fixed Version
litellm
BerriAI
>= 1.87.0, < 1.100.41.100.4
litellm
BerriAI
>= 1.101.0, < 1.101.31.101.3
litellm
BerriAI
>= 1.102.0, < 1.102.21.102.2
litellm
BerriAI
>= 1.103.0, < 1.103.11.103.1
litellm
BerriAI
>= 1.104.0-rc.1, < 1.104.0-rc.21.104.0-rc.2
AttributeDetail
CWE IDCWE-269 / CWE-345 / CWE-441
Attack VectorNetwork (Authenticated Low-Privilege API)
CVSS v3.1 Score9.9 (Critical)
EPSS Score0.24% (13.63th Percentile)
Primary ImpactPrivilege Escalation to proxy_admin & RCE via MCP stdio
Exploit StatusProof-of-Concept (Official E2E Regression Test)
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1068Exploitation for Privilege Escalation
Privilege Escalation
T1059Command and Scripting Interpreter
Execution
CWE-269
Improper Privilege Management

The software does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor. Additionally classified under CWE-345 (Insufficient Verification of Data Authenticity) and CWE-441 (Unintended Proxy or Intermediary / 'Confused Deputy').

Known Exploits & Detection

GitHub (Official E2E Regression Suite)End-to-end regression test (tests/e2e/other/test_session_token_e2e.py) demonstrating the oracle encryption via /key/generate langfuse_secret_key and replay against /user/list.

Vulnerability Timeline

Patch commits authored and backported across LiteLLM maintenance branches.
2026-09-29
GitHub Security Advisory GHSA-7hp6-4w63-5g45 and CVE-2026-114612 officially published.
2026-10-22
NIST NVD analysis completed with a CVSS v3.1 score of 9.9 (Critical) and initial EPSS scoring assigned.
2026-10-23

References & Sources

  • [1]GitHub Security Advisory GHSA-7hp6-4w63-5g45
  • [2]NIST National Vulnerability Database (NVD) - CVE-2026-114612
  • [3]CVE.org Official Record - CVE-2026-114612
  • [4]CVE List V5 JSON Record - CVE-2026-114612
  • [5]LiteLLM Patch Commit 1d5fdc87fed66b49e1df8cf7d708ad93249eb7d0
  • [6]Wiz Vulnerability Database - CVE-2026-114612

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-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
3 views•11 min read
•about 1 hour 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
5 views•10 min read
•about 2 hours ago•CVE-2026-109461
5.9

CVE-2026-109461: Origin Validation Error in @azure/msal-node Leads to Information Disclosure

An origin validation vulnerability in @azure/msal-node allows attackers to process unchecked authorization code response payloads or request query parameters in ConfidentialClientApplication, potentially directing sensitive client credentials to external endpoints.

Amit Schendel
Amit Schendel
9 views•4 min read
•about 3 hours ago•CVE-2026-113719
4.5

CVE-2026-113719: Arbitrary File Deletion via Path Traversal in virtualenv Seed Package Metadata

A path traversal vulnerability in PyPA virtualenv prior to version 21.14.9 allows local attackers to cause arbitrary file and directory deletion when a virtual environment target directory containing untrusted distribution metadata is reused or reseeded.

Amit Schendel
Amit Schendel
6 views•6 min read
•about 4 hours ago•CVE-2026-113715
2.0

CVE-2026-113715: Configuration Key Injection via Unicode Line Boundary in virtualenv

In virtualenv versions prior to 21.14.9, creating isolated Python virtual environments using the standard library creator (`--creator venv`) allows configuration key injection into `pyvenv.cfg` if an attacker can control or influence the destination directory path containing Unicode line break characters.

Amit Schendel
Amit Schendel
5 views•5 min read
•about 5 hours ago•CVE-2026-113723
7.1

CVE-2026-113723: OS Command Injection via Unsafe Prompt Expansion in PyPA virtualenv

A command injection vulnerability in PyPA virtualenv prior to version 21.14.9 allows local attackers to execute arbitrary commands when a user sources a generated shell activation script containing a crafted prompt value.

Alon Barad
Alon Barad
6 views•5 min read