ADR-027: Sandbox Security Policy

Context

ADR-026 established OS-native sandboxing as the mechanism for isolating MCP server processes. This ADR documents the security policy decisions: what the sandbox allows and denies by default, how it handles failure, and how user-configured paths are applied.

These decisions balance security (restrict as much as possible) against usability (sensible defaults that work for most servers with minimal configuration).

Decision

Fail-closed behavior

If a server has sandbox: {enabled: true} but no sandbox engine is available (e.g., Linux without bubblewrap installed, or Windows), Gatekit refuses to start the server rather than running it unsandboxed. This prevents a configuration that promises isolation from silently degrading to no isolation.

The error message includes platform-specific installation instructions (e.g., apt install bubblewrap on Debian/Ubuntu).

Why not fail-open? A user who enables sandboxing has made a deliberate security decision. Silently ignoring it undermines trust and creates a false sense of security. The cost of a clear error is far lower than the cost of running an untrusted server without the isolation the user expects.

Filesystem policy: deny-all default with allowlist

The baseline is deny all filesystem access, then explicitly allow what the server needs:

System paths (implicitly readable):

Read-write (allowed by default):

Always denied (sensitive credential directories):

Implementation differs by engine:

Both approaches require symlink-aware path resolution to prevent traversal bypasses.

Why deny-all rather than deny-writes-only? A denylist approach (read everything, deny 10 known paths) is fundamentally weak: an MCP server with read access to the full filesystem plus network access can exfiltrate any sensitive file not on the deny list (.env files, database configs, API keys, etc.). Industry consensus (Docker, Flatpak, Deno, macOS App Sandbox) is deny-all with explicit allowlists.

Network: allowed by default

Most MCP servers need external API access (e.g., Context7 calls external APIs, web search servers fetch URLs). Denying network by default would break the majority of servers and require users to understand which servers need network access.

When explicitly denied (network: false):

Why not deny by default? Unlike filesystem writes (where the damage from an accidental write is immediate and local), network access is fundamental to most MCP server functionality. Denying it by default would make the sandbox unusable without per-server configuration, defeating the "just works" goal.

Explicit paths only (no workspace inference)

Workspace paths must be explicitly configured via the paths field. There is no automatic inference from command arguments.

Why explicit-only rather than heuristic inference? Workspace inference from command arguments was considered but rejected:

  1. Security risk: Implicit magic that grants write access based on heuristics is counter to the security principle of explicit-over-implicit
  2. Unpredictable: Not all directory arguments are workspaces (some are read-only reference paths)
  3. Industry convention: Docker, Flatpak, Deno, and macOS App Sandbox all require explicit path configuration
  4. Clarity: Users should know exactly what their sandbox allows — no hidden behavior

Sandbox is opt-in

Sandboxing defaults to disabled (enabled: false) in YAML configuration. Users must explicitly enable it per server. The TUI defaults new servers to sandbox-enabled when a backend is available, since the TUI is an interactive setup where the secure default is appropriate.

Why not opt-out (enabled by default)? Sandboxing can break servers that write to unexpected locations. An opt-in approach lets users enable it deliberately and debug any issues, rather than discovering their server is broken and having to figure out why. As the feature matures and we gain confidence in the defaults, we may revisit this.

Consequences

Positive

Negative

Future considerations

Status

Accepted