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
57 changes: 33 additions & 24 deletions internal/config/env_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,59 +207,68 @@ func validateContainerID(containerID string) error {
return nil
}

// checkPortMapping uses docker inspect to verify that the specified port is mapped
func checkPortMapping(containerID, port string) (bool, error) {
// runDockerInspect is a helper function that executes docker inspect with a given format template.
// It validates the container ID before running the command and returns the output as a string.
//
// Security Note: This is an internal helper function that should only be called with
// hardcoded format templates defined within this package. The formatTemplate parameter
// is not validated as it is never exposed to user input.
//
// Parameters:
// - containerID: The Docker container ID to inspect (validated before use)
// - formatTemplate: The Go template format string for docker inspect (e.g., "{{.Config.OpenStdin}}")
//
// Returns:
// - output: The trimmed output from docker inspect
// - error: Any validation or command execution error
func runDockerInspect(containerID, formatTemplate string) (string, error) {
if err := validateContainerID(containerID); err != nil {
return false, err
return "", err
}

// Use docker inspect to get port bindings
cmd := exec.Command("docker", "inspect", "--format", "{{json .NetworkSettings.Ports}}", containerID)
cmd := exec.Command("docker", "inspect", "--format", formatTemplate, containerID)
output, err := cmd.Output()
if err != nil {
return false, fmt.Errorf("docker inspect failed: %w", err)
return "", fmt.Errorf("docker inspect failed: %w", err)
}

return strings.TrimSpace(string(output)), nil
}

// checkPortMapping uses docker inspect to verify that the specified port is mapped
func checkPortMapping(containerID, port string) (bool, error) {
output, err := runDockerInspect(containerID, "{{json .NetworkSettings.Ports}}")
if err != nil {
return false, err
}

// Parse the port from the output
portKey := fmt.Sprintf("%s/tcp", port)
outputStr := string(output)

// Check if the port is in the output with a host binding
// The format is like: {"8000/tcp":[{"HostIp":"0.0.0.0","HostPort":"8000"}]}
return strings.Contains(outputStr, portKey) && strings.Contains(outputStr, "HostPort"), nil
return strings.Contains(output, portKey) && strings.Contains(output, "HostPort"), nil
}

// checkStdinInteractive uses docker inspect to verify the container was started with -i flag
func checkStdinInteractive(containerID string) bool {
if err := validateContainerID(containerID); err != nil {
return false
}

// Use docker inspect to check stdin_open
cmd := exec.Command("docker", "inspect", "--format", "{{.Config.OpenStdin}}", containerID)
output, err := cmd.Output()
output, err := runDockerInspect(containerID, "{{.Config.OpenStdin}}")
if err != nil {
return false
}

return strings.TrimSpace(string(output)) == "true"
return output == "true"
}

// checkLogDirMounted uses docker inspect to verify the log directory is mounted
func checkLogDirMounted(containerID, logDir string) bool {
if err := validateContainerID(containerID); err != nil {
return false
}

// Use docker inspect to get mounts
cmd := exec.Command("docker", "inspect", "--format", "{{json .Mounts}}", containerID)
output, err := cmd.Output()
output, err := runDockerInspect(containerID, "{{json .Mounts}}")
if err != nil {
return false
}

// Check if the log directory is in the mounts
return strings.Contains(string(output), logDir)
return strings.Contains(output, logDir)
}

// GetGatewayPortFromEnv returns the MCP_GATEWAY_PORT value, parsed as int
Expand Down
47 changes: 47 additions & 0 deletions internal/config/env_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,3 +492,50 @@ func TestValidateExecutionEnvironment(t *testing.T) {
}
})
}

func TestRunDockerInspect(t *testing.T) {
tests := []struct {
name string
containerID string
formatTemplate string
shouldError bool
}{
{
name: "empty container ID",
containerID: "",
formatTemplate: "{{.Config.OpenStdin}}",
shouldError: true,
},
{
name: "invalid container ID - too short",
containerID: "abc123",
formatTemplate: "{{.Config.OpenStdin}}",
shouldError: true,
},
{
name: "invalid container ID - special chars",
containerID: "abc;def123456",
formatTemplate: "{{.Config.OpenStdin}}",
shouldError: true,
},
{
name: "valid container ID format - command will fail without docker",
containerID: "abcdef123456",
formatTemplate: "{{.Config.OpenStdin}}",
shouldError: true, // Will fail because container doesn't exist
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := runDockerInspect(tt.containerID, tt.formatTemplate)

if tt.shouldError {
assert.Error(t, err, "Expected error but got none")
assert.Empty(t, output, "Expected empty output on error")
} else {
assert.NoError(t, err, "Unexpected error")
}
})
}
}