# MCPProxy Security Scanner QA Report

Comprehensive audit of scanning feature across all server types | 2026-04-06 | Branch: feat/039-security-scanner-plugins

## 1. Executive Overview

11
Servers Tested
6
Scanners Installed
214
Total Scans Run
42
Total Bugs Found
6
Bugs Fixed
1,899
Total Findings
Findings by Severity (Global Overview)

Level | Count | Percentage
--- | --- | ---
Critical | 14 | 0.7%
High | 631 | 33.2%
Medium | 1,163 | 61.2%
Low | 91 | 4.8%

Threat classification: 247 dangerous, 473 warnings, 1,179 informational

## 2. Testing Coverage

QA Methodology

Phase | Method | Scope
--- | --- | ---
API Testing | curl + jq | 72 API tests across all scan endpoints
Frontend Code Review | Static analysis | ServerDetail.vue (2,000+ lines), Security.vue, api.ts
Backend Code Review | Static analysis | service.go, engine.go, source_resolver.go, registry_bundled.go, security_scanner.go
Visual UI Testing | Chrome screenshots | Global Security page, server detail security tabs
Scanner Quality | False positive analysis | All findings from cisco-mcp-scanner, trivy, semgrep

Server Types Tested

Type | Servers | Source Method | Scanners Run
--- | --- | --- | ---
HTTP (remote) | context7, hugginface, kaggle, supabase | url | 1-6
Streamable-HTTP (remote) | kubic, synapbus | url | 1-3
Stdio (local) | demo-filesystem | working_dir | 6
Stdio (Docker) | perplexity, screenshot-website-fast | docker_extract | 3-6
Stdio (quarantined) | malicious-demo | uvx_cache | 6 (1 failed)
Stdio (disconnected) | everything-server | npx_cache | 6

## 3. Server-by-Server Scan Results

context7
HTTP | https://mcp.context7.com/mcp | 2 tools
Risk: 60 2 False Positives
cisco-mcp-scanner: 2 findings (PROMPT INJECTION)
semgrep-mcp: 0 findings
trivy-mcp: 0 findings
ramparts: 0 findings
nova-proximity: 0 findings
mcp-scan: 0 findings
demo-filesystem
Stdio (local) | working_dir | 14 tools
Risk: 31 7 Findings
trivy-mcp: 5 findings (secrets)
semgrep-mcp: 2 findings (secrets)
cisco-mcp-scanner: 0 findings
ramparts/nova/mcp-scan: 0 findings
perplexity
Stdio (Docker) | docker_extract | 3 tools
Risk: 20 2 CVEs
trivy-mcp: 2 findings (MCP SDK CVEs)
All other scanners: 0 findings

CVE-2025-66414 (DNS rebinding), CVE-2026-0621 (ReDoS) in @modelcontextprotocol/sdk

malicious-demo
Stdio (quarantined) | uvx_cache | 0 tools (disconnected)
Risk: 0 Scan Incomplete
cisco-mcp-scanner: FAILED (tools.json not found)
All other scanners: 0 findings

Tool poisoning detector could not run - server failed to connect for tool export

ElevenLabs
Stdio | Error state | 0 tools
Risk: 100 16 False Positives
cisco-mcp-scanner: 16 findings (SYSTEM MANIPULATION)

Audio processing tools incorrectly flagged as system manipulation

hugginface / kaggle / supabase / kubic / synapbus
HTTP/Streamable-HTTP | url | 5-58 tools
Risk: 0 Clean

Note: supabase and kubic had 2 failed scanners each (cisco, trivy) due to tools.json not exported at time of scan

## 4. All Bugs Found (42 total)

### API / Backend Bugs (27)

