|
9 | 9 | "path/filepath" |
10 | 10 | "strings" |
11 | 11 |
|
| 12 | + "github.com/smart-mcp-proxy/mcpproxy-go/internal/dockernaming" |
12 | 13 | "github.com/smart-mcp-proxy/mcpproxy-go/internal/shellwrap" |
13 | 14 | "go.uber.org/zap" |
14 | 15 | ) |
@@ -307,12 +308,16 @@ func dirLooksLikeSource(dir string) bool { |
307 | 308 | return found |
308 | 309 | } |
309 | 310 |
|
310 | | -// findServerContainer finds the running Docker container for a server |
311 | | -// MCPProxy names containers as: mcpproxy-<sanitized-server-name>-<suffix> |
| 311 | +// findServerContainer finds the running Docker container for a server. |
| 312 | +// MCPProxy names containers as: mcpproxy-<sanitized-server-name>-<suffix>. |
| 313 | +// The sanitization MUST match the one used to name the container at launch |
| 314 | +// (internal/upstream/core), hence the shared dockernaming package — official |
| 315 | +// registry names like "com.pulsemcp/google-flights" keep their dots and would |
| 316 | +// otherwise never match (MCP-2123). |
312 | 317 | func (r *SourceResolver) findServerContainer(ctx context.Context, serverName string) (string, error) { |
313 | 318 | // Use docker ps with filter to find matching containers |
314 | 319 | cmd := r.dockerCmd(ctx, "ps", |
315 | | - "--filter", fmt.Sprintf("name=mcpproxy-%s-", sanitizeForDocker(serverName)), |
| 320 | + "--filter", fmt.Sprintf("name=mcpproxy-%s-", dockernaming.SanitizeServerName(serverName)), |
316 | 321 | "--format", "{{.ID}}", |
317 | 322 | "--no-trunc", |
318 | 323 | ) |
@@ -346,8 +351,12 @@ func (r *SourceResolver) findServerContainer(ctx context.Context, serverName str |
346 | 351 | // hoisted into the same shared cache cannot leak into the scan. |
347 | 352 | func (r *SourceResolver) extractFromContainer(ctx context.Context, containerID string, info ServerInfo) (string, func(), error) { |
348 | 353 | serverName := info.Name |
349 | | - // Create temp directory for extracted source |
350 | | - tempDir, err := os.MkdirTemp("", fmt.Sprintf("mcpproxy-scan-%s-", serverName)) |
| 354 | + // Create temp directory for extracted source. Keep the pattern a constant: |
| 355 | + // os.MkdirTemp's random suffix already guarantees uniqueness, so embedding the |
| 356 | + // (user-controlled) server name added nothing but a go/path-injection taint |
| 357 | + // (MCP-2155) and a slash-rejection bug for official-registry names like |
| 358 | + // "com.pulsemcp/google-flights" (MCP-2123). Dropping it fixes both. |
| 359 | + tempDir, err := os.MkdirTemp("", "mcpproxy-scan-") |
351 | 360 | if err != nil { |
352 | 361 | return "", nil, fmt.Errorf("failed to create temp dir: %w", err) |
353 | 362 | } |
@@ -806,7 +815,10 @@ func (r *SourceResolver) ResolveFullSource(ctx context.Context, info ServerInfo) |
806 | 815 | // false positives (e.g. flagging shutil.py or tempfile.py as "malicious"). |
807 | 816 | func (r *SourceResolver) extractFullFromContainer(ctx context.Context, containerID string, info ServerInfo) (string, func(), error) { |
808 | 817 | serverName := info.Name |
809 | | - tempDir, err := os.MkdirTemp("", fmt.Sprintf("mcpproxy-scan-full-%s-", serverName)) |
| 818 | + // Keep the pattern a constant — os.MkdirTemp's random suffix guarantees |
| 819 | + // uniqueness; embedding the user-controlled server name only added a |
| 820 | + // go/path-injection taint (MCP-2155) and a slash-rejection bug (MCP-2123). |
| 821 | + tempDir, err := os.MkdirTemp("", "mcpproxy-scan-full-") |
810 | 822 | if err != nil { |
811 | 823 | return "", nil, fmt.Errorf("failed to create temp dir: %w", err) |
812 | 824 | } |
@@ -1326,8 +1338,3 @@ func (r *SourceResolver) findGitCheckoutByRepo(checkoutsDir, repoName, gitURL st |
1326 | 1338 | } |
1327 | 1339 | return bestPath, nil |
1328 | 1340 | } |
1329 | | - |
1330 | | -// sanitizeForDocker removes characters invalid in Docker container names |
1331 | | -func sanitizeForDocker(name string) string { |
1332 | | - return strings.NewReplacer("/", "-", ":", "-", ".", "-", " ", "-").Replace(name) |
1333 | | -} |
|
0 commit comments