Skip to content

fix(scanner): resolve source for runtime-added Docker-isolated stdio servers (MCP-2123 Defect A)#643

Merged
Dumbris merged 3 commits into
mainfrom
fix/mcp-2123-docker-scan-source
Jun 13, 2026
Merged

fix(scanner): resolve source for runtime-added Docker-isolated stdio servers (MCP-2123 Defect A)#643
Dumbris merged 3 commits into
mainfrom
fix/mcp-2123-docker-scan-source

Conversation

@Dumbris

@Dumbris Dumbris commented Jun 12, 2026

Copy link
Copy Markdown
Member

Summary

Fixes Defect A of MCP-2123: security scans of a Docker-isolated stdio server added at runtime from the official registry (slash/dot names like com.pulsemcp/google-flights) reported "No Source Available" (source_method=none), exported no tools.json, and skipped Pass 2 with "No server info available".

Two independent root causes, both confirmed by static trace from the issue's evidence:

1. Stale config snapshot (primary — matches all reported evidence)

configServerInfoProvider iterated a *config.Config captured at server boot. runtime.Config() returns a fresh immutable snapshot on every reload, so a server added at runtime (exactly the registry case) was absent from the boot snapshot → GetServerInfo returned not found → the scanner ran with no ServerInfo:

  • source resolution never ran → source_method stayed none;
  • tool definitions were never exported → /scan/source/tools.json missing → tpa-descriptions / mcp-scan failed;
  • Pass 2 got serverInfo == nil"No server info available for Pass 2, skipping".

The provider now resolves against the live config (runtime.Config), falling back to the boot snapshot when no accessor is wired.

2. Container-name sanitizer mismatch (secondary)

The scanner's docker ps --filter name=mcpproxy-<sanitized>- lookup mapped .-, while the launcher (internal/upstream/core) preserves . (Docker allows it). So for any dotted name the filter prefix never matched the real container (mcpproxy-com.pulsemcp-google-flights-umqu) and docker_extract silently fell through. Both sides now share internal/dockernaming.SanitizeServerName so the rule can't drift again.

With (1) fixed the scan resolves ServerInfo and (worst case) exports tools.json via the existing tool_definitions_only path; with (2) fixed docker_extract matches the container and scans the real package source.

Tests

  • internal/dockernaming: TestSanitizeServerName — locks the com.pulsemcp/google-flightscom.pulsemcp-google-flights case plus full parity with the launcher's prior rules.
  • internal/server: TestConfigServerInfoProvider_UsesLiveConfig / _FallsBackToSnapshot — regression guard for the stale-snapshot bug.
  • internal/upstream/core sanitizer tests still pass (behavior preserved via delegation).

Verification

  • go build ./...
  • go test -race ./internal/dockernaming/... ./internal/security/scanner/... ./internal/upstream/core/... ./internal/server/...
  • ./scripts/run-linter.sh0 issues
  • gofmt -l clean, go vet clean ✅

Scope

  • In scope: Defect A (backend), internal/ only.
  • Defect B (frontend: scan-report "View Full Report" link not encodeURIComponent-ing the scan id) → separate FrontendEngineer child issue.
  • Infra follow-up (ramparts arm64 GLIBC, trivy DB-download timeout) → noted, out of scope.

No public API/CLI/config contract change; no docs change required (restores the behavior source_method docs already describe).

Related #MCP-2123

…servers

Two independent defects broke security scans of Docker-isolated stdio
servers added at runtime from the official registry (slash/dot names like
com.pulsemcp/google-flights), surfacing as "No Source Available"
(source_method=none) with no tools.json and a skipped Pass 2.

1. Stale config snapshot (primary). configServerInfoProvider iterated a
   config captured at boot, so servers added at runtime were invisible to
   GetServerInfo. The scanner then ran with no ServerInfo: source stayed
   "none", tool definitions were never exported, and Pass 2 was skipped
   with "No server info available". The provider now resolves against the
   live config snapshot (runtime.Config), falling back to the boot snapshot.

2. Container-name sanitizer mismatch. The scanner's docker-ps lookup mapped
   '.' to '-' while the launcher preserves it, so the
   'mcpproxy-<sanitized>-' filter never matched the real container for any
   dotted name and docker_extract silently fell through. Both sides now
   share internal/dockernaming.SanitizeServerName so the rule can't drift.

Adds regression tests for both. Defect B (frontend scan-report link not
URL-encoding the scan id) is tracked separately.

Related #MCP-2123
@cloudflare-workers-and-pages

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

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: bde3dcb
Status: ✅  Deploy successful!
Preview URL: https://23703e92.mcpproxy-docs.pages.dev
Branch Preview URL: https://fix-mcp-2123-docker-scan-sou.mcpproxy-docs.pages.dev

View logs

@codecov-commenter

codecov-commenter commented Jun 12, 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 79.41176% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/dockernaming/naming.go 86.66% 1 Missing and 1 partial ⚠️
internal/security/scanner/source_resolver.go 33.33% 2 Missing ⚠️
internal/httpapi/security_scanner.go 0.00% 1 Missing ⚠️
internal/security/scanner/service.go 0.00% 1 Missing ⚠️
internal/server/server.go 92.30% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: fix/mcp-2123-docker-scan-source

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 (24 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 27457860138 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@Dumbris

Dumbris commented Jun 13, 2026

Copy link
Copy Markdown
Member Author

🔎 Verification (CEO cockpit) — CHANGES REQUESTED

Built this branch merged with #644 and ran a real end-to-end scan of a runtime-added, Docker-isolated com.pulsemcp/google-flights (npx). The two fixes here are correct, but they expose two more backend defects that must land before slash-named (official-registry) servers can actually be scanned. Tracked + patch attached in MCP-2149 (assigned BackendEngineer, inherits this branch's workspace).

What this PR fixes (confirmed): stale config snapshot → ServerInfo found; sanitizer unified → container now matches mcpproxy-com.pulsemcp-google-flights-….

Newly-exposed, blocking:

  1. os.MkdirTemp rejects slash-named servers → extraction fails with pattern contains path separator (sites: source_resolver.go:355,814, service.go:682). Fix: dockernaming.SanitizeServerName(serverName).
  2. handleGetScanReportByJobID reads raw chi.URLParam(r,"jobId") (no decode) → report endpoint 404s for slash jobIds even after fix(frontend): encode scan id in "View Full Report" link (MCP-2125) #644. Fix: decodePathParam(...) at security_scanner.go:666.

With both applied (verified): source_method=docker_extract, 23 files extracted, tools_exported=3, scanner completed; report endpoint → HTTP 200; Web UI shows "Source extracted from container (23 files)" and the report page renders (no 404).

Recommend folding MCP-2149's patch into this branch so it ships as one PR; QA will re-bless the new head SHA.

…ode scan-report jobId

Follow-up to the same MCP-2123 branch: once the sanitizer fix makes the
container actually match, two more defects surface for official-registry
slash-named servers (verified end-to-end by CEO on com.pulsemcp/google-flights):

1. os.MkdirTemp rejects a pattern containing a path separator, so the three
   extraction temp-dir patterns (mcpproxy-scan-%s-, -scan-full-%s-,
   -scan-tools-%s-) failed for '/'-containing names and source fell back to
   'none'. Sanitize the name via dockernaming.SanitizeServerName first.

2. handleGetScanReportByJobID read the raw chi jobId param. Scan ids are
   scan-<serverName>-<ts> and contain '/', arriving percent-encoded, so the
   report endpoint 404'd. Decode via decodePathParam (same pattern the
   /servers subtree uses).

Verified result: source_method=docker_extract, 23 files extracted,
tools_exported=3, report endpoint HTTP 200. Adds regression tests:
TestScanTempDirPatternHandlesSlashNames, TestDecodePathParamScanJobID.

Related #MCP-2123

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

Dumbris commented Jun 13, 2026

Copy link
Copy Markdown
Member Author

Pushed follow-up commit d610cba1 addressing two defects the sanitizer fix exposed for slash-named official-registry servers (verified end-to-end by CEO on com.pulsemcp/google-flights):

  1. os.MkdirTemp rejected slash names — sanitized all three extraction temp-dir patterns (mcpproxy-scan-%s-, -scan-full-%s-, -scan-tools-%s-) via dockernaming.SanitizeServerName.
  2. handleGetScanReportByJobID raw jobId — now decodePathParam(...) so the scan-report endpoint stops 404ing (scan ids embed the slash-containing server name).

Verified result: source_method=docker_extract, 23 files extracted, tools_exported=3, report endpoint HTTP 200. New regression tests: TestScanTempDirPatternHandlesSlashNames, TestDecodePathParamScanJobID. Local -race + run-linter.sh (0 issues) green.

Comment thread internal/security/scanner/service.go Fixed
…ection)

The user-controlled server name in the os.MkdirTemp patterns was purely
cosmetic — the random suffix already guarantees a unique dir. Embedding it
introduced a go/path-injection CodeQL high alert (taint flows into the path
expression even through dockernaming.SanitizeServerName) and was the original
cause of the slash-name rejection bug. Make all three scan temp-dir patterns
constant, killing the taint at the source and trivially handling slash-named
servers.

Drop the now-unused dockernaming import from service.go; keep it in
source_resolver.go (still used by findServerContainer). Update the MCP-2123
regression test to assert the patterns are constant and carry no user data.

Related #643

@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 89dfb99 into main Jun 13, 2026
37 checks passed
@Dumbris Dumbris deleted the fix/mcp-2123-docker-scan-source branch June 13, 2026 17:37
Dumbris added a commit that referenced this pull request Jun 13, 2026
…644)

Scan ids embed the raw upstream server name, so official-registry
servers whose names contain '/' (e.g. com.pulsemcp/google-flights)
produce a scan id like scan-com.pulsemcp/google-flights-<ts> with a
'/' in it. The scan-report route (/security/scans/:jobId) is a single
segment, so the unencoded ServerDetail "View Full Report" link split
the path and fell through to the catch-all 404.

Add a scanReportPath() helper that percent-encodes the id (vue-router
v4 decodes it back on read, so ScanReport.vue needs no manual decode)
and use it in both ServerDetail.vue (the bug) and Security.vue (DRY;
was already encoding inline). Same class as MCP-1112 / serverDetailPath.

Adds a vitest covering the encoded link target and a route round-trip.

Related #643
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.

3 participants