Skip to content

Commit 42ea897

Browse files
Theodor N. EngøyTheodor N. Engøy
authored andcommitted
security: safer CORS + avoid shell exec in examples
1 parent dda845a commit 42ea897

File tree

4 files changed

+12
-15
lines changed

4 files changed

+12
-15
lines changed

examples/servers/simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless/server.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]:
127127
# for browser-based clients (ensures 500 errors get proper CORS headers)
128128
starlette_app = CORSMiddleware(
129129
starlette_app,
130-
allow_origins=["*"], # Allow all origins - adjust as needed for production
130+
# For local/dev browser clients, allow localhost origins. For production, use
131+
# a strict allowlist of known UI origins.
132+
allow_origin_regex=r"https?://(localhost|127\\.0\\.0\\.1)(:\\d+)?",
131133
allow_methods=["GET", "POST", "DELETE"], # MCP streamable HTTP methods
132134
expose_headers=["Mcp-Session-Id"],
133135
)

examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]:
152152
# for browser-based clients (ensures 500 errors get proper CORS headers)
153153
starlette_app = CORSMiddleware(
154154
starlette_app,
155-
allow_origins=["*"], # Allow all origins - adjust as needed for production
155+
# For local/dev browser clients, allow localhost origins. For production, use
156+
# a strict allowlist of known UI origins.
157+
allow_origin_regex=r"https?://(localhost|127\\.0\\.0\\.1)(:\\d+)?",
156158
allow_methods=["GET", "POST", "DELETE"], # MCP streamable HTTP methods
157159
expose_headers=["Mcp-Session-Id"],
158160
)

examples/snippets/clients/url_elicitation_client.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424

2525
import asyncio
2626
import json
27-
import subprocess
28-
import sys
2927
import webbrowser
3028
from typing import Any
3129
from urllib.parse import urlparse
@@ -124,12 +122,9 @@ def extract_domain(url: str) -> str:
124122
def open_browser(url: str) -> None:
125123
"""Open URL in the default browser."""
126124
try:
127-
if sys.platform == "darwin":
128-
subprocess.run(["open", url], check=False)
129-
elif sys.platform == "win32":
130-
subprocess.run(["start", url], shell=True, check=False)
131-
else:
132-
webbrowser.open(url)
125+
if not webbrowser.open(url, new=2):
126+
print("Could not open browser automatically.")
127+
print(f"Please manually open: {url}")
133128
except Exception as e:
134129
print(f"Failed to open browser: {e}")
135130
print(f"Please manually open: {url}")

src/mcp/cli/cli.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import importlib.metadata
44
import importlib.util
55
import os
6+
import shutil
67
import subprocess
78
import sys
89
from pathlib import Path
@@ -44,13 +45,10 @@ def _get_npx_command():
4445
if sys.platform == "win32":
4546
# Try both npx.cmd and npx.exe on Windows
4647
for cmd in ["npx.cmd", "npx.exe", "npx"]:
47-
try:
48-
subprocess.run([cmd, "--version"], check=True, capture_output=True, shell=True)
48+
if shutil.which(cmd):
4949
return cmd
50-
except subprocess.CalledProcessError:
51-
continue
5250
return None
53-
return "npx" # On Unix-like systems, just use npx
51+
return shutil.which("npx")
5452

5553

5654
def _parse_env_var(env_var: str) -> tuple[str, str]: # pragma: no cover

0 commit comments

Comments
 (0)