Skip to main content

Security Scanner Plugin System

MCPProxy integrates external security scanners as Docker-based plugins. Scanners analyze quarantined servers before approval, detecting tool poisoning attacks, prompt injection, malware, secrets leakage, and supply chain risks.

CLI reference: for the full breakdown of every mcpproxy security subcommand, flags, and examples see Security Commands.

Overview

Every scanner is a plugin — there is no built-in scanner. MCPProxy exposes a universal plugin interface that scanner authors implement by shipping a Docker image that reads from /scan/source and writes SARIF to /scan/report/results.sarif. Users browse a bundled scanner registry, enable scanners with one command, configure API keys if needed, and start scanning.

Key features

  • Plugin-only architecture — every scanner is a Docker-based plugin.
  • Universal scanner interface — source filesystem input, SARIF output.
  • Parallel scanning — multiple scanners run concurrently; per-scanner failures are isolated.
  • Source resolver — detects the right thing to scan for npx/uvx/pipx/bunx package-runner commands, falls back to working dir or tool definitions for HTTP/SSE servers.
  • Risk scoring — composite 0–100 risk score from aggregated findings.
  • Integrity verification — image digest checks on server restart.
  • Multi-UI — REST API, CLI, Web UI all powered by the same backend, consistent status vocabulary.
  • Failed-scanner visibility — both CLI and Web UI surface per-scanner failures so silent crashes don't look like "clean".

What changed recently (2026-04)

  • security approve now actually unquarantines the server and indexes its tools (was a no-op stub).
  • Source resolver tries the package cache first for package-runner commands, so filesystem-server-style data-dir args are no longer mistaken for source code.
  • The scanner configure path no longer touches the OS keyring by default on macOS. Env values are stored in the scanner record in BBolt. See Security Commands → configure for details and the opt-in flag.
  • security report surfaces the count of failed scanners and their names.
  • security scan --dry-run prints a source-resolution plan without starting any containers.
  • security scan blocking mode has a real wait loop (no more hangs) with a hard timeout.
  • scanner status vocabulary unified between table and JSON: available / pulling / installed / configured / error.

Quick start

1. List available scanners

mcpproxy security scanners
ID                   NAME                     VENDOR                  STATUS       INPUTS
-------------------------------------------------------------------------------------------------------
cisco-mcp-scanner Cisco MCP Scanner Cisco AI Defense available source
mcp-ai-scanner MCP AI Scanner MCPProxy available source
mcp-scan Snyk Agent Scan Snyk (Invariant Labs) available source
nova-proximity Nova Proximity MCPProxy available source
ramparts Ramparts MCP Scanner Javelin available source
semgrep-mcp Semgrep MCP Rules Semgrep available source
trivy-mcp Trivy Vulnerability... Aqua Security available source, container_image

2. Enable scanners

mcpproxy security enable nova-proximity
mcpproxy security enable trivy-mcp
mcpproxy security enable mcp-ai-scanner

(install is a hidden alias for enable, kept for backward compatibility.)

3. Configure API keys (if the scanner needs them)

Only a subset of scanners requires an API key. mcp-scan (Snyk Agent Scan) needs a free token from Snyk; mcp-ai-scanner can use an optional Anthropic API key or Claude Code OAuth token for richer analysis but works in pattern-only mode without one.

mcpproxy security configure mcp-scan --env SNYK_TOKEN=snyk_xxx

Values are stored directly in the scanner record in BBolt. The scanner container receives them via Docker --env flags at scan time.

4. Scan a quarantined server

mcpproxy security scan github-server

The CLI runs all enabled scanners in parallel, prints live progress, and shows a summary.

5. Review the findings

mcpproxy security report github-server
mcpproxy security status github-server # per-scanner detail including stderr
mcpproxy security report github-server -o sarif > github-server.sarif

6. Approve or reject

# Approve (unquarantines and indexes tools)
mcpproxy security approve github-server

# Or reject (deletes scan artifacts, keeps server quarantined)
mcpproxy security reject github-server

Scanner registry

MCPProxy ships with a bundled registry of 7 scanners. The bundled list lives in internal/security/scanner/registry_bundled.go.

