Skip to content

Commit 5073f52

Browse files
committed
fix(scan-eval): gate NetworkReq scanner skip on Docker-unavailable only (Codex finding)
The runnabilityReason function unconditionally skipped NetworkReq scanners even when Docker was enabled. When the operator explicitly opts in via --scanners + MCPPROXY_SCAN_EVAL_DOCKER=1, network-req scanners should be runnable — the Docker-off case is already covered by the earlier return. Adds TestSelectScanners_NetworkReq_RunnableWhenDockerEnabled to lock in the new behavior. Related #574
1 parent f699fa7 commit 5073f52

2 files changed

Lines changed: 42 additions & 9 deletions

File tree

cmd/scan-eval/scanners.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ func selectScanners(reg *scanner.Registry, csv string, dockerEnabled bool, looku
5656
// runnabilityReason returns "" when the scanner can run under the given
5757
// constraints, otherwise a human-readable reason it must be skipped. Used both
5858
// to partition in selectScanners and to explain the skip to the operator, so
59-
// the gating rules live in exactly one place (DRY). Order matters: Docker is
60-
// the cheapest gate, then secrets, then network.
59+
// the gating rules live in exactly one place (DRY). Order: Docker is the
60+
// cheapest gate, then secrets. Network-req scanners are NOT gated here — the
61+
// Docker gate above subsumes that (Docker-off → everything skipped).
6162
func runnabilityReason(p *scanner.ScannerPlugin, dockerEnabled bool, lookupEnv func(string) (string, bool)) string {
6263
if !dockerEnabled {
6364
return "Docker isolation disabled (set MCPPROXY_SCAN_EVAL_DOCKER=1 to enable)"
@@ -67,9 +68,12 @@ func runnabilityReason(p *scanner.ScannerPlugin, dockerEnabled bool, lookupEnv f
6768
return fmt.Sprintf("missing required secret %s", req.Key)
6869
}
6970
}
70-
if p.NetworkReq {
71-
return "requires network access (unavailable in offline corpus evaluation)"
72-
}
71+
// Network-req scanners are NOT skipped here: when Docker is available the
72+
// operator explicitly opted in via --scanners + MCPPROXY_SCAN_EVAL_DOCKER=1,
73+
// and running the scanner (even offline — the runner enforces NetworkMode=none,
74+
// Security-by-Default) is preferred over silently skipping it. The Docker gate
75+
// above already covers the Docker-unavailable case (everything skipped), so
76+
// reaching here means Docker IS enabled.
7377
return ""
7478
}
7579

cmd/scan-eval/scanners_test.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import (
1717
)
1818

1919
// testRegistry builds an isolated registry (temp dataDir → no user registry
20-
// bleed) seeded with two controlled custom scanners: one offline+no-secret and
21-
// one that requires a secret env var. Metadata-driven gating is exercised
22-
// against these so the tests do not couple to bundled-registry churn.
20+
// bleed) seeded with three controlled custom scanners: one offline+no-secret,
21+
// one that requires a secret env var, and one that requires network access.
22+
// Metadata-driven gating is exercised against these so the tests do not couple
23+
// to bundled-registry churn.
2324
func testRegistry(t *testing.T) *scanner.Registry {
2425
t.Helper()
2526
reg := scanner.NewRegistry(t.TempDir(), zap.NewNop())
@@ -38,7 +39,15 @@ func testRegistry(t *testing.T) *scanner.Registry {
3839
Command: []string{"scan"},
3940
RequiredEnv: []scanner.EnvRequirement{{Key: "ENG_TEST_TOKEN", Label: "Token", Secret: true}},
4041
}
41-
for _, s := range []*scanner.ScannerPlugin{offline, secret} {
42+
netReq := &scanner.ScannerPlugin{
43+
ID: "eng-netreq",
44+
Name: "Eng Network Required",
45+
DockerImage: "example/eng-netreq:latest",
46+
Inputs: []string{"source"},
47+
Command: []string{"scan"},
48+
NetworkReq: true,
49+
}
50+
for _, s := range []*scanner.ScannerPlugin{offline, secret, netReq} {
4251
if err := reg.Register(s); err != nil {
4352
t.Fatalf("Register(%s): %v", s.ID, err)
4453
}
@@ -100,6 +109,26 @@ func TestSelectScanners_DockerDisabled(t *testing.T) {
100109
}
101110
}
102111

112+
// TestSelectScanners_NetworkReq_RunnableWhenDockerEnabled — a scanner that
113+
// requires network access is NOT skipped when Docker is enabled. The operator
114+
// explicitly opted in via --scanners + MCPPROXY_SCAN_EVAL_DOCKER=1; running the
115+
// scanner (even offline — the runner enforces NetworkMode=none) is preferred
116+
// over silently skipping it. The Docker-unavailable case is covered by
117+
// TestSelectScanners_DockerDisabled (everything skipped).
118+
func TestSelectScanners_NetworkReq_RunnableWhenDockerEnabled(t *testing.T) {
119+
reg := testRegistry(t)
120+
run, skipped, err := selectScanners(reg, "eng-netreq", true, env(nil))
121+
if err != nil {
122+
t.Fatalf("selectScanners: %v", err)
123+
}
124+
if len(skipped) != 0 {
125+
t.Errorf("skipped = %+v, want none (docker enabled, network-req scanner should be runnable)", skipped)
126+
}
127+
if len(run) != 1 || run[0].ID != "eng-netreq" {
128+
t.Errorf("run = %v, want [eng-netreq]", ids(run))
129+
}
130+
}
131+
103132
// TestSelectScanners_Runnable — offline scanner with Docker enabled, and a
104133
// secret scanner with its secret present, both become runnable.
105134
func TestSelectScanners_Runnable(t *testing.T) {

0 commit comments

Comments
 (0)