Skip to content

fix(scanner): wire ramparts v0.8.x URL/stdio scanning via static replay shim#670

Merged
Dumbris merged 1 commit into
mainfrom
fix/ramparts-v08-url-scanning
Jun 14, 2026
Merged

fix(scanner): wire ramparts v0.8.x URL/stdio scanning via static replay shim#670
Dumbris merged 1 commit into
mainfrom
fix/ramparts-v08-url-scanning

Conversation

@Dumbris

@Dumbris Dumbris commented Jun 14, 2026

Copy link
Copy Markdown
Member

Problem (MCP-2422)

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

  1. The image shipped only the binary — but ramparts loads its YARA rules/ (and taxonomies/, config.yaml) from the working directory at runtime, so even a corrected CLI would load zero rules → no findings.
  2. The Go 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 mis-graded severities.

Approach — static stdio replay shim (no untrusted execution)

MCPProxy must never re-execute the untrusted upstream just to give ramparts a target (would violate the no-exec invariant, MCP-2206/#658). Instead the engine already exports the captured tool definitions into /scan/source/tools.json (the file the Cisco scanner consumes). We replay them:

ramparts scan "stdio:python3:/usr/local/bin/mcp-replay.py" --format json

mcp-replay.py speaks just enough MCP stdio (initialize → tools/list → resources/prompts → ping) to serve those static definitions. No upstream code runs.

Changes

  • docker/scanners/ramparts/mcp-replay.py (+ mcp-replay_test.py, 6/6 passing) — static MCP stdio replay server.
  • entrypoint.sh — v0.8.x invocation; emit native JSON, tolerate findings/offline-LLM non-zero exit, fail only if no report.
  • Dockerfile — pin ramparts 0.8.2, ship rules/ + taxonomies/ + config.yaml, add python3.
  • engine.go — fix rampartsIssue to the v0.8.x SecurityIssue shape (yara_results path was already compatible).
  • registry_bundled.goNetworkReq: false (shim is local, YARA offline); refreshed description/comments.
  • docs — ramparts design note + corrected amd64-only / emulated-on-arm64 status.

Verification

  • go build ./...
  • go test ./internal/security/... -race ✅ (incl. new TestEngineParseResultsRampartsV08JSON proving the exact v0.8.2 --format json shape yields findings, and TestRampartsV08Invariants)
  • python3 docker/scanners/ramparts/mcp-replay_test.py → 6/6 ✅
  • golangci-lint (v2, CI config) → 0 issues ✅; shellcheck entrypoint.sh ✅; gofmt clean ✅

Deferred to CI/QA: full container E2E (built amd64 image vs a poisoned MCP server). No Rust toolchain locally and the image is amd64-only, so the end-to-end "findings out of the built image" check runs in CI + QA. The parse-boundary and shim-protocol layers are unit-proven here.

Related MCP-2422

…ay shim

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
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: e1612e6
Status: ✅  Deploy successful!
Preview URL: https://8dfb0cf3.mcpproxy-docs.pages.dev
Branch Preview URL: https://fix-ramparts-v08-url-scannin.mcpproxy-docs.pages.dev

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

Files with missing lines Patch % Lines
internal/security/scanner/engine.go 42.10% 7 Missing and 4 partials ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: fix/ramparts-v08-url-scanning

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

Note: Artifacts expire in 14 days.

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

Claude review ACCEPT — ramparts v0.8.x URL/stdio scanning via static replay shim. Verified: correct v0.8.x CLI invocation, no-exec invariant (shim re-serves tools.json, never re-executes upstream), tools.json data contract (inputSchema), output parsing (issue_type/severity/message), report pickup (results.json), tests green, no regression to other scanners. Residual full-container E2E deferred to QA.

@Dumbris Dumbris enabled auto-merge (squash) June 14, 2026 18:03
@Dumbris Dumbris merged commit 93d9b58 into main Jun 14, 2026
42 checks passed
Dumbris added a commit that referenced this pull request Jun 15, 2026
Apply four hardenings to the npx/uvx package-source fetch extractor
(internal/security/scanner/package_fetch.go), for both the tar and zip
paths, while keeping behavior identical for well-formed small archives:

1. Decompression bomb across skipped members: read the gzip tar stream
   through a cappedReader so EVERY decompressed byte is bounded —
   including the bodies of members the extractor skips (oversized,
   symlink, traversal), which tar.Reader still decompresses while
   advancing to the next header.
2. safeJoin REJECTS path-traversal and absolute entries with an error
   instead of cleaning '../' into an in-dest path (the rewrite bug).
3. Oversized and partially-written files are charged to the file-count
   and byte caps (a truncated-but-large file still counts).
4. Directory creation is capped (charged to the same combined entry
   limit) before the file-count limit can be bypassed by an
   all-directories archive — for both tar and zip.

extractTarballGz/extractZip take an explicit maxFileBytes so the
per-file cap is testable. Adds TDD fixtures: gzip bomb of skipped
members, traversal reject (not rewrite), oversized-charged, dir cap.

Related #670
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