# | Severity | Category | Description | Status
--- | --- | --- | --- | ---
1 | High | API | Concurrent scan returns 500 instead of 409 Conflict | Fixed
2 | High | Backend | Duplicate findings when merging Pass 1 + Pass 2 reports (same CVE appears twice) | Fixed
3 | High | Backend | Security overview threat levels all zero (dangerous/warnings/info_level not aggregated) | Fixed
4 | High | Backend | malicious-demo tools.json not exported - cisco scanner fails, server shows "clean" | Open
5 | High | Backend | CancelScan doesn't cancel running Docker containers (uses context.Background()) | Open
6 | High | Backend | Race condition between Pass 1 completion and Pass 2 start | Open
7 | High | Backend | Report directory (scanner-reports/) never cleaned up | Open
8 | High | Backend | No scanner-source matching: all scanners run on all source types | Open
9 | Medium | API | handleStartScan silently ignores JSON decode errors | Open
10 | Medium | Backend | Pass 1 cleanup removes temp dir before Pass 2 can use it | Open
11 | Medium | Backend | Race condition reading/writing job.Status without lock | Open
12 | Medium | API | POST scan for nonexistent server returns 500 instead of 404 | Open
13 | Medium | Backend | tools_exported inconsistently null for some servers | Open
14 | Medium | Backend | Inconsistent scanner count: some servers get 6 scanners, others only 1-3 | Open
15 | Medium | Backend | Docker cache mount at /root/.cache may conflict with scanner-specific paths | Open
16 | Medium | Backend | extractTopLevelDir includes /usr, /var for Docker - too broad for supply chain audit | Open
17 | Medium | Backend | cancel-all wipes scan job data for servers with active scans | Open
18 | Medium | Backend | Scan report has duplicate scanner entries for multi-scanned servers | Open
19 | Low | Backend | ValidateManifest requires Command non-empty, but 3 bundled scanners have nil Command | Open
20 | Low | Backend | parseResults silently treats unparseable scanner output as 'clean' | Open
21 | Low | Backend | File-to-findings path matching uses flawed normalization | Open
22 | Low | Backend | GetScanSummary doesn't check for active Pass 2 scans | Open
23 | Low | Backend | Cisco scanner hardcodes --tools /scan/source/tools.json path | Open
24 | Low | Backend | Docker-extracted scans report total_files=0 despite scanning extracted files | Open
25 | Low | Backend | Argument-based source resolution matches non-flag args as file paths incorrectly | Open
26 | Low | Backend | Job ID collision risk with time.Now().UnixNano() generation | Open
27 | Low | Backend | handleGetScanFiles retrieves report independently of job (potential mismatch) | Open

### Frontend / UI Bugs (15)

# | Severity | Category | Description | Status
--- | --- | --- | --- | ---
28 | High | UI | No Cancel button during active scan (API exists but UI doesn't expose it) | Fixed
29 | Medium | UI | Scanned Files section visible for tool_definitions_only source method | Fixed
30 | Medium | UI | No retry button after scan failure | Fixed
31 | High | UI | Race condition: polling completion fires before scanReport loads | Open
32 | High | UI | "Already in progress" error extracts job ID with fragile regex | Open
33 | Medium | UI | No debounce on Scan Now button (rapid clicks can cause issues) | Open
34 | Medium | UI | Polling continues silently on network errors with no max retry | Open
35 | Medium | UI | Scan error alert has no dismiss action | Open
36 | Medium | UI | Approve/Reject only shown with findings (can't approve clean servers) | Open
37 | Medium | UI | Active scan state lost on page navigation and return | Open
38 | Low | UI | Inconsistent risk score color thresholds between pages | Open
39 | Low | UI | Failed scanners counted as "completed" in progress bar | Open
40 | Low | UI | Scanner Execution Logs depend on scanStatus populated at wrong time | Open
41 | Low | UI | No explanation of Risk Score metric anywhere | Open
42 | Low | UI | No "last scanned" timestamp shown prominently | Open

## 5. Bugs Fixed (6 implemented)

Fix 1: Concurrent scan returns 409 Conflict Verified

File: `internal/httpapi/security_scanner.go`

When a scan is already running for a server and another scan is triggered, the API now returns HTTP 409 Conflict instead of 500 Internal Server Error.

- s.writeError(w, r, http.StatusInternalServerError, err.Error())
+ if strings.Contains(err.Error(), "already in progress") {
+ s.writeError(w, r, http.StatusConflict, err.Error())
+ } else {
+ s.writeError(w, r, http.StatusInternalServerError, err.Error())
+ }

Validated: POST /scan returns 409 with "scan already in progress" message

Fix 2: Deduplicate Pass 1/Pass 2 findings Verified

File: `internal/security/scanner/service.go`

When merging Pass 1 (security scan) and Pass 2 (supply chain audit) reports, duplicate findings (same scanner + rule + title) are now removed. Pass 1 findings take priority.

Example: Perplexity had 4 findings (2 duplicated). Now correctly shows 2.

Validated: perplexity report shows 2 findings (was 4)

Fix 3: Security overview threat level aggregation Verified

File: `internal/security/scanner/service.go`

The global security overview now correctly counts findings by threat level (dangerous, warnings, info_level). Previously these were all zero because ClassifyFinding() wasn't called during overview aggregation.

Validated: Overview shows dangerous=247, warnings=473, info_level=1179 (was all 0)

Fix 4: Cancel button in security tab Verified

File: `frontend/src/views/ServerDetail.vue`

Added a "Cancel" button that appears during active scans. Calls the existing cancelScan API endpoint, stops polling, and resets scan state.

Validated: Cancel button renders, calls API correctly

Fix 5: Scanned Files section visibility Verified

File: `frontend/src/views/ServerDetail.vue`

The Scanned Files collapsible section is now hidden for HTTP servers and tool_definitions_only source methods (no filesystem to show files for).

Validated: Section hidden for url, url_full, and tool_definitions_only

Fix 6: Retry button after scan error Verified

File: `frontend/src/views/ServerDetail.vue`

Added a "Retry" button to the scan error alert, allowing users to easily re-trigger a scan after failure without refreshing the page.

Validated: Retry button clears error and re-triggers scan

## 6. False Positive Analysis

False Positive: context7 "PROMPT INJECTION" (Risk: 60)

Scanner: cisco-mcp-scanner | Findings: 2

What was flagged:

Tool: resolve-library-id Evidence: "You MUST call this function before 'Query Documentation' tool to obtain a valid Context7-compatible library ID UNLESS the user explicitly provides a library ID..."

Analysis: This is standard MCP tool description pattern. Context7 instructs the LLM to call resolve-library-id before query-docs. The phrase "You MUST call" triggers the prompt injection detector, but this is normal tool orchestration guidance, not malicious prompt injection.

Verdict: FALSE POSITIVE — Cisco scanner is too aggressive with imperative language in tool descriptions.

False Positive: ElevenLabs "SYSTEM MANIPULATION" (Risk: 100)

Scanner: cisco-mcp-scanner | Findings: 16 (2 dangerous + 14 warning)

What was flagged: All audio tools (text_to_speech, speech_to_text, text_to_sound_effects, isolate_audio, speech_to_speech, etc.) flagged as "SYSTEM MANIPULATION"

Analysis: ElevenLabs is a legitimate audio processing API. Its tools interact with audio data, not system resources. The scanner's description of system manipulation ("unsolicited modification or deletion of files, registries") does not match what these tools do.

Verdict: FALSE POSITIVE — Cisco scanner misclassifies media processing as system manipulation.

True Positives (Confirmed Real Issues)

Server | Findings | Assessment
--- | --- | ---
demo-filesystem | 7 findings (Stripe key, GitHub PAT, private keys) | TRUE POSITIVE - real secrets in filesystem
perplexity | 2 CVEs (DNS rebinding, ReDoS in MCP SDK) | TRUE POSITIVE - real vulnerabilities in dependencies

## 7. Remaining Issues (Not Fixed)

Critical: malicious-demo tool poisoning not detected

The quarantined malicious-demo server can't have its tool definitions exported because it fails to connect (MCP initialize timeout). The cisco-mcp-scanner, which is the primary tool poisoning detector, requires `/scan/source/tools.json` which can't be created without a connection.

Impact: Quarantined servers that are truly malicious can't be scanned for tool poisoning — the exact scenario this feature is designed for.

Suggested fix: Cache tool definitions when they are first discovered (before quarantine), so scanning can use cached definitions even when the server refuses to connect.

High: Inconsistent scanner count across servers

Some servers get 6 scanners, others only 1-3. The scanner selection logic doesn't match scanner capabilities to source types. For example, hugginface (HTTP, 8 tools) only ran semgrep, while context7 (HTTP, 2 tools) ran all 6.

Impact: Inconsistent security coverage across servers.

Suggested fix: Implement scanner-to-source capability matching based on scanner input requirements.

High: False positive rate from cisco-mcp-scanner

The Cisco MCP Scanner produces a high false positive rate for standard MCP tool descriptions. Imperative language ("You MUST call", "always use") and media processing tools are incorrectly flagged.

Impact: Risk score of 60-100 for legitimate servers, eroding user trust.

Suggested fix: Implement scanner result post-processing to filter known false positive patterns, or adjust cisco scanner configuration thresholds.

## 8. Recommendations

### Priority 1 (Next Sprint)

- Cache tool definitions for quarantined servers to enable tool poisoning detection
- Implement scanner-source capability matching to avoid running irrelevant scanners
- Add false positive suppression rules for cisco-mcp-scanner (imperative language patterns)
- Fix CancelScan to actually terminate Docker containers
- Fix Pass 1/Pass 2 race condition (Pass 2 starts before Pass 1 cleanup)

### Priority 2 (Future)

- Add report directory cleanup (TTL-based or max-size)
- Add Risk Score explanation tooltip in the UI
- Show "last scanned" timestamp prominently
- Add scan history view (past scans comparison)
- Improve error handling for nonexistent servers (404 instead of 500)
- Add scanner input/output type enforcement during installation

### Priority 3 (Polish)

- Standardize risk score color thresholds across all pages
- Add debounce to Scan Now button
- Add polling error limit (stop after N consecutive failures)
- Show scanner capability badges in the scanner list
- Improve progress bar to distinguish failed vs completed scanners

Generated 2026-04-06 | MCPProxy v0.23.1 | Branch: feat/039-security-scanner-plugins
QA Coverage: 72 API tests, 15 UI bugs, 20 backend bugs, 8 design issues, 10 UX improvements