GHSA-QG2G-G9W3-M5H8
Vulnerability from github – Published: 2026-09-18 17:16 – Updated: 2026-09-18 17:16Summary
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
$ 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
Result: Full MCP handshake succeeds:
{"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
$ 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
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:
{"jsonrpc":"2.0","id":3,"result":{"content":[{"text":"Allowed directories:\n/tmp","type":"text"}]}}
3. Container can reach other host services
# Kubernetes API
$ docker exec <container_id> wget -qO- --no-check-certificate https://host.docker.internal:6443/version
{"major":"1","minor":"34","gitVersion":"v1.34.1"...}
# Ollama LLM API
$ docker exec <container_id> 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:
- Port-scan
host.docker.internalto discover listening services - Attempt MCP handshakes on discovered ports
- Call privileged tools (e.g.,
execute_command("rm -rf /")orwrite_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
insecure_allow_all: trueas default — permits outbound connections to any destination includinghost.docker.internal- No authentication on ToolHive API / MCP proxies — any client that can reach the port can interact fully
- Docker's
host.docker.internalDNS — resolves to the host machine, bypassing localhost-only binding assumptions
Suggested Mitigations
Short-term
- Block
host.docker.internaland172.17.0.1(Docker gateway) in container networking by default, even wheninsecure_allow_allis 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_profileinfrastructure. 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
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/stacklok/toolhive"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.30.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-58197"
],
"database_specific": {
"cwe_ids": [
"CWE-284",
"CWE-306"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-18T17:16:02Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\nA 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\u0027s 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.\n\n## Severity\n\n**High** \u2014 This bypasses the container isolation model that is ToolHive\u0027s core security value proposition.\n\n## Reproduction\n\nAll tests performed from inside the `filesystem` MCP container (`docker.io/mcp/filesystem:latest`), started with default settings via `thv run filesystem -- /tmp`.\n\n### 1. Container can reach the ToolHive control plane MCP endpoint\n\n```bash\n$ docker exec \u003ccontainer_id\u003e wget -qO- \\\n --header=\"Content-Type: application/json\" \\\n --header=\"Accept: application/json\" \\\n --post-data=\u0027{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{},\"clientInfo\":{\"name\":\"evil-mcp\",\"version\":\"1.0\"}},\"id\":1}\u0027 \\\n http://host.docker.internal:50444/mcp\n```\n\n**Result:** Full MCP handshake succeeds:\n```json\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"logging\":{},\"tools\":{}},\"serverInfo\":{\"name\":\"toolhive-mcp\",\"version\":\"v0.9.3\"}}}\n```\n\n### 2. Container can connect to another MCP server\u0027s proxy and call its tools\n\n```bash\n$ docker exec \u003ccontainer_id\u003e wget -qO- \\\n --header=\"Content-Type: application/json\" \\\n --header=\"Accept: application/json\" \\\n --post-data=\u0027{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"params\":{},\"id\":2}\u0027 \\\n http://host.docker.internal:64965/mcp\n```\n\n**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:\n\n```json\n{\"jsonrpc\":\"2.0\",\"id\":3,\"result\":{\"content\":[{\"text\":\"Allowed directories:\\n/tmp\",\"type\":\"text\"}]}}\n```\n\n### 3. Container can reach other host services\n\n```bash\n# Kubernetes API\n$ docker exec \u003ccontainer_id\u003e wget -qO- --no-check-certificate https://host.docker.internal:6443/version\n{\"major\":\"1\",\"minor\":\"34\",\"gitVersion\":\"v1.34.1\"...}\n\n# Ollama LLM API\n$ docker exec \u003ccontainer_id\u003e wget -qO- http://host.docker.internal:11434/api/tags\n{\"models\":[{\"name\":\"kimi-k2:1t-cloud\"...}]}\n```\n\n## Attack Scenarios\n\n### Scenario 1: Malicious MCP server pivots to privileged native MCP tools\n\nMany 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:\n\n1. Port-scan `host.docker.internal` to discover listening services\n2. Attempt MCP handshakes on discovered ports\n3. Call privileged tools (e.g., `execute_command(\"rm -rf /\")` or `write_file(\"/etc/crontab\", \"...\")`)\n\nThis achieves **full host compromise without any container escape vulnerability**.\n\n### Scenario 2: Compromised MCP server manipulates ToolHive itself\n\nVia the unauthenticated ToolHive MCP endpoint on port 50444, a compromised container could potentially:\n\n- List and stop other running MCP servers (denial of service)\n- Start new MCP servers with attacker-controlled images\n- Modify configurations\n\n### Scenario 3: Data exfiltration via cross-MCP-server access\n\nA low-privilege MCP server (e.g., `sequentialthinking` with no file mounts) could reach the `filesystem` server\u0027s proxy and call `read_file` to access files it was never authorized to see.\n\n### Scenario 4: LLM model theft / abuse\n\nAs demonstrated, the container can reach Ollama\u0027s API and could enumerate models, run inference, or exfiltrate model weights from self-hosted LLMs.\n\n## Root Causes\n\n1. **`insecure_allow_all: true` as default** \u2014 permits outbound connections to any destination including `host.docker.internal`\n2. **No authentication on ToolHive API / MCP proxies** \u2014 any client that can reach the port can interact fully\n3. **Docker\u0027s `host.docker.internal` DNS** \u2014 resolves to the host machine, bypassing localhost-only binding assumptions\n\n## Suggested Mitigations\n\n### Short-term\n\n- **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.\n- **Add authentication to MCP proxy endpoints** \u2014 even a shared secret or token per session would prevent cross-container lateral movement.\n\n### Medium-term\n\n- **Network policy per container** \u2014 ToolHive already has the `permission_profile` infrastructure. Add support for explicit allow-lists rather than just the binary none/all choice.\n- **Isolate container networks** \u2014 run each MCP server in its own Docker network with no access to the Docker bridge gateway.\n\n### Long-term\n\n- **Mutual TLS or token-based auth** between ToolHive proxy and containers, so even if network access exists, unauthorized MCP calls are rejected.\n- **Audit logging** \u2014 log all MCP tool calls with source identification so lateral movement attempts are visible.\n\n## Environment\n\n- ToolHive v0.9.3 (macOS desktop app, Docker runtime)\n- Docker Desktop for Mac (host.docker.internal enabled by default)\n- Tested with `docker.io/mcp/filesystem:latest`",
"id": "GHSA-qg2g-g9w3-m5h8",
"modified": "2026-09-18T17:16:02Z",
"published": "2026-09-18T17:16:02Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/stacklok/toolhive/security/advisories/GHSA-qg2g-g9w3-m5h8"
},
{
"type": "WEB",
"url": "https://github.com/stacklok/toolhive-studio/pull/2469"
},
{
"type": "WEB",
"url": "https://github.com/stacklok/toolhive/pull/5583"
},
{
"type": "WEB",
"url": "https://github.com/stacklok/toolhive-studio/commit/968182d7f3ee1e55123369e66ad88f82128119b0"
},
{
"type": "WEB",
"url": "https://github.com/stacklok/toolhive/commit/d8f40cb1599b8bf66657f2dfff15bfbfc236e712"
},
{
"type": "PACKAGE",
"url": "https://github.com/stacklok/toolhive"
},
{
"type": "WEB",
"url": "https://github.com/stacklok/toolhive-studio/releases/tag/v0.38.0"
},
{
"type": "WEB",
"url": "https://github.com/stacklok/toolhive/releases/tag/v0.30.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:A/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "ToolHive: containerized MCP servers can reach host services via host.docker.internal, enabling lateral movement"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.