Skip to content

fix(mcp): block arbitrary command execution via stdio transport#25343

Merged
yuneng-berri merged 4 commits intomainfrom
litellm_fix-mcp-stdio-rce3
Apr 8, 2026
Merged

fix(mcp): block arbitrary command execution via stdio transport#25343
yuneng-berri merged 4 commits intomainfrom
litellm_fix-mcp-stdio-rce3

Conversation

@Sameerlite
Copy link
Copy Markdown
Collaborator

Relevant issues

Fixes critical RCE vulnerability in MCP stdio test endpoints.

Pre-Submission checklist

  • I have Added testing in the tests/test_litellm/ directory, Adding at least 1 test is a hard requirement - see details
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible, it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Type

🐛 Bug Fix

Changes

  • Command allowlist: Added MCP_STDIO_ALLOWED_COMMANDS constant (npx, uvx, python, python3, node, docker, deno) in litellm/constants.py. Extensible via LITELLM_MCP_STDIO_EXTRA_COMMANDS env var.
  • Pydantic validation: NewMCPServerRequest and UpdateMCPServerRequest now validate stdio commands against the allowlist, blocking bash, sh, /bin/bash, etc.
  • Defense-in-depth: _create_mcp_client in mcp_server_manager.py also validates commands for MCPServer objects loaded from config/DB.
  • PROXY_ADMIN role check: /mcp-rest/test/connection and /mcp-rest/test/tools/list now require PROXY_ADMIN role (403 for non-admins). Also added missing dependencies=[Depends(user_api_key_auth)] to test_tools_list.
  • Docs fix: docker/README.md corrected MASTER_KEYLITELLM_MASTER_KEY (4 occurrences).
  • Tests: 11 new tests for command allowlist validation and role checks.

