Skip to content

feat(upstream): native sandbox launcher (Landlock + rlimits) for stdio servers (MCP-34.3)#768

Open
Dumbris wants to merge 1 commit into
mainfrom
feat/mcp-3234-sandbox-launcher
Open

feat(upstream): native sandbox launcher (Landlock + rlimits) for stdio servers (MCP-34.3)#768
Dumbris wants to merge 1 commit into
mainfrom
feat/mcp-3234-sandbox-launcher

Conversation

@Dumbris

@Dumbris Dumbris commented Jun 25, 2026

Copy link
Copy Markdown
Member

What

Implements the sandbox spawn branch alongside docker/plain in connectStdio and buildLauncherCmd (MCP-34.3), using the Landlock mechanism proven by the MCP-34.1 spike (#754) and wired into the mode-aware selector from MCP-34.2 (#759).

Mechanism — re-exec wrapper

Landlock confines the current process and every process it then execs, irreversibly — so it can't be applied in-process before mcp-go spawns the server. Integration is a tiny re-exec wrapper:

mcpproxy __sandbox_exec -- <command> [args...]

with the confinement Spec JSON-encoded in the env. The hidden child applies Landlock + setrlimit, does a best-effort uid/gid drop, then execs the real command. Confinement is inherited across execve; stdin/stdout pass straight through, no mux. Reuses the existing SysProcAttr{Setpgid} process-group cleanup.

Scope coverage

  • ✅ rlimits (RLIMIT_CORE=0, RLIMIT_NOFILE)
  • ✅ filesystem write-allowlist (Landlock): RO /, RW = working_dir + temp + package caches
  • ✅ best-effort uid/gid (Setuid/Setgid only when root w/ non-root real user — documented no-op otherwise)
  • ✅ raw stdin/stdout passthrough (no mux)
  • ✅ graceful degraded fallback + diagnostic when kernel lacks Landlock; macOS/Windows = documented no-op (none)
  • ✅ wired into ResolveMode == sandbox (MCP-34.2 selector) in both stdio + launcher paths

Honest limits (documented in docs/features/sandbox-isolation.md)

  • Writes are confined, reads are not (a read allowlist breaks tool discovery; deferred).
  • uid/gid drop is a no-op without root/CAP_SETUID.
  • Landlock-less kernels degrade to unconfined-with-diagnostic (availability over hard-fail); use docker mode for a hard guarantee.

Tests (red→green)

  • internal/sandbox: WrapCommand argv/env round-trip, SpecFromEnv absent/malformed.
  • internal/upstream/core: buildSandboxSpec write-allowlist defaults; Linux end-to-end via real re-exec — cmd construction, RLIMIT_NOFILE application, write-outside-allowlist denied, stdin/stdout passthrough; fail-closed refuses to run unconfined.
  • Re-exec plumbing (passthrough, best-effort degrade, both fail-closed paths, hidden command) smoke-validated locally on darwin.
  • Landlock enforcement runs on ubuntu-latest CI (24.04) — same empirical-proof approach as the MCP-34.1 spike (sandbox.Available() skips on kernels without Landlock).

Local: go build ./..., go vet (linux cross), go test ./internal/sandbox/... ./internal/upstream/core/..., gofmt, golangci-lint v2 all clean.

Closes MCP-3234.

…o servers (MCP-34.3)

Implements the `sandbox` spawn branch alongside docker/plain in connectStdio and
buildLauncherCmd, using the Landlock mechanism proven by the MCP-34.1 spike and
wired into the mode-aware selector from MCP-34.2.

Mechanism: a tiny re-exec wrapper. mcpproxy launches itself as
`mcpproxy __sandbox_exec -- <command> [args...]` with the confinement Spec
encoded in the environment; the hidden child applies Landlock + setrlimit, does a
best-effort uid/gid drop, then execs the real command — so confinement is
inherited across execve and the server's stdin/stdout pass straight through with
no mux. Reuses the existing SysProcAttr{Setpgid} process-group cleanup.

- internal/sandbox: WrapCommand/SpecFromEnv (cross-platform), RunChild (unix +
  non-unix guard), Available() honest-diagnostic helper.
- internal/upstream/core: wrapWithSandbox + buildSandboxSpec (filesystem WRITE
  allowlist: RO "/", RW working_dir + temp + package caches; RLIMIT_CORE=0 +
  NOFILE; BestEffort graceful degrade). Wired into both stdio + launcher plain
  branches behind ResolveMode == sandbox.
- cmd/mcpproxy: hidden __sandbox_exec subcommand.
- Graceful fallback: non-Linux / Landlock-less kernels degrade to unconfined
  (effective "none") with a logged diagnostic; fail-closed when BestEffort=false.
- docs/features/sandbox-isolation.md documents enforcement, honest limits
  (no read confinement, uid/gid no-op without root), and platform support.

Tests (red→green): WrapCommand argv/env round-trip, SpecFromEnv absent/malformed,
buildSandboxSpec write-allowlist defaults; Linux end-to-end (cmd construction,
RLIMIT_NOFILE application, write-outside-allowlist denied, stdin/stdout
passthrough) + fail-closed refuses to run unconfined. Landlock enforcement runs
on ubuntu-latest CI (24.04), mirroring the MCP-34.1 spike's empirical proof.

Co-Authored-By: Paperclip <[email protected]>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: d72b898
Status:🚫  Build failed.

View logs

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 24.08759% with 104 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/sandbox/runchild_unix.go 0.00% 53 Missing ⚠️
internal/upstream/core/sandbox.go 35.71% 27 Missing ⚠️
cmd/mcpproxy/sandbox_exec_cmd.go 0.00% 10 Missing ⚠️
internal/upstream/core/connection_launcher.go 0.00% 3 Missing and 1 partial ⚠️
internal/upstream/core/connection_stdio.go 0.00% 4 Missing ⚠️
internal/sandbox/sandbox_linux.go 0.00% 3 Missing ⚠️
internal/sandbox/wrap.go 87.50% 1 Missing and 1 partial ⚠️
cmd/mcpproxy/main.go 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: feat/mcp-3234-sandbox-launcher

Available Artifacts

  • archive-darwin-amd64 (28 MB)
  • archive-darwin-arm64 (25 MB)
  • archive-linux-amd64 (16 MB)
  • archive-linux-arm64 (14 MB)
  • archive-windows-amd64 (28 MB)
  • archive-windows-arm64 (25 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (21 MB)
  • installer-dmg-darwin-arm64 (19 MB)

How to Download

Option 1: GitHub Web UI (easiest)

  1. Go to the workflow run page linked above
  2. Scroll to the bottom "Artifacts" section
  3. Click on the artifact you want to download

Option 2: GitHub CLI

gh run download 28144942328 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants