T09 · Insecure Skill Coding Practices
- Location
server.py:28- Finding
Server-Side Request Forgery Through Unrestricted Base URL Override
- Content
View full analysis
str: """Get the base URL, using default if not provided.""" return base_url or DEFAULT_BASE_URL async def make_request( method: str, url: str, content: str | None = None, headers: dict[str, str] | None = None, ) -> httpx.Response: """Make an HTTP request to SilverBullet.""" async with httpx.AsyncClient(timeout=30.0) as client: request_headers = headers or {} if method == "PUT" and content is not None: request_headers["Content-Type"] = "text/markdown" response = await client.request( method=method, url=url, content=content, headers=request_headers, ) return response @mcp.tool() async def list_files(base_url: str | None = None) -> str: """ List all files in the SilverBullet space. Returns a JSON array of all files with metadata including name, lastModified, created, and permissions. Args: base_url: SilverBullet server URL (optional, uses SILVERBULLET_URL env var if not provided) """ url = f"{get_base_url(base_url)}/.fs" ``` The same unsafe pattern is used by the other MCP tools at `server.py:80-81`, `112-113`, `143-144`, `174-175`, `210`, `253`, `284`, and `303`. ### Technical Analysis Every exposed MCP tool accepts an optional, caller-controlled `base_url`. The `get_base_url()` function returns this value without validating its scheme, hostname, port, DNS resolution, or destination address. The resulting URL is passed directly to `httpx.AsyncClient.request()`. Consequently, a caller can direct the MCP server to make requests from its own network context to destinations other than the configured SilverBullet instance. Potential targets include: - Loopback se ...[truncated 2456 chars]- Remediation
View remediation
