You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(security): scanner-flow parity under sandbox isolation (MCP-34.4)
D3 option (b) from the MCP-34 spike: clean, surfaced degradation. Under a
non-Docker isolation mode (sandbox/none) the Docker-based scanner plugins
(Spec 039) cannot run, so the scan engine now skips them with an honest,
mode-specific reason pointing at MCPX_DOCKER_SNAP_APPARMOR instead of the
misleading "pull the image" guidance. The always-on in-process
tpa-descriptions scanner still runs, and the skipped Docker scanners stay in
the resolved set (recorded as failed) so the existing coverage check downgrades
the server's security_scan.status to "degraded" rather than a silent all-clear.
- Engine: new isolationMode gate in resolveScanners/checkImage
- Service: SetIsolationMode setter (+ startup WARN), wired in server.go from
cfg.DockerIsolation.ResolvedMode()
- Tests: engine skip under sandbox/none + docker-mode back-compat guard +
service setter propagation
- Docs: rewrite docs/docker-isolation.md to cover all three modes (docker/
sandbox/none), the snap-docker AppArmor failure mode, the honest uid/gid
limitation, and a platform support matrix; add sandbox option to the
MCPX_DOCKER_SNAP_APPARMOR error doc
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Copy file name to clipboardExpand all lines: docs/docker-isolation.md
+84-2Lines changed: 84 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,8 @@
1
-
# Docker Security Isolation
1
+
# Security Isolation (Docker · Sandbox · None)
2
2
3
-
MCPProxy provides Docker isolation for stdio MCP servers to enhance security by running each server in its own isolated container.
3
+
MCPProxy can confine stdio MCP servers so a malicious or buggy server cannot freely touch the host. There are **three isolation modes** — `docker`, `sandbox`, and `none` — selected by `docker_isolation.mode` (global) or `isolation.mode` (per-server). This document covers all three; most of it describes the **Docker** mode (the default and most capable), with the **Sandbox** mode and the **scanner behaviour under each mode** in [Isolation Modes](#isolation-modes) below.
4
+
5
+
> **Naming note:** the global config key is still `docker_isolation` for backward compatibility, but its `mode` field selects any of the three modes — it is not Docker-only.
4
6
5
7
> **New installs:** Docker isolation is turned on automatically when mcpproxy creates its initial `mcp_config.json` and a Docker daemon is reachable (`docker info` responds within 2 seconds). If Docker isn't available at first run, isolation stays off so stdio servers still work — you can enable it later from the **Security** page in the Web UI or by editing the config below.
- **Resource Limits**: Memory and CPU limits prevent resource exhaustion
27
29
- **Automatic Runtime Detection**: Maps commands to appropriate Docker images
28
30
31
+
## Isolation Modes
32
+
33
+
MCPProxy resolves an **isolation mode** for every stdio server. Set it globally with `docker_isolation.mode` and override per-server with `isolation.mode`:
34
+
35
+
| Mode | What it does | Where it works | uid/gid drop |
| `docker` | Wraps the server in a Docker container (process/FS/network isolation, resource limits). The default and most capable mode. | Any host with a working Docker daemon. | Yes (container user) |
38
+
| `sandbox` | Runs the server **natively** under a Linux [Landlock](https://docs.kernel.org/userspace-api/landlock.html) filesystem allowlist + `setrlimit` resource caps — **no Docker required**. For hosts where Docker isolation is unavailable or broken (e.g. snap-docker + AppArmor). | Linux 5.13+ only (Landlock). Best-effort downgrade across ABI 1–5. macOS/Windows: documented no-op ⇒ behaves like `none`. | **No** — see [Honest limitations](#honest-limitations) |
39
+
| `none` | No confinement; the server runs directly on the host. | Everywhere. | n/a |
Per-server precedence: explicit per-server `mode` → per-server legacy `enabled` → global `mode` → global legacy `enabled`. A per-server `mode` (e.g. `none` for a trusted server) overrides the global gate.
61
+
62
+
### Sandbox mode (Landlock)
63
+
64
+
`sandbox` mode confines a stdio server **without Docker** by applying a Linux Landlock LSM ruleset (a writable-path allowlist) plus `setrlimit` resource caps to the process before it `exec`s, then preserving the raw stdin/stdout JSON-RPC pipes. It is unaffected by `kernel.apparmor_restrict_unprivileged_userns=1` (it needs no user namespaces), which is exactly why it works where bubblewrap/userns-based sandboxes are blocked. See the spike write-up in [docs/development/sandbox-spike-mcp-34.md](development/sandbox-spike-mcp-34.md) for the mechanism comparison and PoC.
65
+
66
+
### Scanner behaviour under each mode (MCP-34.4)
67
+
68
+
The security **scanner plugins** (Spec 039) are Docker-based. Under a non-Docker isolation mode they cannot run, so MCPProxy **degrades cleanly and surfaces it** rather than failing silently:
69
+
70
+
| Mode | Docker scanner plugins | In-process scanner (`tpa-descriptions`) | Scan result for a server with only Docker scanners |
|`sandbox` / `none`|**Skipped** with an honest, mode-specific reason pointing at [`MCPX_DOCKER_SNAP_APPARMOR`](errors/MCPX_DOCKER_SNAP_APPARMOR.md)|**Still runs**|`security_scan.status: "degraded"` (a low/zero risk score from incomplete coverage is not reported as a trustworthy all-clear) |
74
+
75
+
This is **decision D3 option (b)** from the [MCP-34 spike](development/sandbox-spike-mcp-34.md#recommendation-for-the-d3-scanner-question): clean, surfaced degradation. A native (non-Docker) scanner runtime — option (a) — is a larger follow-up and is not yet implemented. To run the full Docker-based scanner fleet, use `mode: docker` on a host with a working Docker daemon, or replace snap-docker with a distro Docker package (see the error doc).
76
+
77
+
The skip is also logged at startup:
78
+
79
+
```
80
+
WARN Isolation mode runs no Docker for scanner plugins; Docker-based scanners will be skipped … {"isolation_mode": "sandbox"}
81
+
```
82
+
29
83
## Configuration
30
84
31
85
### Global Docker Isolation
@@ -297,6 +351,34 @@ docker stats
297
351
exists at the bundle path above, or pre-pull the image with
298
352
`docker pull <image>`.
299
353
354
+
## Snap-docker (AppArmor) failure mode
355
+
356
+
On Ubuntu hosts where Docker is installed via **snap**, AppArmor's profile transition fights the security flags the scanner sandbox requires (`--security-opt no-new-privileges` + a pinned AppArmor profile), so in-container commands fail with *operation not permitted*. This is the original driver for non-Docker `sandbox` mode. Symptoms, root cause, and fixes are documented in [`docs/errors/MCPX_DOCKER_SNAP_APPARMOR.md`](errors/MCPX_DOCKER_SNAP_APPARMOR.md). The related systemd/snap-confine variant for *upstream* docker servers is detected by `mcpproxy doctor` (issue #457).
357
+
358
+
Your options on such a host:
359
+
360
+
1. Replace snap Docker with a distro/upstream Docker package (full Docker mode works).
361
+
2. Set `docker_isolation.mode: "sandbox"` — stdio servers are confined natively with Landlock; Docker-based scanners degrade cleanly (see [Scanner behaviour](#scanner-behaviour-under-each-mode-mcp-344)).
362
+
3. Set `security.scanner_disable_no_new_privileges: true` to drop the `no-new-privileges` flag from scanner containers (weakens scanner hardening; prefer 1 or 2).
363
+
364
+
## Honest limitations
365
+
366
+
`sandbox` mode is deliberately scoped. Known limitations:
367
+
368
+
-**No uid/gid drop.** Dropping to an unprivileged uid/gid requires `CAP_SETUID`/`CAP_SETGID` (i.e. running as root). When mcpproxy runs unprivileged, the uid/gid drop is **best-effort and typically a no-op** — the sandboxed process keeps the launching user's identity. Landlock (filesystem) and `setrlimit` (resource caps) still apply. Docker mode does drop to a container user. This is an honest trade-off, not a bug.
369
+
-**Linux-only.** Landlock is a Linux 5.13+ feature. On older kernels the launcher degrades best-effort (fewer access-right bits enforced on ABI 1). On macOS/Windows `sandbox` is a documented **no-op** and behaves like `none`.
370
+
-**Filesystem + resources only.** Landlock confines the filesystem write-allowlist; it does not provide network namespacing. Pair with care for network-sensitive servers, or use `docker` mode with `network_mode: none`.
371
+
-**Docker-based scanners do not run under `sandbox`/`none`.** They are skipped (the scan reports `degraded`). A native scanner runtime is a future enhancement (D3 option a).
0 commit comments