Skip to content

feat(039): Security Scanner Plugin System#364

Merged
Dumbris merged 65 commits into
mainfrom
feat/039-security-scanner-plugins
Apr 9, 2026
Merged

feat(039): Security Scanner Plugin System#364
Dumbris merged 65 commits into
mainfrom
feat/039-security-scanner-plugins

Conversation

@Dumbris

@Dumbris Dumbris commented Apr 3, 2026

Copy link
Copy Markdown
Member

Summary

  • Pluggable security scanner system for analyzing quarantined MCP servers before approval
  • Docker-based scanners run in isolated containers, produce SARIF 2.1.0 reports
  • 4 bundled scanners: mcp-scan, Cisco MCP Scanner, Semgrep, Trivy
  • Parallel scan execution with risk scoring (0-100), approve/reject/rescan workflow
  • Full-stack: REST API (13 endpoints), CLI (12 subcommands), Web UI (Security dashboard), SSE events

What's Included

Backend (Go)

  • internal/security/scanner/ — Scanner types, registry, Docker runner, SARIF parser, scan engine, service
  • internal/storage/scanner.go — BBolt CRUD for 4 new buckets (scanners, jobs, reports, baselines)
  • internal/httpapi/security_scanner.go — 13 REST API endpoints + SecurityController interface
  • internal/runtime/events.go — 5 new SSE event types for scan lifecycle
  • internal/config/config.go — SecurityConfig section
  • cmd/mcpproxy/security_cmd.go — CLI commands: scanners/install/scan/approve/reject/overview

Frontend (Vue 3)

  • frontend/src/views/Security.vue — Dashboard with scanner marketplace, scan trigger, findings viewer
  • API methods, router, sidebar navigation updated

Documentation

  • docs/features/security-scanner-plugins.md — Feature guide with CLI reference, API reference
  • specs/039-security-scanner-plugins/ — Spec, plan, checklist, autonomous summary

Test plan

  • Scanner package tests pass with -race (registry, SARIF, engine, service)
  • Storage tests pass with -race (CRUD for all 4 entity types)
  • HTTP API tests pass with -race (all 13 endpoints)
  • Config tests pass (SecurityConfig parsing)
  • Frontend type-checks clean (vue-tsc --noEmit)
  • Frontend builds successfully (npm run build)
  • Full binary builds (go build ./...)
  • Pre-commit hooks pass (trailing whitespace, gofmt, OAS verification)
  • Manual: Install mcp-scan scanner and scan a real quarantined server
  • Manual: Verify Web UI Security page renders and functions
  • Manual: Test CLI commands end-to-end against running instance

claude added 2 commits April 3, 2026 09:05
Implement pluggable security scanner system for analyzing quarantined
MCP servers before approval. Docker-based scanners run in isolated
containers, produce SARIF reports, and integrate with the existing
quarantine workflow.

## Changes
- Scanner plugin architecture: types, registry (4 bundled scanners),
  Docker runner, SARIF 2.1.0 parser, parallel scan engine
- Storage: 4 new BBolt buckets (scanners, jobs, reports, baselines)
- Security service: install/configure scanners, scan/approve/reject
  workflow, integrity verification, risk scoring
- REST API: 13 endpoints for scanner management, scan operations,
  approval flow, and security overview
- CLI: mcpproxy security command group with 12 subcommands
- Web UI: Security dashboard with scanner marketplace, scan trigger,
  findings viewer, approve/reject actions
- SSE events: scan lifecycle and integrity alert events
- Documentation: feature guide, spec, plan, autonomous summary

## Testing
- 500+ tests passing across scanner, storage, httpapi, config packages
- Race detector clean on all new code
- Frontend type-checks clean and builds successfully

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 3, 2026

Copy link
Copy Markdown

Deploying mcpproxy-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 65995f2
Status: ✅  Deploy successful!
Preview URL: https://16781119.mcpproxy-docs.pages.dev
Branch Preview URL: https://feat-039-security-scanner-pl.mcpproxy-docs.pages.dev

View logs

@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

📦 Build Artifacts

Workflow Run: View Run
Branch: feat/039-security-scanner-plugins

Available Artifacts

  • archive-darwin-amd64 (26 MB)
  • archive-darwin-arm64 (23 MB)
  • archive-linux-amd64 (15 MB)
  • archive-linux-arm64 (13 MB)
  • archive-windows-amd64 (25 MB)
  • archive-windows-arm64 (23 MB)
  • frontend-dist-pr (0 MB)
  • installer-dmg-darwin-amd64 (19 MB)
  • installer-dmg-darwin-arm64 (17 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 24201040664 --repo smart-mcp-proxy/mcpproxy-go

Note: Artifacts expire in 14 days.

claude added 26 commits April 3, 2026 09:34
Critical fixes discovered during E2E testing:
- Wire scanner.Service as SecurityController in server setup
- Register security routes unconditionally (nil-guard in handlers)
- Use background context for scan goroutines (prevent request context cancellation)
- Sync registry from storage on startup (scanner state survives restart)
- Add source_dir parameter to scan API and service
- Fix Semgrep: mount source at /src (Docker image requirement), enable network
- Disable read-only for scanner containers (they need cache writes)
- Fix Trivy image name: aquasecurity/trivy (not aquasec/trivy)
- Add GetSecurityOverview alias on Service for interface compliance

Verified E2E:
- Semgrep scan found subprocess shell=True (HIGH) and eval() (MEDIUM)
- Full approve/reject/report CLI workflow tested against running server

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… scanning

Users no longer need to provide --source-dir. When scanning, MCPProxy
automatically:
1. Finds the running Docker container for the server (mcpproxy-<name>-*)
2. Extracts app source files via docker cp + docker diff analysis
3. Mounts extracted source for scanner containers
4. Cleans up temp files after scan completes

Fallback chain: Docker container -> working_dir -> command args -> error.
HTTP/SSE servers resolve to URL for mcp_connection scanners.

Verified E2E: mcpproxy security scan perplexity works with zero config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Scanner API keys configured via the API or CLI are now stored in the
OS keyring (macOS Keychain / Linux Secret Service / Windows Credential
Manager) using the existing mcpproxy secrets system.

- ConfigureScanner stores values via keyring, keeps ${keyring:...} refs
- Engine resolves keyring references at scan time before passing to containers
- Secrets visible in `mcpproxy secrets list` alongside server secrets
- Fallback to direct storage if keyring is unavailable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Web UI expected APIResponse format {success: true, data: ...} but
security handlers were writing raw data. Changed all handlers to use
writeSuccess() which wraps via contracts.NewSuccessResponse().

Updated all 23 httpapi security tests to parse the wrapped format.
Rebuilt frontend dist for embedded Web UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bugs found and fixed during E2E testing with real scanners:

1. CLI commands failed to parse wrapped API responses ({success, data})
   - Added unwrapAPIResponse() helper to extract data from envelope
   - Applied to all 8 json.Unmarshal points in security_cmd.go

2. Trivy scanner: "unknown command trivy for trivy"
   - Trivy Docker image has ENTRYPOINT=[trivy], so command was doubled
   - Fixed: command is now ["fs", "--format", "sarif", "/scan/source"]

3. Trivy scanner: "no space left on device" during DB download
   - Trivy downloads 89MB vuln DB to /tmp on first run
   - Tmpfs was 100MB with noexec — too small and restrictive
   - Fixed: increased to 500MB, removed noexec (scanners need it)

4. Trivy Docker image: aquasecurity/trivy not on Docker Hub
   - Changed to ghcr.io/aquasecurity/trivy:latest (GHCR registry)

Verified E2E: Semgrep + Trivy both complete scans successfully on
quarantined everything-server with real Docker containers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ores

Findings now include:
- help_uri: clickable link to CVE advisory (e.g., https://avd.aquasec.com/nvd/CVE-xxx)
- cvss_score: CVSS severity score (0-10) from SARIF properties
- package_name, installed_version, fixed_version: extracted from Trivy message format

CLI report shows:
  [HIGH] CVE-2025-66414 (trivy-mcp)
         Package: @modelcontextprotocol/sdk v0.6.0 -> fix: 1.24.0
         Details: https://avd.aquasec.com/nvd/cve-2025-66414

Web UI findings table shows clickable CVE links, package column,
fix version badge, and CVSS score.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…categories

Backend: SecurityScanSummary on Server contract, threat classification,
scan summary computation from stored reports.

Frontend: Security tab with risk score, threat summary cards, grouped
findings, scan button with polling. Scan badge on server cards.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The /api/v1/servers endpoint uses management.ListServers(), not the
legacy GetAllServers(). Moved scan summary enrichment to the httpapi
handler. Also re-classify legacy findings without threat_level on read.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AggregateReports now classifies findings before summarizing.
ReportSummary includes dangerous/warnings/info_level counts.
Frontend reads summary from API response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Risk score based on threat level instead of raw CVSS severity.
Scanner stdout/stderr/exit_code stored on ScannerJobStatus.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Configure button shows for all installed/configured scanners.
Dialog supports required, optional, and custom env vars with
keyring integration. Users can add OPENAI_API_KEY etc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each finding is now an expandable card with full details.
Scanner Execution Logs section shows stderr/stdout per scanner.
Fixed TS types to match Go API field names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ScanContext on ScanJob with source method, path, isolation status,
file list. File tree API with suspicious file markers.
Auto-prune keeps last 20 scans. Frontend scan context banner +
scanned files collapsible with lazy loading.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SARIF raw data can be 2MB+ (Semgrep includes all rule definitions).
Web UI was stuck on "Loading scan report..." because of this.
Now stripped by default; add ?include_sarif=true for CLI --output sarif.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ScanQueue with 3 concurrent workers, progress tracking, cancel support.
REST API: scan-all, queue, cancel-all endpoints.
CLI: --all flag and cancel-all subcommand.
Web UI: Scan All button with progress card.
Disabled servers auto-skipped with hint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SECURITY: UI now shows red error instead of green "safe" when scanners fail.
AggregatedReport tracks scan_complete, scanners_run/failed/total.
Server card badge shows "Scan Failed" (red) for incomplete scans.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes for scanner reliability:

1. resolveScanners() now checks ImageExists() before adding to run list.
   Scanners with non-existent Docker images (mcp-scan, cisco-mcp-scanner
   placeholders) are skipped with a warning log instead of failing at
   runtime with exit code 125.

2. Per-scanner cache directory persisted at ~/.mcpproxy/scanner-cache/<id>/
   mounted at /root/.cache in containers. Trivy DB (90MB) is now
   downloaded once and reused across runs instead of re-downloading
   every scan.

3. Fixed test to use nil docker runner for resolution logic tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Semgrep timed out on ElevenLabs (14,953 files, 361MB including 6,979
Python files from site-packages). Two fixes:

1. Semgrep timeout increased from 5min to 10min for large source trees
2. Semgrep command now excludes site-packages, node_modules, dist-packages
3. Source resolver excludes dependency directories from docker diff
   extraction — these are third-party packages, not user code

Before: ElevenLabs extracted 14,953 files (361MB) -> Semgrep timeout
After: Only actual MCP server code extracted -> fast scan

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For servers installed via uvx --from pkg@git+URL, the actual source
code lives at /root/.cache/uv/git-v0/checkouts/<hash>/<rev>/.
Previously this was extracted as /root/.cache (too broad) or filtered
out entirely.

Changes:
- extractAppRoot recognizes UV git checkout paths specifically
- isSystemPath excludes UV archive-v0 (dependencies) but keeps git-v0
- npm node_modules exclusion now allows npx cache paths
- Tests updated for UV git checkout extraction

Note: Scanner requires the server container to be fully connected.
If uvx is still downloading from GitHub when the scan runs, the source
won't be available yet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For servers installed via uvx --from pkg@git+URL (like gcore-mcp-server),
the actual source lives at /root/.cache/uv/git-v0/checkouts/<hash>/<rev>/.

Fixes:
- extractAppRoot excludes /root/.cache and /root/.local (too broad)
- Fallback path uses docker exec find to locate UV git checkouts
  directly when docker diff doesn't show them
- Removed /root from fallback dirs (was copying entire 10K+ file cache)

Before: 10,536 files (174MB) including all pip/uv dependencies
After: 26 files (422KB) — just the actual MCP server source code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pass 1: Semgrep on source + Trivy on lockfile (fast, immediate results)
Pass 2: Trivy full filesystem (background, auto-starts after Pass 1)
Results merged in single report with pass1/pass2 completion tracking.
7 new unit tests. Frontend shows both passes with progress indicator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In-memory cache for scan summaries, invalidated on scan start/complete.
Cold cache: 3.0s. Warm cache: 0.8s. Was: 12.1s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pagination with limit/offset/suspicious_only. Progressive loading
in frontend with "Load more" button. Suspicious files sorted first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
claude added 23 commits April 7, 2026 09:40
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…play

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…urity page

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Report link

Replace the full inline security report (findings grouped by threat type,
supply chain audit, scanner logs, scanned files) with a compact summary
card showing risk score, finding counts, and a "View Full Report" link
to /security/scans/:jobId. Remove unused computed properties and constants
(groupedFindings, pass2Findings, pass2HasDangerous, pass2HasWarnings,
pass1SupplyChainCount, FindingGroup, threatTypeLabels, dangerousTypes).
Add job_id field to SecurityScanReport TypeScript type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…story performance

- Security.vue used scan.job_id but API returns scan.id — fixed
- ListScanHistory: use ScannerStatuses.FindingsCount instead of loading
  all reports per job (was O(N*M) bucket scans, hung on 224 jobs)
- Update frontend dist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pdown

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ActivityTypeSecurityScan constant and ActivitySourceInternal source,
then wire EventTypeSecurityScan{Started,Completed,Failed} events to
new handlers that persist scan lifecycle records to the activity log.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…can report page

Shows actual server admin_state (enabled/disabled/quarantined) and offers
contextual actions: quarantine when dangerous findings on enabled server,
unquarantine when server is quarantined.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e nav to Security Scanners

Remove Approve and Reject buttons from the security scan summary in
ServerDetail.vue since these actions are no longer needed there (the
scan report page handles this). Also remove the associated
approveServerSecurity/rejectServerSecurity functions and the
securityActionLoading ref. Rename the left nav item from "Security"
to "Security Scanners" for clarity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the mcpproxy process is killed, scan jobs stuck in "running" or
"pending" status remain in the database permanently. This adds a
CleanupStaleJobs() method that marks all such jobs as failed with an
"interrupted by server restart" message, called once during startup
before serving requests. The previous stale cleanup in ListScanHistory
(which only ran on API calls and required a 2-hour threshold) is removed
in favor of this more reliable startup-based approach.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add docker_available field to SecurityOverview API response. Disable
"Scan All Servers" and per-server "Scan Now" buttons when Docker is not
running, with tooltip explaining the requirement. Show a warning alert
on the Security page when Docker is unavailable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…PI key hints, error details

- Status shows "enabled" (green), "disabled" (gray), "failed" (red)
- Error details shown inline when scanner fails (e.g., Docker pull failure)
- "API key required" warning + "Set API Key" button for unconfigured required env
- "Retry" button for failed scanners

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ppear in Activity Log

SetEmitter(s.runtime) was never called, so the scanner service used
NoopEmitter and all scan events were silently discarded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added ScannerStatuses to AggregatedReport struct and populated from
the ScanJob. ScanReport.vue now shows per-scanner stdout/stderr,
status badges, exit codes, and error messages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously HTTP/URL servers were exempted from empty scan detection,
so when tool export failed (server disconnected), the scan reported
risk_score=0 and scan_complete=true — misleading "clean" result.

Now empty scan detection checks only TotalFiles==0 && ToolsExported==0,
regardless of source method. HTTP servers with successfully exported
tools (ToolsExported > 0) still pass; those where export failed are
correctly flagged as empty_scan=true.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s.json for container

The scanner image uses Claude Agent SDK which reads OAuth tokens from
~/.claude/.credentials.json. When CLAUDE_CODE_OAUTH_TOKEN is configured
via the UI (stored in keyring), we now:

1. Create a temp .claude directory
2. Copy .claude.json from host (if exists)
3. Generate .credentials.json with the OAuth token
4. Mount the temp dir as /app/.claude:ro

Previously, the host's ~/.claude was mounted directly (read-only) but
had no .credentials.json, so the scanner fell back to pattern-only mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ls export fails

HTTP/SSE servers (SourceMethod="url") with 0 files and ToolsExported=0 were
slipping past the abort check, causing the Cisco MCP Scanner to fail with
"File not found: /scan/source/tools.json" because no tools.json was written.

Also fix AggregateReportsWithJobStatus to not mark URL or tool_definitions_only
scans as empty — these methods are inherently file-less and 0 files is
expected when scanners analyze URL endpoints or tool descriptions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace linear additive risk score with logarithmic diminishing returns:
  score = weight * log2(1 + unique_count)

Findings are now deduplicated by (rule_id + location) so duplicate detections
from multiple scanners no longer triple-count. Four dangerous findings now
score 58 instead of 90 (cap).

Add disclaimer banner on the Scan Report page and a tooltip on the Risk
column in the Security overview explaining that the score is an experimental
heuristic. There is no industry standard for aggregating multi-scanner MCP
security findings into a single number (verified via research on CVSS/EPSS,
Snyk/Ramparts/Cisco MCP Scanner, and OWASP/FAIR risk frameworks).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add per-scanner Docker image override so users can pin a specific version,
use a mirror registry, or substitute a custom build without editing the
bundled registry.

Backend:
  - ScannerPlugin.ImageOverride field (persisted via existing scanner storage)
  - EffectiveImage() helper returns override if set, else bundled DockerImage
  - Engine uses EffectiveImage() for container launch and existence checks
  - PUT /api/v1/security/scanners/{id}/config accepts optional docker_image
    alongside env map (either or both required)

Frontend:
  - Add Docker Image field to Configure dialog (placeholder shows default)
  - Show Configure button for all scanner states, not just installed/configured,
    so users can edit settings on disabled scanners too

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
claude added 5 commits April 9, 2026 15:38
Previously the Servers page, ServerDetail security tab, and Security
"Scan All" button were always visible regardless of whether any scanners
were actually installed. Clicking them just produced an error "no scanners
configured to run", which was a confusing dead end for users who hadn't
opted into security scanning.

Now scan-trigger UI is gated on the new SecurityOverview.ScannersEnabled
field — the count of scanners with status installed or configured (the
same set the engine actually runs).

Backend:
  - Add ScannersEnabled int field to SecurityOverview
  - Compute it in Service.GetOverview by filtering scanner status

Frontend:
  - New composable frontend/src/composables/useSecurityScannerStatus.ts
    that fetches /api/v1/security/overview once at app load and exposes
    a reactive hasEnabledScanners() helper. Module-scoped cache so all
    components share a single network call.
  - Servers.vue: gate "Scan All" header button
  - ServerDetail.vue: gate the "Security" tab itself and the "Scan Now"
    button inside the tab (defense-in-depth in case of deep links)
  - ServerCard.vue: gate the per-server "Scan" button
  - Security.vue: gate the "Scan All Servers" button using the existing
    overview response (which now includes scanners_enabled). Also calls
    refreshSecurityScannerStatus() after install/remove so other pages
    update immediately without a full reload.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The button used "btn btn-primary btn-outline" which in the current
DaisyUI theme collapses the text color onto the background, making
"Scan All" invisible except on hover (reported in both corporate and
business themes).

Match the styling of the other scan triggers (Security "Scan All
Servers", ServerDetail "Scan Now") by dropping btn-outline so the
button renders as a standard filled btn-primary with primary-content
text — readable in all themes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sidebar restructure:
  - Group navigation into Workspace / System sections with subtle
    uppercase labels — Dashboard sits solo at the top as the landing.
  - Workspace section holds the four priority items (Servers, Secrets,
    Activity Log, Security Scanners) with Agent Tokens nested as a
    second-level child under Secrets.
  - System section holds Repositories and Configuration, rendered at
    reduced weight (text-[13px], text-base-content/70) to signal they
    are secondary.
  - Remove Search entry — the top header already exposes a tools/servers
    search input.
  - Move Feedback out of the main nav to an icon button in the footer
    row next to the Theme dropdown, sized appropriately (h-9, w-5 icon)
    and flex-centered so it aligns correctly in both expanded and
    collapsed states.
  - Add inline SVG icons for every personal-edition nav item so the
    collapsed rail is recognisable at a glance.

Collapsible sidebar:
  - New sidebarCollapsed state + toggleSidebar action in the system
    store, persisted to localStorage under mcpproxy-sidebar-collapsed.
  - Sidebar animates between w-64 (expanded) and w-14 (icon rail). The
    main content padding in App.vue animates in lockstep.
  - Collapse chevron sits in the logo row when expanded; a separate
    expand chevron appears in the rail when collapsed.
  - Labels hide in collapsed mode; titles fall back to native tooltips
    for discoverability.

ServerCard tooltip:
  - Wrap the security scan badge in a DaisyUI tooltip that explains the
    current state (clean / N warnings / dangerous / failed / scanning /
    not scanned). Every variant includes the disclaimer that the score
    is an experimental heuristic and findings should be verified
    manually — so users don't over-trust a "Clean" label.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enabling a scanner no longer blocks while Docker pulls a multi-hundred-MB
image. Install/Configure now mark the scanner "pulling", return instantly,
and kick off the pull in a goroutine via a new pullManager. Success flips
it to installed/configured; failure flips it to error with the real docker
pull output in error_message. All transitions publish a new
security.scanner_changed SSE event so the web UI updates live without
polling.

Stop silently dropping scanners whose image is missing. engine.resolveScanners
now returns a resolvedScanner{plugin, prefail} and executeScan records any
prefailed scanner as a failed entry in the scan job. A "Scan All" run that
previously reported "1 of 1 scanners ran" when Snyk was silently skipped now
correctly reports "1 of 2 failed" with a user-readable reason pointing at
the missing image.

Normalize every custom scanner image under ghcr.io/smart-mcp-proxy/scanner-*
(vendor images for Semgrep and Trivy are kept upstream). Ship Dockerfiles
for snyk, cisco, ramparts, and nova-proximity under docker/scanners/ plus a
multi-arch GitHub Actions workflow that publishes them to GHCR on merges to
main. See docs/features/scanner-images.md for the rationale on keeping the
images in this repo instead of a separate one.

Scanner listing is now sorted alphabetically by ID in Registry.List(),
Service.ListScanners(), and Security.vue so the API, CLI, and web UI all
agree on the same order.

Frontend: new "pulling…" badge with spinner and image-name hint, dedicated
Retry button for error state (the old Retry wired to toggleScanner called
remove — wrong), Configure/Set-API-Key buttons disabled while pulling, and
a window-event bridge so Security.vue reacts to SSE updates without
re-fetching unrelated pages.

Also race-safe the scanner service EventEmitter with atomic.Pointer — the
old bare field assignment was latently racy and the new background pull
goroutine made it observable.

Tests: updated engine/service mocks for the new signature and the new
EmitSecurityScannerChanged method; fixed two preexisting test-drift
assertions in internal/httpapi (install returned "enabled"/"disabled",
tests still expected "installed"/"removed"). go test -race passes on
scanner, httpapi, and runtime packages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Snyk scanner: upstream package was renamed mcp-scan → snyk-agent-scan.
The renamed CLI no longer accepts --version, so switch build sanity
check to `-h` and update the entrypoint to call snyk-agent-scan.

Cisco scanner: mcp-scanner CLI also rejects --version; use `-h`.

Lint: remove the dead `buildFileTree` wrapper, unused mock types in
engine_test.go and service_test.go, the unused `findNewestSubdir`
helper, `shellescapeArgs`, and an empty else branch in server.go.

Windows tests: `os.UserHomeDir()` reads USERPROFILE on Windows, so the
npx/uvx resolver tests that only set HOME could not find the fake home.
Set both env vars in Setenv so the tests run cross-platform.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Dumbris Dumbris merged commit 60c2eef into main Apr 9, 2026
29 checks passed
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