Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions internal/config/config_stdin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestConvertStdinServerConfig_StdioServer(t *testing.T) {
Entrypoint: "/bin/custom",
EntrypointArgs: []string{"arg1", "arg2"},
Args: []string{"--network", "host"},
Mounts: []string{"/tmp:/tmp"},
Mounts: []string{"/tmp:/tmp:rw"},
Env: map[string]string{
"TEST_VAR": "value123",
"PASSTHROUGH": "",
Expand Down Expand Up @@ -46,7 +46,7 @@ func TestConvertStdinServerConfig_StdioServer(t *testing.T) {

// Check mounts
assert.Contains(t, result.Args, "-v")
assert.Contains(t, result.Args, "/tmp:/tmp")
assert.Contains(t, result.Args, "/tmp:/tmp:rw")

// Check user environment variables
assert.Contains(t, result.Args, "TEST_VAR=value123")
Expand Down Expand Up @@ -380,7 +380,7 @@ func TestConvertStdinServerConfig_ArgsOrdering(t *testing.T) {
Entrypoint: "/custom/entry",
EntrypointArgs: []string{"entry-arg1"},
Args: []string{"--custom-flag", "value"},
Mounts: []string{"/host:/container"},
Mounts: []string{"/host:/container:ro"},
Env: map[string]string{
"MY_VAR": "my-value",
},
Expand Down
21 changes: 9 additions & 12 deletions internal/config/rules/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,31 +120,28 @@ func TimeoutPositive(timeout int, fieldName, jsonPath string) *ValidationError {
return nil
}

// MountFormat validates a mount specification in the format "source:dest" or "source:dest:mode"
// MountFormat validates a mount specification in the format "source:dest:mode"
// Returns nil if valid, *ValidationError if invalid
// Per MCP Gateway specification v1.7.0 section 4.1.5:
// Per MCP Gateway specification v1.8.0 section 4.1.5:
// - Host path MUST be an absolute path
// - Container path MUST be an absolute path
// - Mode (if provided) MUST be either "ro" (read-only) or "rw" (read-write)
// - Mode MUST be either "ro" (read-only) or "rw" (read-write)
func MountFormat(mount, jsonPath string, index int) *ValidationError {
log.Printf("Validating mount format: mount=%s, jsonPath=%s, index=%d", mount, jsonPath, index)
parts := strings.Split(mount, ":")
if len(parts) < 2 || len(parts) > 3 {
if len(parts) != 3 {
log.Printf("Mount format validation failed: invalid part count=%d", len(parts))
return &ValidationError{
Field: "mounts",
Message: fmt.Sprintf("invalid mount format '%s' (expected 'source:dest' or 'source:dest:mode')", mount),
Message: fmt.Sprintf("invalid mount format '%s' (expected 'source:dest:mode')", mount),
JSONPath: fmt.Sprintf("%s.mounts[%d]", jsonPath, index),
Suggestion: "Use format 'source:dest' or 'source:dest:mode' where mode is 'ro' (read-only) or 'rw' (read-write)",
Suggestion: "Use format 'source:dest:mode' where mode is 'ro' (read-only) or 'rw' (read-write), e.g. '/host/path:/container/path:ro'",
}
}

source := parts[0]
dest := parts[1]
mode := ""
if len(parts) == 3 {
mode = parts[2]
}
mode := parts[2]

// Validate source is not empty
if source == "" {
Expand Down Expand Up @@ -186,8 +183,8 @@ func MountFormat(mount, jsonPath string, index int) *ValidationError {
}
}

// Validate mode if provided
if mode != "" && mode != "ro" && mode != "rw" {
// Validate mode
if mode != "ro" && mode != "rw" {
return &ValidationError{
Field: "mounts",
Message: fmt.Sprintf("invalid mount mode '%s' (must be 'ro' or 'rw')", mode),
Expand Down
5 changes: 3 additions & 2 deletions internal/config/rules/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,12 @@ func TestMountFormat(t *testing.T) {
shouldErr: false,
},
{
name: "valid mount without mode",
name: "invalid mount without mode",
mount: "/host/path:/container/path",
jsonPath: "mcpServers.github",
index: 0,
shouldErr: false,
shouldErr: true,
errMsg: "invalid mount format",
},
{
name: "invalid format - too many colons",
Expand Down
2 changes: 1 addition & 1 deletion internal/config/validation_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ 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))?$`)
urlPattern = regexp.MustCompile(`^https?://.+`)
mountPattern = regexp.MustCompile(`^[^:]+:[^:]+(:(ro|rw))?$`)
mountPattern = regexp.MustCompile(`^[^:]+:[^:]+:(ro|rw)$`)

Copilot AI Feb 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mountPattern (used by validateStringPatterns) rejects Windows drive-letter host paths because it forbids additional ':' characters in the source segment. If Windows host paths are intended to be supported (other validators in this package accept them), the regex should be updated accordingly (or this pre-check should be removed in favor of MountFormat doing the authoritative parsing/validation).

Suggested change
mountPattern = regexp.MustCompile(`^[^:]+:[^:]+:(ro|rw)$`)
// mountPattern validates "source:target:mode" structure but allows colons within source/target
mountPattern = regexp.MustCompile(`^.+:.+:(ro|rw)$`)

Copilot uses AI. Check for mistakes.
domainVarPattern = regexp.MustCompile(`^\$\{[A-Z_][A-Z0-9_]*\}$`)

// logSchema is the debug logger for schema validation
Expand Down
4 changes: 2 additions & 2 deletions internal/config/validation_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ func TestValidateStringPatterns(t *testing.T) {
shouldErr: false,
},
{
name: "valid mount without mode",
name: "invalid mount without mode",
config: &StdinConfig{
MCPServers: map[string]*StdinServerConfig{
"test": {
Expand All @@ -402,7 +402,7 @@ func TestValidateStringPatterns(t *testing.T) {
},
},
},
shouldErr: false,
shouldErr: true,
},
{
name: "valid http url pattern",
Expand Down
4 changes: 2 additions & 2 deletions internal/config/validation_string_patterns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ func TestValidateStringPatternsComprehensive(t *testing.T) {
shouldError: false,
},
{
name: "valid mount without mode",
name: "invalid mount without mode",
mounts: []string{"/host/path:/container/path"},
shouldError: false,
shouldError: true,
},
{
name: "valid multiple mounts",
Expand Down
4 changes: 2 additions & 2 deletions internal/config/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,13 @@ func TestValidateStdioServer(t *testing.T) {
shouldErr: false,
},
{
name: "valid mount without mode",
name: "invalid mount without mode",
server: &StdinServerConfig{
Type: "stdio",
Container: "test:latest",
Mounts: []string{"/host:/container"},
},
shouldErr: false,
shouldErr: true,
},
{
name: "invalid mount format - too many parts",
Expand Down
Loading