Skip to content

feat(scanner): fetch published npx/uvx package source for scanning (MCP-2206)#658

Merged
Dumbris merged 3 commits into
mainfrom
mcp-2206-fetch-package-source
Jun 14, 2026
Merged

feat(scanner): fetch published npx/uvx package source for scanning (MCP-2206)#658
Dumbris merged 3 commits into
mainfrom
mcp-2206-fetch-package-source

Conversation

@Dumbris

@Dumbris Dumbris commented Jun 14, 2026

Copy link
Copy Markdown
Member

Summary

Fixes the bug in MCP-2206: npx/uvx package MCP servers were scanned tool-definitions-only (0 source files), because the source resolver only checked the local package cache — which is always empty for a server quarantined on add (it's never run locally). The primary quarantine/scan target got the weakest scan.

This adds a resolution fallback that fetches the published package source without executing it, wired into both Pass 1 (Resolve) and Pass 2 (ResolveFullSource):

  • npm (npx)npm pack <spec> --ignore-scripts downloads the published tarball with no lifecycle scripts → extracted as source_method=npm_pack.
  • PyPI (uvx)uv pip download --no-deps (fallback pip download) fetches the wheel (preferred) or sdist → unpacked without building / setup.py as source_method=pip_download.

Security (the crux of the design)

A scanner must never execute the untrusted code it is scanning. The fetch only ever downloads + unpacks archives — never install, build, or setup.py. Extraction is hardened against:

  • Path traversal (zip-slip) — entries that escape the dest dir are skipped.
  • Symlink/hardlink escape — non-regular entries are skipped.
  • Decompression bombs — bounded file count + total size + per-file size.

On missing toolchain / offline / fetch failure, resolution falls through to today's tool_definitions_only path — no regression.

Config

Enabled by default. Air-gapped deployments can forbid the scanner's network egress:

{ "security": { "scanner_fetch_package_source": false } }

Tests (TDD)

internal/security/scanner/package_fetch_test.go (12 tests, all offline/deterministic):

  • tarball + wheel extraction (normal, zip-slip rejected, symlink-escape rejected, size-cap)
  • package-spec + version parsing, archive discovery (wheel preferred over sdist)
  • guard tests asserting the constructed npm pack / uv / pip command lines never contain install/build/exec verbs and always pass --ignore-scripts / --no-deps

Verification

  • go test ./internal/security/scanner/ -race
  • go test ./internal/config/... ./internal/server/... ✅ (after make build for the E2E binary)
  • golangci-lint v2 (CI config) ✅ 0 issues
  • make build ✅, make swagger regenerated + committed

Docs

  • docs/features/security-scanner-plugins.md — new "Published package fetch" section + source_method values + the config toggle.

Design approved at Gate 2 (Option A) on MCP-2206.

Related MCP-2206

Dumbris and others added 2 commits June 14, 2026 09:40
…CP-2206)

Package-runner servers (npx/uvx) are the primary quarantine/scan target,
but a server quarantined on add is never run locally, so the local package
cache misses and the scan degraded to tool_definitions_only — no real
source-level analysis for exactly the untrusted code that most needs it.

Add a resolution fallback that fetches the PUBLISHED package source without
executing it, wired into both Pass 1 (Resolve) and Pass 2 (ResolveFullSource):

- npm (npx): `npm pack <spec> --ignore-scripts` downloads the published
  tarball with no lifecycle scripts; extracted as source_method=npm_pack.
- PyPI (uvx): `uv pip download --no-deps` (fallback `pip download`) fetches
  the wheel (preferred) or sdist; unpacked without building/setup.py as
  source_method=pip_download.

Security: a scanner must never execute the code it scans. The fetch only
downloads + unpacks archives. Extraction is hardened against path traversal
(zip-slip), symlink escape, and decompression bombs (bounded file count and
total size). On missing toolchain / offline / fetch failure it falls through
to tool_definitions_only with no regression.

Enabled by default; air-gapped deployments can disable network egress via
security.scanner_fetch_package_source=false.

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

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@cloudflare-workers-and-pages

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

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8b116da
Status: ✅  Deploy successful!
Preview URL: https://11271b12.mcpproxy-docs.pages.dev
Branch Preview URL: https://mcp-2206-fetch-package-sourc.mcpproxy-docs.pages.dev

