From 45990986033e7c11be06b54c34fb1e225b25ea7a Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Sun, 23 Aug 2026 21:27:44 +0300 Subject: [PATCH 1/8] feat(security): scan_server/get_scan_report in quarantine_security + fix Scan-All visibility and Docker gating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TPA scanning existed but was unreachable from where people actually are. MCP: quarantine_security — the most-used security surface — never mentioned scanning and had no way to run one. It gains two operations, `scan_server` (runs the offline in-process baseline scan; returns the verdict when it settles, otherwise the job id) and `get_scan_report` (latest verdict, counts, findings). list_quarantined / inspect_quarantined / inspect_tools now carry a one-line scan status, so an unscanned server reads as "never scanned — run scan_server first" instead of looking clean. Neither operation adds a parameter (both reuse `name`); the three frozen tool-surface goldens were regenerated deliberately and their enumerated-delta gates widened to name quarantine_security. Web UI: the Security page's "Scan All Servers" button never rendered on a fresh install (the overview counted only BBolt-persisted scanners and missed the always-on in-process baseline, reporting 0) and was disabled without Docker — the same false gate already fixed for the per-server Scan Now button. The overview now counts registry in-process scanners, and the button is no longer gated on Docker; the tooltip and the Docker alert say what Docker actually affects (optional deep scanners). CLI: `mcpproxy security --help` opened with "Security scanners run as Docker containers", which is only true of the optional layer. Also fixes dispatch for the advertised `quarantine_server` operation name, which only accepted the historical "quarantine" spelling. --- cmd/mcpproxy/security_cmd.go | 11 +- docs/features/security-quarantine.md | 22 ++ frontend/src/views/Security.vue | 25 +- frontend/src/views/security/deepScanState.ts | 17 + .../unit/security-scan-all-ungate.spec.ts | 109 +++++++ .../scanner/overview_baseline_test.go | 72 +++++ internal/security/scanner/service.go | 24 +- internal/server/mcp.go | 39 ++- internal/server/mcp_menu_surface_test.go | 74 +++++ internal/server/mcp_quarantine_scan.go | 269 ++++++++++++++++ internal/server/mcp_quarantine_scan_test.go | 301 +++++++++++++++++ internal/server/scan_admission_test.go | 61 +++- internal/server/server.go | 15 + .../code_execution_mode.json | 10 +- .../toolslist_goldens/default_server.json | 10 +- .../pre099/code_execution_mode.json | 304 ++++++++++++++++++ .../retrieve_tools_mode.json | 10 +- internal/server/toolslist_snapshot_test.go | 77 +++-- 18 files changed, 1384 insertions(+), 66 deletions(-) create mode 100644 frontend/tests/unit/security-scan-all-ungate.spec.ts create mode 100644 internal/security/scanner/overview_baseline_test.go create mode 100644 internal/server/mcp_quarantine_scan.go create mode 100644 internal/server/mcp_quarantine_scan_test.go create mode 100644 internal/server/testdata/toolslist_goldens/pre099/code_execution_mode.json diff --git a/cmd/mcpproxy/security_cmd.go b/cmd/mcpproxy/security_cmd.go index 688f2d392..87994062c 100644 --- a/cmd/mcpproxy/security_cmd.go +++ b/cmd/mcpproxy/security_cmd.go @@ -41,8 +41,15 @@ func GetSecurityCommand() *cobra.Command { Long: `Commands for managing security scanners, scanning MCP servers, and reviewing scan results. -Security scanners run as Docker containers and analyze upstream MCP servers -for vulnerabilities, tool poisoning attacks, and other security issues. +Scanning works out of the box: the offline baseline scanner is built into +mcpproxy, runs in-process on every scan, and needs no Docker and no setup. It +analyzes tool descriptions and schemas for tool poisoning attacks (TPAs), +prompt injection, and data exfiltration. + +Deep scanners are the optional extra layer. They run as Docker containers for +source and dependency analysis (CVEs, secrets), so they need Docker plus +"deep scan" enabled — and when they are unavailable they are skipped, never +blocking the baseline verdict. Examples: mcpproxy security scanners diff --git a/docs/features/security-quarantine.md b/docs/features/security-quarantine.md index 309021335..c053553b7 100644 --- a/docs/features/security-quarantine.md +++ b/docs/features/security-quarantine.md @@ -172,6 +172,28 @@ Manage held prompts with the `quarantine_security` MCP tool: ## Managing Quarantine +### Scan a Server for TPAs (MCP) + +The `quarantine_security` tool can also run and read the TPA scan, so an agent +reviewing a held server does not have to leave for the CLI or web UI: + +```jsonc +// run the offline baseline scan (in-process, no Docker required) +{ "operation": "scan_server", "name": "github" } +// read the latest verdict + findings +{ "operation": "get_scan_report", "name": "github" } +``` + +`scan_server` answers with the verdict when the scan settles quickly, otherwise +with the job id and `"status": "scan started"` — poll `get_scan_report` for the +result. Every `list_quarantined`, `inspect_quarantined` and `inspect_tools` +response also carries a one-line `scan_status`, so a server nobody ever scanned +reads as `never scanned — run scan_server first` instead of looking clean. + +The optional Docker-based deep scanners are a separate layer: they run only when +[deep scan](/features/security-scanner-plugins) is enabled, and when they are +unavailable they are skipped without changing the baseline verdict. + ### View Quarantined Servers **Web UI:** diff --git a/frontend/src/views/Security.vue b/frontend/src/views/Security.vue index c289a6862..6467f6a03 100644 --- a/frontend/src/views/Security.vue +++ b/frontend/src/views/Security.vue @@ -7,12 +7,16 @@

Configure security scanner plugins and review scan results

-
- @@ -151,12 +155,13 @@
- -
+ +
- Docker is not running. Security scanners require Docker to analyze MCP servers. + Docker is not running, so the optional deep scanners are skipped. The built-in offline baseline scan still runs.