diff --git a/internal/config/schema/mcp-gateway-config.schema.json b/internal/config/schema/mcp-gateway-config.schema.json index 754024c1..c317d00d 100644 --- a/internal/config/schema/mcp-gateway-config.schema.json +++ b/internal/config/schema/mcp-gateway-config.schema.json @@ -74,9 +74,9 @@ }, "container": { "type": "string", - "description": "Container image for the MCP server (e.g., 'ghcr.io/example/mcp-server:latest'). This field is required for stdio servers per MCP Gateway Specification section 4.1.2.", + "description": "Container image for the MCP server (e.g., 'ghcr.io/example/mcp-server:latest' or 'ghcr.io/example/mcp-server@sha256:'). This field is required for stdio servers per MCP Gateway Specification section 4.1.2.", "minLength": 1, - "pattern": "^[a-zA-Z0-9][a-zA-Z0-9./_-]*(:([a-zA-Z0-9._-]+|latest))?$" + "pattern": "^[a-zA-Z0-9][a-zA-Z0-9./_-]*(:([a-zA-Z0-9._-]+|latest))?(@sha256:[a-fA-F0-9]{64})?$" }, "entrypoint": { "type": "string", diff --git a/internal/config/validation_schema.go b/internal/config/validation_schema.go index b562c66b..002cda3e 100644 --- a/internal/config/validation_schema.go +++ b/internal/config/validation_schema.go @@ -59,7 +59,7 @@ func isTransientHTTPError(statusCode int) bool { var ( // Compile regex patterns from schema for additional validation - containerPattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9./_-]*(:([a-zA-Z0-9._-]+|latest))?$`) + containerPattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9./_-]*(:([a-zA-Z0-9._-]+|latest))?(@sha256:[a-fA-F0-9]{64})?$`) urlPattern = regexp.MustCompile(`^https?://.+`) mountPattern = regexp.MustCompile(`^[^:]+:[^:]+:(ro|rw)$`) domainVarPattern = regexp.MustCompile(`^\$\{[A-Z_][A-Z0-9_]*\}$`) @@ -506,7 +506,7 @@ func validateStringPatterns(stdinCfg *StdinConfig) error { if server.Container != "" && !containerPattern.MatchString(server.Container) { return rules.InvalidPattern("container", server.Container, fmt.Sprintf("%s.container", jsonPath), - "Use a valid container image format (e.g., 'ghcr.io/owner/image:tag' or 'owner/image:latest')") + "Use a valid container image format (e.g., 'ghcr.io/owner/image:tag', 'owner/image:latest', or 'ghcr.io/owner/image:tag@sha256:')") } // Validate mount patterns diff --git a/internal/config/validation_schema_test.go b/internal/config/validation_schema_test.go index 8a380e01..b4c3f573 100644 --- a/internal/config/validation_schema_test.go +++ b/internal/config/validation_schema_test.go @@ -365,6 +365,30 @@ func TestValidateStringPatterns(t *testing.T) { }, shouldErr: false, }, + { + name: "valid container pattern - tag with sha256 digest", + config: &StdinConfig{ + MCPServers: map[string]*StdinServerConfig{ + "test": { + Type: "stdio", + Container: "ghcr.io/owner/image:v1.2.3@sha256:2763823c67a0adca3fce6e3bdfee41a674e3bf22f0e6b2eee94ed3a72ebcd519", + }, + }, + }, + shouldErr: false, + }, + { + name: "valid container pattern - sha256 digest only", + config: &StdinConfig{ + MCPServers: map[string]*StdinServerConfig{ + "test": { + Type: "stdio", + Container: "ghcr.io/owner/image@sha256:2763823c67a0adca3fce6e3bdfee41a674e3bf22f0e6b2eee94ed3a72ebcd519", + }, + }, + }, + shouldErr: false, + }, { name: "invalid container pattern - starts with special char", config: &StdinConfig{ diff --git a/internal/config/validation_string_patterns_test.go b/internal/config/validation_string_patterns_test.go index aa04c61d..19c28563 100644 --- a/internal/config/validation_string_patterns_test.go +++ b/internal/config/validation_string_patterns_test.go @@ -64,6 +64,19 @@ func TestValidateStringPatternsComprehensive(t *testing.T) { serverType: "", shouldError: false, }, + // Valid container patterns with SHA-256 digest + { + name: "valid container with tag and sha256 digest", + container: "ghcr.io/github/github-mcp-server:v0.32.0@sha256:2763823c67a0adca3fce6e3bdfee41a674e3bf22f0e6b2eee94ed3a72ebcd519", + serverType: "stdio", + shouldError: false, + }, + { + name: "valid container with sha256 digest only", + container: "ghcr.io/github/github-mcp-server@sha256:2763823c67a0adca3fce6e3bdfee41a674e3bf22f0e6b2eee94ed3a72ebcd519", + serverType: "stdio", + shouldError: false, + }, // Invalid container patterns { name: "invalid container starts with special char", @@ -86,6 +99,20 @@ func TestValidateStringPatternsComprehensive(t *testing.T) { shouldError: true, errorField: "container", }, + { + name: "invalid container sha256 digest too short", + container: "ghcr.io/github/github-mcp-server@sha256:short", + serverType: "stdio", + shouldError: true, + errorField: "container", + }, + { + name: "invalid container wrong digest algorithm", + container: "ghcr.io/github/github-mcp-server@md5:2763823c67a0adca3fce6e3bdfee41a674e3bf22f0e6b2eee94ed3a72ebcd519", + serverType: "stdio", + shouldError: true, + errorField: "container", + }, { name: "invalid container empty string accepted (empty is valid)", container: "",