Skip to content

Commit c516b8b

Browse files
committed
fix: prevent command injection in example URL opening
Replace platform-specific subprocess calls with webbrowser.open() and add URL scheme validation to the elicitation example client. The previous Windows code path used shell=True with subprocess, which allowed command injection via crafted URLs containing shell metacharacters (e.g., & as a command separator in cmd.exe). Changes: - Remove subprocess/sys imports, use webbrowser.open() for all platforms - Add URL scheme allowlist (http/https only) in handle_url_elicitation, validated before prompting the user for consent - Return ElicitResult(action='decline') for disallowed schemes instead of silently continuing with action='accept' - Simplify open_browser() to a pure browser-opening helper - Align with the safe pattern already used in the OAuth example client
1 parent fc57c2c commit c516b8b

File tree

1 file changed

+14
-11
lines changed

1 file changed

+14
-11
lines changed

examples/snippets/clients/url_elicitation_client.py

Lines changed: 14 additions & 11 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
@@ -56,15 +54,19 @@ async def handle_elicitation(
5654
)
5755

5856

57+
ALLOWED_SCHEMES = {"http", "https"}
58+
59+
5960
async def handle_url_elicitation(
6061
params: types.ElicitRequestParams,
6162
) -> types.ElicitResult:
6263
"""Handle URL mode elicitation - show security warning and optionally open browser.
6364
6465
This function demonstrates the security-conscious approach to URL elicitation:
65-
1. Display the full URL and domain for user inspection
66-
2. Show the server's reason for requesting this interaction
67-
3. Require explicit user consent before opening any URL
66+
1. Validate the URL scheme before prompting the user
67+
2. Display the full URL and domain for user inspection
68+
3. Show the server's reason for requesting this interaction
69+
4. Require explicit user consent before opening any URL
6870
"""
6971
# Extract URL parameters - these are available on URL mode requests
7072
url = getattr(params, "url", None)
@@ -75,6 +77,12 @@ async def handle_url_elicitation(
7577
print("Error: No URL provided in elicitation request")
7678
return types.ElicitResult(action="cancel")
7779

80+
# Reject dangerous URL schemes before prompting the user
81+
parsed = urlparse(str(url))
82+
if parsed.scheme.lower() not in ALLOWED_SCHEMES:
83+
print(f"\nRejecting URL with disallowed scheme '{parsed.scheme}': {url}")
84+
return types.ElicitResult(action="decline")
85+
7886
# Extract domain for security display
7987
domain = extract_domain(url)
8088

@@ -124,12 +132,7 @@ def extract_domain(url: str) -> str:
124132
def open_browser(url: str) -> None:
125133
"""Open URL in the default browser."""
126134
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)
135+
webbrowser.open(url)
133136
except Exception as e:
134137
print(f"Failed to open browser: {e}")
135138
print(f"Please manually open: {url}")

0 commit comments

Comments
 (0)