diff --git a/docs/configuration.md b/docs/configuration.md index 6c6bc060c..0acf27e69 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -88,7 +88,7 @@ A running MCPProxy core watches `mcp_config.json` and hot-reloads external edits | Field | Type | Default | Description | |-------|------|---------|-------------| -| `enable_socket` | boolean | `true` | Enable Unix socket (macOS/Linux) or named pipe (Windows) for secure local IPC between tray and core | +| `enable_socket` | boolean | `true` | Enable the Unix socket (macOS/Linux) or named pipe (Windows) the tray uses to talk to the core. **Connections over it are trusted without the API key**, and that includes every stdio server the proxy itself spawns under `none` or `sandbox` isolation: such a server can call the admin API and mint agent tokens with no credential. The tray is the only socket client, so **set `false` on headless installs, and whenever a stdio server you do not fully trust runs outside `docker` isolation**. `mcpproxy doctor` warns about the combination. See [What the sandbox does not hide](features/sandbox-isolation.md#what-the-sandbox-does-not-hide). | | `tray_endpoint` | string | `""` | Override socket/pipe path (advanced, usually not needed) | ### Search & Tool Limits diff --git a/docs/configuration/config-file.md b/docs/configuration/config-file.md index 94eb1461d..3a2be0de2 100644 --- a/docs/configuration/config-file.md +++ b/docs/configuration/config-file.md @@ -63,7 +63,7 @@ MCPProxy uses a JSON configuration file located at `~/.mcpproxy/mcp_config.json` | `trusted_proxies` | string[] | `[]` (trust nobody) | CIDRs or IP addresses whose `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto` and `X-Forwarded-Host` headers are believed. Headers from any other peer are ignored and the direct `RemoteAddr` is used. Env `MCPPROXY_TRUSTED_PROXIES` (comma list). Live (hot-reload, no restart). Validation: `trusted_proxies[N] "value" is not a valid CIDR or IP address` — refused identically at boot, `PATCH /api/v1/config` and `/config/apply`. See [Reverse Proxy Deployment](/operations/reverse-proxy#trusted_proxies-forwarded-headers) | | `require_mcp_auth` | boolean | `false` | Require an API key on the `/mcp` endpoint (off by default for client compatibility). Enable when exposing MCPProxy beyond localhost. **Server edition:** forced to `true` whenever `server_edition.enabled` is `true` — an explicit `false` is not an error, but boot logs `require_mcp_auth: false is overridden to true because server_edition.enabled is true` and `mcpproxy doctor` reports the same finding | | `mcp_reject_query_apikey` | boolean | `false` | Refuse a `?apikey=` query parameter on every MCP endpoint (`/mcp*` and the legacy `/v1/tool_code` aliases) with a 401, forcing clients onto `Authorization: Bearer ` or `X-API-Key` so tokens stop appearing in access logs, proxy logs and browser history (a URL already sent is still logged in transit; the option makes the client fail until it is migrated). MCP endpoints only: the REST API, the Web UI and `/events` keep accepting the query form. Clients connected by the Connect wizard with `?apikey=` in the URL (Codex, the generic fallback) must be switched to a header carrier first. Live, no restart | -| `enable_socket` | boolean | `true` | Enable Unix socket/named pipe for local communication | +| `enable_socket` | boolean | `true` | Enable the Unix socket/named pipe the tray uses. Socket connections are trusted **without the API key**, including stdio servers spawned under `none` or `sandbox` isolation, which can then mint agent tokens. Set `false` on headless installs and whenever untrusted stdio servers run outside `docker` isolation. | | `forward_client_headers` | boolean | `true` | Master switch for [client header forwarding](/configuration/upstream-servers#client-header-forwarding). Nothing is forwarded until a server lists names in `forward_headers`. Env `MCPPROXY_FORWARD_CLIENT_HEADERS=false` disables it for the process (never persisted). | ### `audit_log` (edition-neutral JSONL audit record) diff --git a/docs/docker-isolation.md b/docs/docker-isolation.md index 7a092df1f..82381a101 100644 --- a/docs/docker-isolation.md +++ b/docs/docker-isolation.md @@ -454,7 +454,7 @@ Your options on such a host: - **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. - **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`. - **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`. -- **`none` and `sandbox` do not hide the data directory or the Unix socket.** A stdio server runs as the proxy's user, so it can read `mcp_config.json` (holds `api_key`) and `config.db` (holds the upstream OAuth tokens), and while `enable_socket` is on it can connect to `/mcpproxy.sock` without a key — a sandboxed child minted an agent token over it ([#1704](https://github.com/smart-mcp-proxy/mcpproxy-go/issues/1704)). Landlock does block `/proc//environ`, so the key is safe in the environment but not on disk or over the socket. Set `enable_socket: false` for headless installs and use `docker` isolation for servers you do not trust; `mcpproxy doctor` warns when an enabled stdio server resolves to `none` or `sandbox` with the socket on. Verification table: [What the sandbox does not hide](features/sandbox-isolation.md#what-the-sandbox-does-not-hide). +- **`none` and `sandbox` do not hide the data directory or the Unix socket.** A stdio server runs as the proxy's user, so it can read `mcp_config.json` (holds `api_key`) and `config.db` (holds the upstream OAuth tokens), and while `enable_socket` is on it can connect to `/mcpproxy.sock` without a key — a sandboxed child minted an agent token over it ([#1704](https://github.com/smart-mcp-proxy/mcpproxy-go/issues/1704)). Landlock does block `/proc//environ`, so the key is safe in the environment but not on disk or over the socket. **`enable_socket: false` is the setting that matters** when a stdio server you do not fully trust runs outside `docker` isolation (headless installs lose nothing: the tray is the only socket client); use `docker` isolation for such servers where you can; `mcpproxy doctor` warns when an enabled stdio server resolves to `none` or `sandbox` with the socket on. Verification table: [What the sandbox does not hide](features/sandbox-isolation.md#what-the-sandbox-does-not-hide). - **Docker-based scanners do not run under `sandbox`/`none`.** They are skipped and the skip is surfaced via the informational `deep_scan` descriptor — it never downgrades the baseline verdict (Spec 077 FR-008). A native scanner runtime is a future enhancement (D3 option a). ## Platform support matrix diff --git a/docs/features/docker-isolation.md b/docs/features/docker-isolation.md index 3e810ead8..5b2150238 100644 --- a/docs/features/docker-isolation.md +++ b/docs/features/docker-isolation.md @@ -424,7 +424,7 @@ See [Shutdown Behavior](/operations/shutdown-behavior) for detailed subprocess l - **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. - **Linux-only.** Landlock is a Linux 5.13+ feature. On older kernels the launcher degrades best-effort (fewer access-right bits enforced). On macOS/Windows `sandbox` is a documented **no-op** and behaves like `none`. - **Filesystem + resources only.** Landlock confines the filesystem write-allowlist; it does not provide network namespacing. For network-sensitive servers, use `docker` mode with `network_mode: none`. -- **`none` and `sandbox` do not hide the data directory or the Unix socket.** A stdio server runs as the proxy's user, so it can read `mcp_config.json` (holds `api_key`) and `config.db` (holds the upstream OAuth tokens), and while `enable_socket` is on it can connect to `/mcpproxy.sock` without a key — a sandboxed child minted an agent token over it ([#1704](https://github.com/smart-mcp-proxy/mcpproxy-go/issues/1704)). Landlock does block `/proc//environ`, so the key is safe in the environment but not on disk or over the socket. Set `enable_socket: false` for headless installs and use `docker` isolation for servers you do not trust; `mcpproxy doctor` warns when an enabled stdio server resolves to `none` or `sandbox` with the socket on. Verification table: [What the sandbox does not hide](/features/sandbox-isolation#what-the-sandbox-does-not-hide). +- **`none` and `sandbox` do not hide the data directory or the Unix socket.** A stdio server runs as the proxy's user, so it can read `mcp_config.json` (holds `api_key`) and `config.db` (holds the upstream OAuth tokens), and while `enable_socket` is on it can connect to `/mcpproxy.sock` without a key — a sandboxed child minted an agent token over it ([#1704](https://github.com/smart-mcp-proxy/mcpproxy-go/issues/1704)). Landlock does block `/proc//environ`, so the key is safe in the environment but not on disk or over the socket. **`enable_socket: false` is the setting that matters** when a stdio server you do not fully trust runs outside `docker` isolation (headless installs lose nothing: the tray is the only socket client); use `docker` isolation for such servers where you can; `mcpproxy doctor` warns when an enabled stdio server resolves to `none` or `sandbox` with the socket on. Verification table: [What the sandbox does not hide](/features/sandbox-isolation#what-the-sandbox-does-not-hide). - **Docker-based scanners do not run under `sandbox`/`none`.** They are skipped and the skip is surfaced via the informational `deep_scan` descriptor — it never downgrades the baseline verdict (Spec 077 FR-008). A native scanner runtime is a future enhancement. ## Platform support matrix diff --git a/docs/features/sandbox-isolation.md b/docs/features/sandbox-isolation.md index 904ceaa80..1ea26e40a 100644 --- a/docs/features/sandbox-isolation.md +++ b/docs/features/sandbox-isolation.md @@ -15,6 +15,15 @@ sandboxes, Landlock does **not** require unprivileged user namespaces, so it is not blocked by `kernel.apparmor_restrict_unprivileged_userns=1` (default on Ubuntu 23.10+). +:::caution The sandbox confines writes, not identity +A stdio server under `sandbox` (or `none`) still runs as the proxy's user. It can +read `mcp_config.json` and `config.db`, and while `enable_socket` is on it can +connect to the admin socket **without a key and mint itself an agent token**, in +both modes. If you run stdio servers you do not fully trust outside `docker` +isolation, **`enable_socket: false` is the setting that matters**. Details and +the verified matrix: [What the sandbox does not hide](#what-the-sandbox-does-not-hide). +::: + ## Enabling Set the isolation **mode** to `sandbox`, globally or per server: @@ -111,8 +120,9 @@ readable in `none` and `sandbox`. What to do: -- **Headless installs** (no tray): set `enable_socket: false`. The tray is the - only socket client, so a headless service loses nothing. +- **Set `enable_socket: false`** unless you use the tray. This is the one + setting that closes the token-minting path in both `none` and `sandbox`; the + tray is the only socket client, so a headless service loses nothing. - **Servers you do not trust**: use `docker` isolation (`docker_isolation.mode: "docker"`, or per-server `isolation.mode`). The container does not mount the data directory, so neither the files nor the diff --git a/docs/features/security-quarantine.md b/docs/features/security-quarantine.md index c5885500f..af042db43 100644 --- a/docs/features/security-quarantine.md +++ b/docs/features/security-quarantine.md @@ -219,6 +219,7 @@ What it does not do: - **It is not proof of safety.** Review is only as good as the reviewer, and the offline scanners can miss attacks. A clean scan means the checks found nothing in the captured definitions; it is not evidence that a server or tool is safe. Manual review, scanner-assisted review and agent-assisted inspection (`inspect_quarantined`) are inputs to your decision. None of them approves anything by itself. - **It does not see behavior outside the definition.** The hash covers name, description, schemas and the safety-relevant annotation hints. A server whose implementation or backend changes while its definitions stay identical is not detected, and neither is a malicious tool result returned at call time. - **Trust modes change the guarantee.** `scan` (the default since spec 119) admits a new server, a new tool and a changed tool contract automatically when the offline scan is clean, so a detector miss — including a paraphrased rug pull that scans clean — is admitted at that point; findings, uncovered tools and safer-direction hint flips are held. `manual` holds every new server and every tool contract change until it is approved: server release needs a person, while tool approval can also come from an admin-access MCP client. `auto` admits without scanning or holding at all and trusts tool changes (rug-pull risk). Server admission and tool-change review are separate controls: `"quarantined": false` on a server entry only releases the server-level quarantine, and under `manual` trust a new or changed tool on that server is still held after its baseline. Tool-change holds are removed by `trust_mode: auto`, the legacy `auto_approve_tool_changes: true` (which an explicit `trust_mode` overrides), or `quarantine_enabled: false` globally. See [Trust modes](#trust-modes-auto--scan--manual). +- **It gates definitions, not processes.** Quarantine decides which tool definitions agents may see and call. A stdio server the proxy spawns under `none` or `sandbox` isolation still runs as the proxy's user: it can read the data directory and, while `enable_socket` is on, mint agent tokens over the admin socket with no key, whatever its quarantine state. For such servers `enable_socket: false` or `docker` isolation is the control, not approval. See [What the sandbox does not hide](./sandbox-isolation.md#what-the-sandbox-does-not-hide). - **It is not data-loss prevention.** See [Sensitive Data Detection](./sensitive-data-detection.md) and [Output Sanitisation](./output-sanitisation.md) for what is flagged and what, if anything, is blocked. ## Managing Quarantine