ScannerVendorInputsRequired envNotes
cisco-mcp-scannerCisco AI DefensesourceYARA rules + readiness analysis. Needs tools.json in the source dir.
mcp-ai-scannerMCPProxysource— (optional ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN)Agent-based AI analysis with a pattern-only fallback. Lives in a separate repo.
mcp-scanSnyk (Invariant Labs)sourceSNYK_TOKENTool poisoning, prompt injection, tool shadowing, toxic flows, secrets, rug pulls.
nova-proximityMCPProxy (NOVA-inspired rules)sourceKeyword-based, fully offline. Very fast.
rampartsJavelinsourceRust-based YARA scanner. (Known upstream issue on arm64 macOS — see Scanner Images.)
semgrep-mcpSemgrepsourceStatic analysis with MCP-specific rules. Uses the upstream returntocorp/semgrep:latest image.
trivy-mcpAqua Securitysource, container_imageFilesystem + CVE scan. Uses the upstream ghcr.io/aquasecurity/trivy:latest image.

See Scanner Images for the image sources and why vendor images are preferred over custom wrappers.

Custom / out-of-tree scanners

Custom scanners can be added by pushing a Docker image that implements the plugin contract (see the Plugin Interface section below) and registering it via the REST API:

curl -X POST http://localhost:8080/api/v1/security/scanners/my-custom-scanner/enable \
-H "X-API-Key: $API_KEY"

Remote scanner registries (not just the bundled one) are planned but currently opt-in via the security.scanner_registry_url config field.

CLI commands

The complete CLI reference is in docs/cli/security-commands.md. At a glance:

# Scanner lifecycle
mcpproxy security scanners # list with status
mcpproxy security enable <scanner-id> # pull image
mcpproxy security disable <scanner-id> # remove image
mcpproxy security configure <id> --env KEY=VALUE # set env vars (repeatable)

# Scan operations
mcpproxy security scan <server> # blocking, with live progress
mcpproxy security scan <server> --async # return job id
mcpproxy security scan <server> --dry-run # print plan, no containers
mcpproxy security scan --all # batch scan, progress table
mcpproxy security scan <server> --scanners a,b,c # subset of scanners
mcpproxy security rescan <server> # alias for scan
mcpproxy security status <server> # per-scanner detail
mcpproxy security report <server> [-o json|yaml|sarif]
mcpproxy security cancel-all # cancel batch in progress

# Approval workflow
mcpproxy security approve <server> [--force] # unquarantine + index
mcpproxy security reject <server> # delete artifacts, keep quarantined
mcpproxy security integrity <server> # verify against baseline

# Dashboard
mcpproxy security overview

All subcommands support -o json, -o yaml, and --json (shorthand) for scripting.

REST API

Scanner management

MethodEndpointDescription
GET/api/v1/security/scannersList all scanners from the registry
GET/api/v1/security/scanners/{id}/statusPer-scanner detail
POST/api/v1/security/scanners/{id}/enableEnable (pull image)
POST/api/v1/security/scanners/{id}/disableDisable (remove image)
PUT/api/v1/security/scanners/{id}/configSet env vars (JSON body {"env": {"KEY": "VALUE"}})
POST/api/v1/security/scanners/installLegacy install endpoint — prefer the per-scanner enable
DELETE/api/v1/security/scanners/{id}Legacy remove endpoint — prefer disable

Scan operations

MethodEndpointDescription
POST/api/v1/servers/{name}/scanStart a scan (body: {"scanners": [...], "dry_run": false})
GET/api/v1/servers/{name}/scan/statusCurrent/latest scan job with per-scanner statuses
GET/api/v1/servers/{name}/scan/reportAggregated report (add ?include_sarif=true for raw SARIF)
GET/api/v1/servers/{name}/scan/filesSource-resolution preview (source_method, path, file list)
POST/api/v1/servers/{name}/scan/cancelCancel the current scan
GET/api/v1/security/scansScan history across all servers
GET/api/v1/security/scans/{jobId}/reportFetch a specific scan's aggregated report
POST/api/v1/security/scan-allKick off a batch scan of all servers
GET/api/v1/security/queueBatch scan progress
POST/api/v1/security/cancel-allCancel the current batch

Approval workflow

MethodEndpointDescription
POST/api/v1/servers/{name}/security/approveApprove: saves integrity baseline + unquarantines + indexes tools. Body {"force": true} bypasses the critical-findings guard.
POST/api/v1/servers/{name}/security/rejectReject: deletes scan artifacts, keeps server quarantined.
GET/api/v1/servers/{name}/integrityIntegrity check against the approved baseline.

Dashboard

MethodEndpointDescription
GET/api/v1/security/overviewTotals: scanners installed, total scans, findings by severity, Docker availability, last scan time

SSE events

Emitted on the /events SSE stream:

EventPayload fields
security.scan_startedserver_name, scanners[], job_id
security.scan_progressserver_name, scanner_id, status, progress
security.scan_completedserver_name, findings_summary
security.scan_failedserver_name, scanner_id, error
security.integrity_alertserver_name, alert_type, action

Plugin interface

Every scanner plugin is a Docker image with the following contract:

