Skip to content

Commit 9090e9f

Browse files
committed
fix(039): skip scanners with missing Docker images + persist cache
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>
1 parent 23c23a4 commit 9090e9f

3 files changed

Lines changed: 24 additions & 3 deletions

File tree

internal/security/scanner/docker.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ type ScannerRunConfig struct {
7171
Env map[string]string // Environment variables
7272
SourceDir string // Host directory to mount at /scan/source (read-only)
7373
ReportDir string // Host directory to mount at /scan/report (writable)
74+
CacheDir string // Host directory for scanner cache (persists between runs)
7475
NetworkMode string // "none", "bridge", or custom network name
7576
Timeout time.Duration // Container execution timeout
7677
ReadOnly bool // Read-only root filesystem
@@ -119,6 +120,11 @@ func (d *DockerRunner) RunScanner(ctx context.Context, cfg ScannerRunConfig) (st
119120
args = append(args, "-v", cfg.ReportDir+":/scan/report:rw")
120121
}
121122

123+
// Mount cache directory (persists scanner DB downloads between runs)
124+
if cfg.CacheDir != "" {
125+
args = append(args, "-v", cfg.CacheDir+":/root/.cache:rw")
126+
}
127+
122128
// Environment variables
123129
for k, v := range cfg.Env {
124130
args = append(args, "-e", k+"="+v)

internal/security/scanner/engine.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"os"
8+
"path/filepath"
79
"strings"
810
"sync"
911
"time"
@@ -166,10 +168,18 @@ func (e *Engine) resolveScanners(requestedIDs []string) ([]*ScannerPlugin, error
166168
return result, nil
167169
}
168170

169-
// Use all installed/configured scanners
171+
// Use all installed/configured scanners whose Docker images exist locally
170172
var result []*ScannerPlugin
171173
for _, s := range all {
172174
if s.Status == ScannerStatusInstalled || s.Status == ScannerStatusConfigured {
175+
// Verify Docker image exists before adding to scan list
176+
if e.docker != nil && !e.docker.ImageExists(context.Background(), s.DockerImage) {
177+
e.logger.Warn("Skipping scanner: Docker image not found locally",
178+
zap.String("scanner", s.ID),
179+
zap.String("image", s.DockerImage),
180+
)
181+
continue
182+
}
173183
result = append(result, s)
174184
}
175185
}
@@ -300,6 +310,10 @@ func (e *Engine) runSingleScanner(ctx context.Context, s *ScannerPlugin, req Sca
300310
networkMode = "bridge"
301311
}
302312

313+
// Create per-scanner cache directory (persists DB downloads between runs)
314+
cacheDir := filepath.Join(e.dataDir, "scanner-cache", s.ID)
315+
os.MkdirAll(cacheDir, 0755)
316+
303317
// Run scanner container
304318
cfg := ScannerRunConfig{
305319
ContainerName: GenerateContainerName(s.ID, req.ServerName),
@@ -308,6 +322,7 @@ func (e *Engine) runSingleScanner(ctx context.Context, s *ScannerPlugin, req Sca
308322
Env: env,
309323
SourceDir: req.SourceDir,
310324
ReportDir: reportDir,
325+
CacheDir: cacheDir,
311326
NetworkMode: networkMode,
312327
Timeout: timeout,
313328
ReadOnly: false, // Scanner containers need to write cache/temp files

internal/security/scanner/engine_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ func TestEngineResolveScanners(t *testing.T) {
210210
// Mark one scanner as installed
211211
registry.scanners["mcp-scan"].Status = ScannerStatusInstalled
212212

213-
docker := NewDockerRunner(logger)
214-
engine := NewEngine(docker, registry, dir, logger)
213+
// Use nil docker to skip image existence checks in tests
214+
engine := NewEngine(nil, registry, dir, logger)
215215

216216
// Resolve all installed
217217
scanners, err := engine.resolveScanners(nil)

0 commit comments

Comments
 (0)