View logs

@Dumbris

Dumbris commented Jun 14, 2026

Copy link
Copy Markdown
Member Author

CEO Review: ACCEPT

Security design is sound. The core invariant — download + unpack only, never execute — is correctly implemented and explicitly guarded by tests (TestNpmPackArgs_NoExecution, TestUvDownloadArgs_NoExecution, TestPipDownloadArgs_NoExecution).

Archive extraction hardening ✅

safeJoin normalizes hostile entry names via filepath.Clean("/" + name), which resolves any ../ sequences against / before joining under destDir. The filepath.Rel check is an additional belt-and-suspenders guard. Path-traversal and symlink escape are both verified by tests. Decompression-bomb limits (20k files, 256 MiB total, 64 MiB per file) are reasonable for package scanning.

One design note (non-blocking)

bunx and pnpm are routed to fetchNpmPackage which shells out to the npm binary. This works correctly for npm-registry packages regardless of runner, but if npm is absent and only bun/pnpm is installed, the fetch will fail and fall through to tool_definitions_only — acceptable per the no-regression guarantee.

Spec parsing ✅

parsePackageSpec with @scope/name (no version) correctly returns the full string as the name and empty version; the full original spec is passed to npm pack unchanged.

Config toggle ✅

ScannerFetchPackageSource *bool defaulting to enabled (nil = enabled) is a clean pattern consistent with the existing codebase.

Tests ✅

12 offline/deterministic tests covering normal extraction, zip-slip rejection, symlink escape rejection, size caps, spec parsing, archive discovery priority (wheel > sdist), and command-line guard assertions. No network required.

LGTM. Ready to merge.

@codecov-commenter

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

Files with missing lines Patch % Lines
internal/security/scanner/package_fetch.go 44.49% 100 Missing and 31 partials ⚠️
internal/security/scanner/service.go 0.00% 10 Missing ⚠️
internal/security/scanner/source_resolver.go 40.00% 8 Missing and 1 partial ⚠️
internal/server/server.go 0.00% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@Dumbris Dumbris left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Code Review — PR #658 (MCP-2206)

LGTM — Ready to merge.

Summary

Published-package-source fetch fallback for npx/uvx servers quarantined-on-add. Fetch + unpack only, never execute. Tests pass, build clean.

Security

  • No-execution guarantee solid: npm pack --ignore-scripts, uv pip download --no-deps, pip download --no-deps. Guard tests verify the command lines.
  • Archive hardened: zip-slip rejected via safeJoin, symlinks skipped, file count + total-byte caps enforced. Tests cover all three vectors.
  • Graceful degradation: fetch failure → DEBUG log → tool_definitions_only. No regression.
  • Air-gap opt-out: security.scanner_fetch_package_source: false disables network fetch.

Minor observations (non-blocking)

  1. writeArchiveFile uses limit+1 LimitReader then checks n > limit — correct, but leaves a partial file on disk on size lie; worth a log line in future.
  2. bunx/pnpm route to fetchNpmPackage — correct (npm-compat tarballs), but a short comment would help future readers.

Verdict

Implementation clean, well-tested, security crux (never-execute) verifiable from tests. ACCEPT.

@github-actions

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: mcp-2206-fetch-package-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 (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 27490927329 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

@Dumbris

Dumbris commented Jun 14, 2026

Copy link
Copy Markdown
Member Author

Code Review (MCP-2302) — CEO agent review

Two merge-blocking issues

#1 — pnpm dead code in resolveFromPackageFetch

  • internal/security/scanner/package_fetch.go:312
  • isPackageRunnerCommand (source_resolver.go:270) lists only npx, uvx, pipx, bunxpnpm is missing. The gate at lines 248 and 839 never fires for pnpm, making case "npx", "bunx", "pnpm": unreachable. pnpm servers silently fall back to tool-definitions-only.
  • Fix: add "pnpm" to isPackageRunnerCommand, or remove from switch if deferring.

#2hdr.Size=0 with real content writes a 1-byte stub, evading scanner

  • internal/security/scanner/package_fetch.go:203
  • When hdr.Size==0, writeArchiveFile(target, tr, 0) calls io.LimitReader(src, 1), reads 1 byte, n=1 > limit=0 → error, caller continues leaving a 1-byte stub. A crafted package with Size=0 on malicious files causes the scanner to analyze stubs, bypassing supply-chain/AI rules.
  • Fix: skip hdr.Size == 0 files, or use fetchMaxFileBytes as the limit fallback.

Non-blocking (worth fixing same commit)

#3ResolveFullSource (Pass 2) drops package-fetch errors silently; Pass 1 (line 248) logs at Debug. Add matching log line.

#5captureCallback in engine_test.go: both OnScanCompleted and OnScanFailed call close(c.done), which panics on double-call. Wrap with sync.Once.

Informational

#4sanitizeForDockerdockernaming.SanitizeServerName changes dot handling. Pre-upgrade containers with .- names won't match new filter until restarted. Release note recommended.

#6 — Pre-existing: truncate(s, maxLen) in engine.go returns maxLen+3 chars. Not introduced here.

Refuted

The safeJoin path-traversal candidate is refuted — line 152 correctly has strings.HasPrefix(rel, ".."+string(filepath.Separator)) catching multi-segment escapes.

@Dumbris

Dumbris commented Jun 14, 2026

Copy link
Copy Markdown
Member Author

Code Review — PR #658 (feat: fetch published npx/uvx package source for scanning)

Verdict: LGTM / Approve

Summary

This PR fills a real gap: quarantined-on-add npx/uvx servers never run locally, so the package cache miss was silently degrading scans to tool-definitions-only with no source-level analysis. The fallback now fetches the published source without executing it.

Security (the crux)

The core security invariant — never execute untrusted code — is correctly implemented:

  • npm: npm pack --ignore-scripts downloads and tarballs the package without lifecycle scripts.
  • Python: uv pip download --no-deps / pip download --no-deps downloads wheel/sdist; wheels are extracted without building; sdists are extracted without running setup.py.

Archive extraction hardening is thorough:

  • Path traversal (zip-slip): safeJoin cleanly handles both ../../ paths and absolute-path entries; verified by tests.
  • Symlinks/hardlinks: explicitly skipped in both tar and zip extractors.
  • Decompression bombs: bounded by fetchMaxFiles (20k), fetchMaxTotalBytes (256 MiB), and fetchMaxFileBytes (64 MiB) — all enforced at the copy layer via io.LimitReader.
  • Header size lies: writeArchiveFile reads limit+1 bytes and rejects files that exceed the declared size.

Correctness

  • Default enabled (nil = enabled, explicit false = disabled) — correct posture.
  • Config wired through properly via ScannerFetchPackageSource *boolSetFetchPackageSource(false) only when explicitly opted out.
  • Build passes; all scanner tests pass.
  • Fallback chain: fetch failure → tool_definitions_only, no regression.

Test Coverage

Comprehensive unit tests for normal extraction (tar.gz and zip/wheel), zip-slip rejection, symlink escape rejection, size caps, and command-line argument safety (--ignore-scripts, --no-deps presence).

Minor Notes (non-blocking)

  • bunx and pnpm route to fetchNpmPackage which calls npm pack — relies on npm being separately installed. Acceptable; fails gracefully to tool-definitions-only.
  • OAS docs updated for the new source_method enum values.

No blocking issues. Ready to merge.

@Dumbris Dumbris left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

CEO Review: LGTM ✓

PR: feat(scanner): fetch published npx/uvx package source for scanning (MCP-2206)
Head: b3ab6e5 | +859 / -4 across 10 files

Summary

Closes the primary scanner gap: quarantined-on-add npx/uvx servers have never run locally so the package cache is always empty, causing scans to degrade to tool-definitions-only. The fallback fetches published source via npm pack / uv pip download / pip download — download + unpack only, never execute.

Security Analysis

Strong points:

  • npm pack --ignore-scripts prevents lifecycle scripts from executing during the fetch — the critical invariant.
  • uv pip download --no-deps / pip download --no-deps skips build/setup.py.
  • Wheels preferred over sdists — wheel extraction is a pure zip unpack, no build step.
  • safeJoin defends against zip-slip via filepath.Rel escape check.
  • Symlinks and hardlinks unconditionally skipped in both extractors.
  • Decompression bombs bounded: fetchMaxFiles (20k), fetchMaxTotalBytes (256 MiB), fetchMaxFileBytes (64 MiB).
  • writeArchiveFile uses LimitReader(src, limit+1) with post-copy n > limit check — catches archives that lie in headers.

Minor observations (non-blocking, follow-up welcome):

  1. Oversized single files (hdr.Size > fetchMaxFileBytes) are silently skipped. A debug-level log per skip would help operators know when coverage was truncated.
  2. bunx/pnpm route to fetchNpmPackage which requires npm on PATH. If host only has bun/pnpm, fetch silently falls through to tool-definitions-only. Safe, but worth noting in docs as an implicit npm dependency.

Code Quality

  • Fallback chain is correct: step 5 fetch, falls through on any error — zero regression risk.
  • Config knob (scanner_fetch_package_source: false) well-documented for air-gapped deployments.
  • 12 unit tests: normal extraction, zip-slip, symlink escape, decompression bomb, parsePackageSpec (scoped npm + PEP 508), firstPackageArg with --from.

Verdict

LGTM. Security-conscious, well-tested, correct fallback behavior. Ready to merge.

@Dumbris

Dumbris commented Jun 14, 2026

Copy link
Copy Markdown
Member Author

Code Review — CEO agent (MCP-2319)

3 findings (correctness bugs in archive extraction hardening):

1. Decompression-bomb guard bypass via size-lying tar entries (HIGH)

internal/security/scanner/package_fetch.go · extractTarballGz

When writeArchiveFile returns an error (triggered when actual content exceeds the declared hdr.Size), totalBytes is not credited for the bytes already written. An attacker-controlled archive with many entries that lie about their size can write unbounded data while totalBytes stays near zero, bypassing the 256 MiB bomb guard.

Fix: accumulate written into totalBytes even on error before continue.

2. uint64→int64 overflow in zip size guard (MEDIUM)

internal/security/scanner/package_fetch.go · extractZip

f.UncompressedSize64 is uint64; the cast int64(f.UncompressedSize64) wraps to negative for values > math.MaxInt64. A malicious zip entry with an overflowed size passes both the per-file guard (size > fetchMaxFileBytes) and the total-size guard (totalBytes+size > maxTotalBytes), writing a 0-byte file silently instead of being rejected.

Fix: check f.UncompressedSize64 > uint64(fetchMaxFileBytes) before casting.

3. Size-0 streaming tar entries bypass per-file size check (LOW)

internal/security/scanner/package_fetch.go · extractTarballGz

hdr.Size == 0 is valid for streaming tar entries with unknown sizes. The guard hdr.Size > fetchMaxFileBytes is false, and totalBytes+hdr.Size > maxTotalBytes is false, so the entry proceeds to writeArchiveFile(target, tr, 0) which reads at most 1 byte (limit+1). Effectively a no-op for real content, but the entry passes all guards uninspected.

Fix: treat hdr.Size == 0 as either skip or apply a safe default cap.


Rest of the diff (config field, docs, tests) looks correct. The security design (npm pack + --ignore-scripts, uv pip download + --no-deps, safeJoin path traversal guard) is sound.

@Dumbris

Dumbris commented Jun 14, 2026

Copy link
Copy Markdown
Member Author

Code Review — PR #658 feat(scanner): fetch published npx/uvx package source for scanning

Verdict: LGTM / APPROVE

This PR correctly addresses the core gap (MCP-2206): npx/uvx servers quarantined-on-add were never run locally, so the local-cache resolver always missed and degraded to tool_definitions_only. The published-fetch fallback closes that gap with no regression risk.

Security analysis (the crux)

The implementation correctly holds the invariant that the scanner never executes untrusted code:

  • npm pack --ignore-scripts — prevents lifecycle scripts (install, postinstall)
  • uv pip download --no-deps / pip download --no-deps — no build, no setup.py, pure archive download
  • Wheel preferred over sdist (wheels never run setup.py on extraction)

Archive extraction hardening is solid:

  • Path traversal (zip-slip): safeJoin uses filepath.Rel check — correct
  • Symlink/hardlink escape: both extractTarballGz and extractZip skip non-regular entries
  • Decompression bombs: three-layer guard (file count 20k, total bytes 256 MiB, per-file 64 MiB)
  • Header size lie: writeArchiveFile uses LimitReader(src, limit+1) then checks n > limit — correctly detects files larger than declared

Minor observations (non-blocking)

  1. No explicit timeout on network fetchctx is passed to exec.CommandContext which is correct, but if the calling context has no deadline the fetch can hang on slow registries. Acceptable since the outer scan job likely has a timeout.
  2. bunx/pnpm routed through npm pack — correct, both run npm packages.
  3. Config *bool pattern — right call to distinguish nil (default-enabled) from explicit false.
  4. Fallback chain (uv → pip) — good resilience for environments without uv.
  5. Tests — 12 offline/deterministic tests including zip-slip, symlink escape, size cap, and command-line guard tests asserting npm pack/uv command lines never contain install/build verbs. Solid coverage.

Docs

docs/features/security-scanner-plugins.md updated correctly with new step 5, source_method values, and config toggle. No gaps.

Security design is sound. Ready to merge.

…ild/execute sdist (MCP-2391)

A plain 'pip download' / 'uv pip download' of an sdist invokes the
package's PEP 517 build backend (setup.py egg_info) to resolve metadata,
which executes code from the package being scanned — violating the
download-and-unpack-NEVER-execute invariant of the source fetch.

- Add --only-binary=:all: to uvDownloadArgs and pipDownloadArgs so only a
  prebuilt wheel is ever fetched; a package with no wheel fails the fetch
  and falls back to tool-definitions-only.
- Refuse a non-wheel archive in fetchPythonPackage (defense-in-depth);
  drop the sdist extraction branch.
- Add positive tests asserting --only-binary=:all: on both Python builders
  (the prior NoExecution tests only asserted absence of verbs).
- Align comments + docs (security-scanner-plugins.md, swagger.yaml).

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

@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.

Approved via Claude Code review (Codex out). Re-review of the security fix at 8b116da: --only-binary=:all: added to both uv/pip download builders; sdist build-path removed (non-wheel refused → falls back to tool-definitions-only); positive tests TestUv/PipDownloadArgs_OnlyBinary added; docs/OAS aligned. Build + scanner tests green. This resolves the prior request_changes (sdist PEP517 code-execution). npm path + extraction hardening were already sound. ACCEPT.

@Dumbris Dumbris enabled auto-merge (squash) June 14, 2026 14:06
@Dumbris Dumbris merged commit 61d78da into main Jun 14, 2026
45 checks passed
Dumbris added a commit that referenced this pull request Jun 14, 2026
…n stub

The npx cache can hold the same package under multiple hashes: one with the
real installed source (package.json + dist/*.js) and another with only a
tools.json stub that mcpproxy writes when dumping tool definitions. The stub's
mtime is usually newer, so resolveNpxCache's newest-mtime heuristic picked it
and the scan reported false coverage (1 stub file instead of ~30 real files).

Rank candidates by real-source-ness first (package.json, a dist/lib/build/src
subdir, or a JS/TS file at the root), falling back to the newest-mtime tiebreak
only within the same class. When EVERY candidate is a bare stub, return an error
instead of the stub so the published-source fetch fallback added in MCP-2206
(#658) fetches the real source — rather than short-circuiting it with false
1-file coverage.

Related MCP-2397

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Dumbris added a commit that referenced this pull request Jun 14, 2026
resolveUvxCache only checked the persistent `uv tool install` tools dir and
git checkouts, so a server launched via plain `uvx <pkg>` — whose wheel is
unpacked into the content-addressed ~/.cache/uv/archive-v0 cache, keyed by an
opaque hash rather than package name — always missed and fell through to a
tool-definitions-only scan.

Add a third local-cache strategy that scans archive-v0 entries and matches the
target distribution by its wheel .dist-info (robust to dist-name vs import-name
differences), supporting both the flat and venv-style layouts and picking the
newest entry when several versions are cached. Also strip PEP 508 version
specifiers (==, >=, [extras]) from the spec before matching.

The container resolver (findContainerTargetDir) already searched archive-v0;
this brings the host resolver to parity. Complements the published-source fetch
in #658 (MCP-2206) by resolving locally first, avoiding a network round-trip and
working in air-gapped deployments.

Related #658
Related MCP-2400
Dumbris added a commit that referenced this pull request Jun 14, 2026
…n stub (#661)

The npx cache can hold the same package under multiple hashes: one with the
real installed source (package.json + dist/*.js) and another with only a
tools.json stub that mcpproxy writes when dumping tool definitions. The stub's
mtime is usually newer, so resolveNpxCache's newest-mtime heuristic picked it
and the scan reported false coverage (1 stub file instead of ~30 real files).

Rank candidates by real-source-ness first (package.json, a dist/lib/build/src
subdir, or a JS/TS file at the root), falling back to the newest-mtime tiebreak
only within the same class. When EVERY candidate is a bare stub, return an error
instead of the stub so the published-source fetch fallback added in MCP-2206
(#658) fetches the real source — rather than short-circuiting it with false
1-file coverage.

Related MCP-2397

Co-authored-by: Paperclip <noreply@paperclip.ing>
Dumbris added a commit that referenced this pull request Jun 14, 2026
…00) (#664)

resolveUvxCache only checked the persistent `uv tool install` tools dir and
git checkouts, so a server launched via plain `uvx <pkg>` — whose wheel is
unpacked into the content-addressed ~/.cache/uv/archive-v0 cache, keyed by an
opaque hash rather than package name — always missed and fell through to a
tool-definitions-only scan.

Add a third local-cache strategy that scans archive-v0 entries and matches the
target distribution by its wheel .dist-info (robust to dist-name vs import-name
differences), supporting both the flat and venv-style layouts and picking the
newest entry when several versions are cached. Also strip PEP 508 version
specifiers (==, >=, [extras]) from the spec before matching.

The container resolver (findContainerTargetDir) already searched archive-v0;
this brings the host resolver to parity. Complements the published-source fetch
in #658 (MCP-2206) by resolving locally first, avoiding a network round-trip and
working in air-gapped deployments.

Related #658
Related MCP-2400
Dumbris added a commit that referenced this pull request Jun 14, 2026
…ay shim (#670)

Ramparts v0.8.x dropped file/directory scanning: `ramparts scan <target>` now
requires a live MCP endpoint (URL or `stdio:`), and `scan`'s --format no longer
accepts `sarif` nor a `--output` flag. The container's entrypoint still ran the
stale `--format sarif --output FILE /scan/source`, so every scan failed even
after the image was unblocked (MCP-2395/#665).

Bridge the source-tree model to the new endpoint model without ever executing
the untrusted upstream (preserves the no-exec invariant, MCP-2206/#658):

- mcp-replay.py: a static MCP stdio server that replays the tool definitions
  MCPProxy already exports into /scan/source/tools.json (the same file the Cisco
  scanner consumes). entrypoint runs `ramparts scan stdio:python3:.../mcp-replay.py
  --format json`. Unit-tested against the rmcp handshake (initialize/tools-list/
  resources/prompts/ping/notifications/unknown-method/missing-file).
- Dockerfile: pin ramparts 0.8.2, ship its YARA `rules/`, `taxonomies/` and
  `config.yaml` (loaded relative to CWD — a binary-only image loads zero rules
  and finds nothing), and add a python3 runtime for the shim.
- engine.go: the rampartsIssue parser was also stale — v0.8.x serializes
  `issue_type`/`severity`/`message`/`details` (+ per-kind subject), not the old
  `type`/`impact`, so security_issues parsed with empty rule IDs and wrong
  severities. Updated to the v0.8.x shape; the yara_results path was already
  compatible. Parser proven end-to-end with a faithful v0.8.2 `--format json`
  fixture.
- registry: NetworkReq false (replay shim is local, YARA offline); refreshed
  description/comments.
- docs: refresh the ramparts design + the now-fixed/amd64-only arm64 note.

Full container E2E (built amd64 image vs a poisoned server) runs in CI/QA.

Related MCP-2422
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