mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 15:19:10 +02:00
Compare commits
3 commits
main
...
fix/saniti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3465166106 |
||
|
|
a40e6ad2f3 |
||
|
|
cc2e3ab11f |
5 changed files with 66 additions and 4 deletions
|
|
@ -12,7 +12,7 @@ from rich import print
|
|||
from fastmcp.utilities.logging import get_logger
|
||||
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
||||
|
||||
from .shared import process_common_args
|
||||
from .shared import process_common_args, validate_server_name
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
|
@ -124,6 +124,8 @@ def install_claude_code(
|
|||
# Build the full command
|
||||
full_command = env_config.build_command(["fastmcp", "run", server_spec])
|
||||
|
||||
validate_server_name(name)
|
||||
|
||||
# Build claude mcp add command
|
||||
cmd_parts = [claude_cmd, "mcp", "add", name]
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from rich import print
|
|||
from fastmcp.utilities.logging import get_logger
|
||||
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
||||
|
||||
from .shared import process_common_args
|
||||
from .shared import process_common_args, validate_server_name
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
|
@ -129,6 +129,8 @@ def install_gemini_cli(
|
|||
for key, value in env_vars.items():
|
||||
cmd_parts.extend(["-e", f"{key}={value}"])
|
||||
|
||||
validate_server_name(name)
|
||||
|
||||
# Add server name and command
|
||||
cmd_parts.extend([name, full_command[0], "--"])
|
||||
cmd_parts.extend(full_command[1:])
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
@ -17,6 +18,26 @@ from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystem
|
|||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Server names are passed as subprocess arguments to CLI tools like `claude`
|
||||
# and `gemini`. On Windows these may resolve to .cmd/.bat wrappers that run
|
||||
# through cmd.exe, where shell metacharacters (& | ; etc.) in arguments can
|
||||
# cause command injection. Restrict names to safe characters.
|
||||
_SAFE_NAME_RE = re.compile(r"^[\w\-. ]+$")
|
||||
|
||||
|
||||
def validate_server_name(name: str) -> str:
|
||||
"""Validate that a server name is safe for use as a subprocess argument.
|
||||
|
||||
Raises SystemExit if the name contains shell metacharacters.
|
||||
"""
|
||||
if not _SAFE_NAME_RE.match(name):
|
||||
print(
|
||||
f"[red]Invalid server name '[bold]{name}[/bold]': "
|
||||
"names may only contain letters, numbers, hyphens, underscores, dots, and spaces.[/red]"
|
||||
)
|
||||
sys.exit(1)
|
||||
return name
|
||||
|
||||
|
||||
def parse_env_var(env_var: str) -> tuple[str, str]:
|
||||
"""Parse environment variable string in format KEY=VALUE."""
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from fastmcp.cli.install import install_app
|
||||
from fastmcp.cli.install.shared import validate_server_name
|
||||
from fastmcp.cli.install.stdio import install_stdio
|
||||
|
||||
|
||||
|
|
@ -455,3 +458,37 @@ class TestInstallCommandParsing:
|
|||
command, bound, _ = install_app.parse_args(cmd_args)
|
||||
assert command is not None
|
||||
assert str(bound.arguments["project"]) == str(Path("/path/to/project"))
|
||||
|
||||
|
||||
class TestServerNameValidation:
|
||||
"""Test server name validation rejects shell metacharacters."""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name",
|
||||
[
|
||||
"my-server",
|
||||
"my_server",
|
||||
"My Server",
|
||||
"server.v2",
|
||||
"test123",
|
||||
],
|
||||
)
|
||||
def test_valid_names(self, name: str):
|
||||
assert validate_server_name(name) == name
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name",
|
||||
[
|
||||
"test&calc",
|
||||
"test|whoami",
|
||||
"test;ls",
|
||||
"test$(id)",
|
||||
"test`id`",
|
||||
'test"quoted',
|
||||
"test>file",
|
||||
"test<file",
|
||||
],
|
||||
)
|
||||
def test_rejects_shell_metacharacters(self, name: str):
|
||||
with pytest.raises(SystemExit):
|
||||
validate_server_name(name)
|
||||
|
|
|
|||
|
|
@ -428,13 +428,13 @@ async def test_fn_with_kwargs_dropped_args_not_in_kwargs(add_tool):
|
|||
|
||||
async def test_forward_outside_context_raises_error():
|
||||
"""Test that forward() raises error when called outside transform context."""
|
||||
with pytest.raises(RuntimeError, match="forward\(\) can only be called"):
|
||||
with pytest.raises(RuntimeError, match=r"forward\(\) can only be called"):
|
||||
await forward(x=1)
|
||||
|
||||
|
||||
async def test_forward_raw_outside_context_raises_error():
|
||||
"""Test that forward_raw() raises error when called outside transform context."""
|
||||
with pytest.raises(RuntimeError, match="forward_raw\(\) can only be called"):
|
||||
with pytest.raises(RuntimeError, match=r"forward_raw\(\) can only be called"):
|
||||
await forward_raw(x=1)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue