Skip to content

feat(security): scanner-flow parity under sandbox isolation + docs (MCP-34.4, closes #71)#781

Merged
Dumbris merged 3 commits into
mainfrom
feat/mcp-3235-scanner-sandbox-parity
Jun 29, 2026
Merged

feat(security): scanner-flow parity under sandbox isolation + docs (MCP-34.4, closes #71)#781
Dumbris merged 3 commits into
mainfrom
feat/mcp-3235-scanner-sandbox-parity

Conversation

@Dumbris

@Dumbris Dumbris commented Jun 28, 2026

Copy link
Copy Markdown
Member

Summary

Implements MCP-34.4 — scanner-flow parity under sandbox isolation mode, per the MCP-34 spike's decision D3 option (b): clean, surfaced degradation. Resolves #71 (snap-docker AppArmor blocks the Docker scanner).

The security scanner plugins (Spec 039) are Docker-based and are the broken path on snap-docker / non-Docker hosts. Rather than fail silently (or print the misleading "pull the image locally" guidance), the scan engine now detects a non-Docker isolation mode and degrades cleanly:

  • Under isolation.mode: sandbox or none, Docker-based scanner plugins are skipped with an honest, mode-specific reason that points at MCPX_DOCKER_SNAP_APPARMOR.
  • The always-on in-process tpa-descriptions scanner still runs.
  • 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 — a low/zero risk score from incomplete coverage is never reported as a trustworthy all-clear.
  • A startup WARN log surfaces the mode.

A native (non-Docker) scanner runtime — D3 option (a) — is intentionally deferred as the larger follow-up the spike calls out.

Changes

Code

  • internal/security/scanner/engine.go — new isolationMode gate in resolveScanners/checkImage.
  • internal/security/scanner/service.goSetIsolationMode(mode) setter + startup warn.
  • internal/server/server.go — wire from cfg.DockerIsolation.ResolvedMode().

Tests (TDD, all green + -race)

  • TestEngineResolveScannersSkipsDockerUnderSandbox (sandbox + none): Docker scanner skipped with mode-named, MCPX_DOCKER_SNAP_APPARMOR-referencing reason; no "docker pull" guidance; in-process scanner still runs.
  • TestEngineResolveScannersDockerModeUnaffected ("" / docker): back-compat guard.
  • TestServiceSetIsolationMode: setter propagates to engine.

Docs

  • docs/docker-isolation.md — retitled to Security Isolation (Docker · Sandbox · None); new Isolation Modes section (three modes + legacy enabled back-compat + per-server precedence), Sandbox mode (Landlock), Scanner behaviour under each mode, Snap-docker (AppArmor) failure mode, Honest limitations (no uid/gid drop without privilege, Linux-only, FS+resources only), and a Platform support matrix.
  • docs/errors/MCPX_DOCKER_SNAP_APPARMOR.md — added the sandbox-mode option (now four fixes).

Verification

  • go build ./internal/... ./cmd/...
  • go test -race ./internal/security/scanner/
  • golangci-lint run --config .github/.golangci.yml ./internal/security/scanner/... ./internal/server/...0 issues

Scope notes

Co-Authored-By: Paperclip noreply@paperclip.ing

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>
@Dumbris Dumbris mentioned this pull request Jun 28, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 28, 2026

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3f12faf
Status: ✅  Deploy successful!
Preview URL: https://b362cc6e.mcpproxy-docs.pages.dev
Branch Preview URL: https://feat-mcp-3235-scanner-sandbo.mcpproxy-docs.pages.dev

View logs

@codecov-commenter

codecov-commenter commented Jun 28, 2026

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 48.14815% with 28 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/server/server.go 0.00% 13 Missing and 2 partials ⚠️
internal/security/scanner/service.go 55.17% 12 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: feat/mcp-3235-scanner-sandbox-parity

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 28346333806 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

Dumbris and others added 2 commits June 28, 2026 10:38
CI build/Cloudflare-Pages failed: onBrokenLinks:throw flagged the error doc's
link to ../docker-isolation.md, which the website does not serve (its `include`
list only builds features/**, errors/**, etc. — not the top-level
docs/docker-isolation.md). Port the Isolation Modes / Sandbox / Scanner-
behaviour / Honest-limitations / Platform-matrix content into the served
docs/features/docker-isolation.md (the user-facing isolation page) and repoint
the MCPX_DOCKER_SNAP_APPARMOR link to ../features/docker-isolation.md.

Verified: website `npm run build` succeeds locally (no broken links).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
…(MCP-34.4)

CodexReviewer correctness finding on #781: the scanner skip was wired from a
single service-wide isolation mode (cfg.DockerIsolation.ResolvedMode()), but
isolation resolves per-server — a per-server isolation.mode overrides the
global (internal/upstream/core IsolationManager). So a server pinned to
isolation.mode:docker would wrongly have its Docker scanners skipped under a
global sandbox default, and vice versa, contradicting the per-server design and
the served docs.

Thread the scanned server's RESOLVED mode through the scan:
- ScanRequest.IsolationMode carries the per-server mode; engine.StartScan uses
  effectiveIsolationMode(req) (per-server wins, else engine-wide default) and
  passes it to resolveScanners(ids, mode).
- Service.SetIsolationModeResolver injects a per-server resolver; StartScan
  (both passes) populates req.IsolationMode via resolveIsolationMode(server).
- server.go wires the resolver from core.IsolationManager.ResolveMode against
  the live per-server config; SetIsolationMode stays as the engine-wide default
  fallback. Scanner package stays decoupled from the resolver.

Tests: per-server override cases (docker under global sandbox runs Docker
scanners; sandbox/none under global docker skips), effectiveIsolationMode
precedence, and Service.resolveIsolationMode fallback. go test -race +
golangci-lint v2 green.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@Dumbris

Dumbris commented Jun 29, 2026

Copy link
Copy Markdown
Member Author

GeminiCritic Review (fallback — MCP-3719)

ACCEPT — head 3f12faf03dce2662a422763dc3ff2d57b3a657be

Summary

PR correctly implements MCP-34.4 D3 option (b): clean, surfaced degradation of Docker-based scanner plugins under sandbox/none isolation modes. In-process scanners (tpa-descriptions) still run; affected servers report security_scan.status: degraded rather than failing silently or emitting misleading "pull the image" guidance.

What's correct

  • Engine-level prefail path (engine.go): Docker scanners prefailed with a message naming the mode and referencing MCPX_DOCKER_SNAP_APPARMOR.md. In-process scanner exempt. ✅
  • Per-server isolation override (SetIsolationModeResolver): server pinned to isolation.mode:docker keeps Docker scanners under global sandbox default. Precedence chain matches docs. ✅
  • Startup warning log at sandbox/none is informative and actionable. ✅
  • Backward compat: docker_isolation.enabled boolean → mode mapping preserved. ✅
  • Tests cover: sandbox/none prefail, docker mode unaffected, per-server override precedence, effectiveIsolationMode unit. Good coverage. ✅
  • Docs: thorough platform support matrix, honest limitations (no uid/gid drop, Linux-only Landlock), updated MCPX_DOCKER_SNAP_APPARMOR.md with sandbox as option 2. ✅

Minor observations (not blocking)

  • Per-server resolver in server.go constructs a new IsolationManager per call — minor allocation churn under heavy scan load, not a correctness issue.
  • The "docker pull" negative guard in engine_test.go is a good regression fence.

No correctness issues, no security regressions. Approve and ship.

@mcpproxy-gatekeeper mcpproxy-gatekeeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gatekeeper approval — Codex review verdict: ACCEPT.

This approval is posted automatically by the MCPProxy Gatekeeper App on behalf of the Codex reviewer (verdict of record lives in the Paperclip review thread). Author≠approver satisfied; QA + CI gates enforced separately.

Auto-approved per Model B (MCP-1249).

@Dumbris Dumbris merged commit 72a51c1 into main Jun 29, 2026
39 checks passed
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.

Process compose option

2 participants