Mount / pathDirectionDescription
/scan/sourceread-onlyThe resolved source directory for the server under scan
/scan/reportread-writeThe scanner must emit results.sarif here
/scan/source/tools.jsonread-onlyThe server's exported MCP tool schemas ([{ "name": ..., "description": ..., "inputSchema": {...} }, ...]). Written by mcpproxy before starting the scanner container.

Scanners communicate results via SARIF 2.1.0. Exit code 0 indicates "scan completed (with or without findings)". Non-zero exit codes are surfaced as scanner failed in the aggregated report.

Source resolution order

1. Docker extraction (for Docker-isolated servers)
2. Package cache (npx/uvx/pipx/bunx) — PREFERRED for package runners
3. WorkingDir (from server config)
4. Arg-scan fallback — accepts only directories containing source markers
5. Tool definitions only — last resort (HTTP / SSE / unresolvable)

The resolved method and path are recorded on the scan job and visible via both the text and JSON report. See Security Commands → scan for more.

SARIF normalization

Scanners produce results in SARIF 2.1.0 format. MCPProxy normalizes findings to a consistent severity vocabulary:

SARIF levelMCPProxy severity
errorhigh
warningmedium
notelow
noneinfo

critical severity is reserved for findings explicitly marked critical in the scanner's SARIF properties.severity field or via a rule-level override. Critical findings block security approve unless --force is supplied.

MCPProxy also augments each finding with a user-facing threat_type (tool_poisoning / prompt_injection / rug_pull / supply_chain / malicious_code / uncategorized) and threat_level (dangerous / warning / info) so the Web UI can group findings in a way that's meaningful to MCP server trust decisions.

Configuration

{
"security": {
"auto_scan_quarantined": false,
"scan_timeout_default": "60s",
"integrity_check_interval": "1h",
"integrity_check_on_restart": false,
"scanner_registry_url": "",
"runtime_read_only": false,
"runtime_tmpfs_size": "100M"
}
}
SettingDefaultDescription
auto_scan_quarantinedfalseAuto-scan newly quarantined servers as soon as they're added.
scan_timeout_default60sPer-scanner timeout. The blocking CLI security scan computes a hard ceiling as scan_timeout_default × scanner_count + 30s, clamped between 15 and 30 minutes.
integrity_check_interval1hPeriodic integrity check interval (when running as a daemon with integrity checks enabled).
integrity_check_on_restartfalseRe-verify integrity baseline every time an approved server is restarted.
scanner_registry_url""Remote scanner registry URL (opt-in). When empty, only the bundled registry is used.
runtime_read_onlyfalseRun approved server containers with --read-only and a tmpfs overlay. (P2 feature, requires Docker isolation.)
runtime_tmpfs_size100MTmpfs size for read-only runtime containers.

Environment variables

VariableEffect
MCPPROXY_KEYRING_WRITESet to 1, true, or yes to opt in to writing secrets to the OS keyring on macOS. Default (empty) routes writes to the in-config fallback. Linux and Windows default to enabled.

Data storage

Scanner data is stored in the BBolt database (~/.mcpproxy/config.db) in these buckets:

BucketContent
security_scannersInstalled scanner configurations (including configured_env)
security_scan_jobsScan job records and per-scanner statuses
security_reportsAggregated reports, per-scanner SARIF payloads, normalized findings
integrity_baselinesPer-server approval records (image digest, scan report IDs, approved-by)

Web UI

The Security page at /security in the Web UI mirrors the CLI and provides:

  • Dashboard stats — scanners installed, total scans, findings by severity, Docker availability, Last scan timestamp.
  • Scanner table — every scanner in the registry with its current status, vendor link, and configure button.
  • Scan All Servers — batch scan trigger with live progress.
  • Scan report viewer at /security/scans/{jobId} — risk-score badge, finding detail cards with rule/location/scanner, per-scanner execution logs including stderr from failed scanners, and scan context (source method + path + file count).
  • Approve Server / Force Approve / Reject — scanner-gated approval dialog that requires a completed scan (or explicit force) before calling the approval endpoint.

Known limitations

  • Ramparts on arm64 macOS — the upstream scanner image ships a binary linked against a newer GLIBC than the image base and fails every run on arm64. Track the scanner-ramparts image rebuild for a fix. Other 6 of 7 scanners work out of the box on arm64 macOS.
  • Cisco scanner output has a hardcoded server_url header in its stdout (https://mcp.deepwiki.com/mcp). Cosmetic, does not affect findings.
  • Pass 2 (supply-chain audit) currently requires Docker isolation to be enabled, otherwise it fails source resolution. The UI doesn't yet surface this precondition.