fix(mcp): block arbitrary command execution via stdio transport#25343
fix(mcp): block arbitrary command execution via stdio transport#25343yuneng-berri merged 4 commits intomainfrom
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes a critical RCE vulnerability in LiteLLM's MCP stdio transport by adding a command allowlist, Pydantic validation, and Key changes:
Confidence Score: 5/5Safe 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.
|
| 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
Reviews (3): Last reviewed commit: "docs: document LITELLM_MCP_STDIO_EXTRA_C..." | Re-trigger Greptile
…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
|
@greptile-apps re review |
Relevant issues
Fixes critical RCE vulnerability in MCP stdio test endpoints.
Pre-Submission checklist
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewType
🐛 Bug Fix
Changes
MCP_STDIO_ALLOWED_COMMANDSconstant (npx,uvx,python,python3,node,docker,deno) inlitellm/constants.py. Extensible viaLITELLM_MCP_STDIO_EXTRA_COMMANDSenv var.NewMCPServerRequestandUpdateMCPServerRequestnow validate stdio commands against the allowlist, blockingbash,sh,/bin/bash, etc._create_mcp_clientinmcp_server_manager.pyalso validates commands for MCPServer objects loaded from config/DB./mcp-rest/test/connectionand/mcp-rest/test/tools/listnow require PROXY_ADMIN role (403 for non-admins). Also added missingdependencies=[Depends(user_api_key_auth)]totest_tools_list.docker/README.mdcorrectedMASTER_KEY→LITELLM_MASTER_KEY(4 occurrences).