Sameerlite and others added 2 commits April 8, 2026 19:42
Add command allowlist for MCP stdio transport to prevent RCE via
/mcp-rest/test/* endpoints. Restrict test endpoints to PROXY_ADMIN
role. Fix docker/README.md MASTER_KEY -> LITELLM_MASTER_KEY.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Defense-in-depth: warn instead of hard-fail for legacy servers
- Move os import to module level in _types.py
- Document args residual risk in allowlist comment
- Add UpdateMCPServerRequest allowlist test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
litellm Ready Ready Preview, Comment Apr 8, 2026 4:03pm

Request Review

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq bot commented Apr 8, 2026

Merging this PR will not alter performance

✅ 16 untouched benchmarks


Comparing litellm_fix-mcp-stdio-rce3 (65829f7) with main (62757ff)

Open in CodSpeed

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 8, 2026

Greptile Summary

This PR fixes a critical RCE vulnerability in LiteLLM's MCP stdio transport by adding a command allowlist, Pydantic validation, and PROXY_ADMIN-only role checks on the /mcp-rest/test/* endpoints. The inline import issues flagged in the previous review (import os and from litellm.constants import MCP_STDIO_ALLOWED_COMMANDS) have been fixed — both are now at module level in _types.py and mcp_server_manager.py.

Key changes:

  • MCP_STDIO_ALLOWED_COMMANDS constant in litellm/constants.py (npx, uvx, python, python3, node, docker, deno) extensible via LITELLM_MCP_STDIO_EXTRA_COMMANDS.
  • NewMCPServerRequest and UpdateMCPServerRequest now validate the command against the allowlist in a model_validator(mode=\"before\").
  • Defense-in-depth in _create_mcp_client now raises HTTP 403 (not just a warning) for legacy DB/config records with non-allowlisted commands.
  • /mcp-rest/test/connection and /mcp-rest/test/tools/list now require PROXY_ADMIN role.
  • 11 new unit tests covering allowlist enforcement and role checks — all mock-only, consistent with the tests/test_litellm/ rule.
  • docker/README.md corrects MASTER_KEYLITELLM_MASTER_KEY.

Confidence Score: 5/5

Safe to merge — the primary RCE vulnerability is fully blocked; the one remaining finding is a P2 gap in the UpdateMCPServerRequest validation layer that is fully mitigated by the defense-in-depth check in _create_mcp_client.

All previous inline-import issues are resolved. Both the Pydantic validation layer and the execution-time defense-in-depth block disallowed commands for new servers. The PROXY_ADMIN role check correctly protects the test endpoints. The only open finding is a P2: partial UpdateMCPServerRequest payloads that omit transport bypass the Pydantic layer but are still blocked at execution by the secondary check, so no actual exploit path exists.

litellm/proxy/_types.py — UpdateMCPServerRequest.validate_transport_fields should unconditionally validate the command allowlist when a command value is provided, regardless of the transport field.

Vulnerabilities

  • Residual bypass in UpdateMCPServerRequest validation: When transport is omitted from an update payload, it defaults to \"sse\", skipping the allowlist check. A malicious PROXY_ADMIN could persist a disallowed command (e.g. bash) to the DB. Execution is still blocked by the defense-in-depth check in _create_mcp_client, so there is no RCE, but the Pydantic layer provides weaker protection for the update path than for the create path.
  • docker is in the built-in allowlist; docker run --rm -it ubuntu bash is permitted via args. Accepted risk per the PR comment since the endpoints require PROXY_ADMIN.
  • Previous inline-import issues flagged in the prior review have been resolved.

Important Files Changed

Filename Overview
litellm/constants.py Adds MCP_STDIO_ALLOWED_COMMANDS frozenset with built-in commands and env-var extension via LITELLM_MCP_STDIO_EXTRA_COMMANDS; clean implementation.
litellm/proxy/_types.py Adds allowlist check to NewMCPServerRequest (solid) and UpdateMCPServerRequest; the update validator skips the check when transport defaults to 'sse', leaving a validation-layer gap that is mitigated by defense-in-depth.
litellm/proxy/_experimental/mcp_server/mcp_server_manager.py Defense-in-depth allowlist check now raises HTTP 403 for legacy non-allowlisted commands loaded from DB/config; os imported at module level.
litellm/proxy/_experimental/mcp_server/rest_endpoints.py test_connection and test_tools_list now require PROXY_ADMIN role; dependencies=[Depends(user_api_key_auth)] added to test_tools_list correctly.
tests/test_litellm/proxy/_experimental/mcp_server/test_rest_endpoints.py 11 new mock-only unit tests covering allowlist validation and role checks; no real network calls; good coverage of happy and sad paths.
docker/README.md Corrects MASTER_KEY → LITELLM_MASTER_KEY in 4 places; documentation-only fix.
docs/my-website/docs/proxy/config_settings.md Adds LITELLM_MCP_STDIO_EXTRA_COMMANDS env var documentation entry.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Client Request] --> B{Endpoint}

    B --> C[POST /mcp-rest/test/connection\nor /mcp-rest/test/tools/list]
    B --> D[POST /mcp-rest/server\nAdd/Update MCP Server]

    C --> E{Role Check\nPROXY_ADMIN?}
    E -- No --> F[403 Forbidden]
    E -- Yes --> G[Pydantic validates\nNewMCPServerRequest]

    D --> H[Pydantic validates\nNewMCPServerRequest\nor UpdateMCPServerRequest]

    G --> I{transport == stdio?}
    H --> I

    I -- Yes --> J{basename\nin MCP_STDIO_ALLOWED_COMMANDS?}
    I -- No --> K[Pass validation]

    J -- No --> L[422 Validation Error]
    J -- Yes --> K

    K --> M[_create_mcp_client\nDefense-in-depth check]
    M --> N{basename\nin allowlist?}
    N -- No --> O[403 Forbidden\nLegacy record blocked]
    N -- Yes --> P[Execute MCP stdio\nsubprocess]

    style F fill:#f55,color:#fff
    style L fill:#f55,color:#fff
    style O fill:#f55,color:#fff
    style P fill:#5a5,color:#fff
Loading

Reviews (3): Last reviewed commit: "docs: document LITELLM_MCP_STDIO_EXTRA_C..." | Re-trigger Greptile

Comment thread litellm/proxy/_experimental/mcp_server/mcp_server_manager.py Outdated
Comment thread litellm/proxy/_types.py Outdated
Comment thread litellm/proxy/_experimental/mcp_server/mcp_server_manager.py Outdated
Comment thread litellm/proxy/_experimental/mcp_server/rest_endpoints.py Dismissed
Comment thread litellm/proxy/_experimental/mcp_server/rest_endpoints.py Dismissed
…list

- Move os and MCP_STDIO_ALLOWED_COMMANDS imports to module level in mcp_server_manager.py
- Move MCP_STDIO_ALLOWED_COMMANDS import to module level in _types.py
- Change defense-in-depth warning to HTTPException 403 for legacy non-allowlisted commands
- Ensures arbitrary command execution is blocked for both new and legacy MCP servers

Addresses Greptile review comments:
- P2: Inline imports violate CLAUDE.md style guide
- P1 security: Defense-in-depth should block, not warn, for legacy commands

Made-with: Cursor
Required by tests/documentation_tests/test_env_keys.py for os.getenv usage in constants.

Made-with: Cursor
Comment thread litellm/proxy/_experimental/mcp_server/mcp_server_manager.py
@Sameerlite
Copy link
Copy Markdown
Collaborator Author

@greptile-apps re review

@yuneng-berri yuneng-berri self-requested a review April 8, 2026 18:11
@yuneng-berri yuneng-berri merged commit 2dac54b into main Apr 8, 2026
102 of 107 checks passed
@yuneng-berri yuneng-berri deleted the litellm_fix-mcp-stdio-rce3 branch April 8, 2026 18:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants