GitHub Advisoryadvisory · Go

GHSA-qg2g-g9w3-m5h8 ToolHive: containerized MCP servers can reach host services via host.docker.internal, enabling lateral movement

HIGH Published 2026-09-18 Modified 2026-09-18 Official page ↗
Aliases
CVE-2026-58197
Ecosystem
Go
Packages
github.com/stacklok/toolhive
Severity
High
CVSS vector
CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
Weaknesses
CWE-284, CWE-306
Reviewed
2026-09-18

Affected packages

• Go github.com/stacklok/toolhive: < 0.30.1 → fixed in 0.30.1

Description

Summary A containerized MCP server running with the default `network` permission profile (`insecure_allow_all: true`) can reach host-local services via `host.docker.internal`. This includes the ToolHive API itself, other ToolHive-managed MCP server proxies, and any other service listening on the host's localhost. Combined with the unauthenticated ToolHive API and MCP proxy endpoints, this enables a compromised or malicious MCP server to perform lateral movement without any container escape. Severity High — This bypasses the container isolation model that is ToolHive's core security value proposition. Reproduction All tests performed from inside the `filesystem` MCP container (`docker.io/mcp/filesystem:latest`), started with default settings via `thv run filesystem -- /tmp`. 1. Container can reach the ToolHive control plane MCP endpoint ```bash $ docker exec wget -qO- \ --header="Content-Type: application/json" \ --header="Accept: application/json" \ --post-data='{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"evil-mcp","version":"1.0"}},"id":1}' \ http://host.docker.internal:50444/mcp ``` Result: Full MCP handshake succeeds: ```json {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-03-26","capabilities":{"logging":{},"tools":{}},"serverInfo":{"name":"toolhive-mcp","version":"v0.9.3"}}} ``` 2. Container can connect to another MCP server's proxy and call its tools ```bash $ docker exec wget -qO- \ --header="Content-Type: application/json" \ --header="Accept: application/json" \ --post-data='{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}' \ http://host.docker.internal:64965/mcp ``` Result: Returns the full tool list of the target MCP server (read_file, write_file, edit_file, move_file, etc.), and tools can be called: ```json {"jsonrpc":"2.0","id":3,"result":{"content":[{"text":"Allowed directories:\n/tmp","type":"text"}]}} ``` 3. Container can reach other host services ```bash Kubernetes API $ docker exec wget -qO- --no-check-certificate https://host.docker.internal:6443/version {"major":"1","minor":"34","gitVersion":"v1.34.1"...} Ollama LLM API $ docker exec wget -qO- http://host.docker.internal:11434/api/tags {"models":[{"name":"kimi-k2:1t-cloud"...}]} ``` Attack Scenarios Scenario 1: Malicious MCP server pivots to privileged native MCP tools Many users run native (non-containerized) MCP servers like Desktop Commander, terminal servers, or custom tools that have `execute_command`, `write_file`, or `shell` capabilities with full host access. These typically listen on localhost ports. A malicious containerized MCP server can: 1. Port-scan `host.docker.internal` to discover listening services 2. Attempt MCP handshakes on discovered ports 3. Call privileged tools (e.g., `execute_command("rm -rf /")` or `write_file("/etc/crontab", "...")`) This achieves full host compromise without any container escape vulnerability. Scenario 2: Compromised MCP server manipulates ToolHive itself Via the unauthenticated ToolHive MCP endpoint on port 50444, a compromised container could potentially: • List and stop other running MCP servers (denial of service) • Start new MCP servers with attacker-controlled images • Modify configurations Scenario 3: Data exfiltration via cross-MCP-server access A low-privilege MCP server (e.g., `sequentialthinking` with no file mounts) could reach the `filesystem` server's proxy and call `read_file` to access files it was never authorized to see. Scenario 4: LLM model theft / abuse As demonstrated, the container can reach Ollama's API and could enumerate models, run inference, or exfiltrate model weights from self-hosted LLMs. Root Causes 1. `insecure_allow_all: true` as default — permits outbound connections to any destination including `host.docker.internal` 2. No authentication on ToolHive API / MCP proxies — any client that can reach the port can interact fully 3. Docker's `host.docker.internal` DNS — resolves to the host machine, bypassing localhost-only binding assumptions Suggested Mitigations Short-term • Block `host.docker.internal` and `172.17.0.1` (Docker gateway) in container networking by default, even when `insecure_allow_all` is enabled. These should require explicit opt-in. • Add authentication to MCP proxy endpoints — even a shared secret or token per session would prevent cross-container lateral movement. Medium-term • Network policy per container — ToolHive already has the `permission_profile` infrastructure. Add support for explicit allow-lists rather than just the binary none/all choice. • Isolate container networks — run each MCP server in its own Docker network with no access to the Docker bridge gateway. Long-term • Mutual TLS or token-based auth between ToolHive proxy and containers, so even if network access exists, unauthorized MCP calls are rejected. • Audit logging — log all MCP tool calls with source identification so lateral movement attempts are visible. Environment • ToolHive v0.9.3 (macOS desktop app, Docker runtime) • Docker Desktop for Mac (host.docker.internal enabled by default) • Tested with `docker.io/mcp/filesystem:latest`

Related

Alias · CVE (1)

  • CVE-2026-58197 ToolHive is a utility designed to simplify the deployment and management of Model Context Protocol servers. vulnerability

Weakness · CWE (2)

  • CWE-284 Improper Access Control weakness
  • CWE-306 Missing Authentication for Critical Functionality weakness

References