Enforce Docker containerization requirement for TOML stdio servers#1029
Enforce Docker containerization requirement for TOML stdio servers#1029
Conversation
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds spec-compliance enforcement so TOML-based stdio server configs cannot run arbitrary host commands and must be containerized via Docker, aligning TOML behavior with the MCP Gateway spec.
Changes:
- Add TOML validation rejecting stdio/local servers whose
commandis not"docker"and call it from TOML file loading. - Update docs (README, example config, AGENTS) to describe the Docker-only requirement and provide an HTTP example.
- Update integration tests’ generated TOML configs to use
docker run ...instead of directecho.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
internal/config/validation.go |
Implements validateTOMLStdioContainerization() to enforce Docker-only stdio execution. |
internal/config/config_core.go |
Invokes the new validation during TOML LoadFromFile(). |
internal/config/validation_test.go |
Adds unit tests covering valid/invalid TOML stdio containerization cases. |
test/integration/binary_test.go |
Updates generated TOML configs in integration tests to use docker run instead of direct commands. |
test/integration/large_payload_test.go |
Same as above for large-payload integration tests. |
README.md |
Updates TOML documentation to state Docker-only stdio requirement and links spec section. |
config.example.toml |
Documents Docker-only requirement for stdio servers in TOML examples. |
AGENTS.md |
Documents the containerization requirement and clarifies TOML vs JSON behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for name, cfg := range servers { | ||
| // Only validate stdio servers (or empty type which defaults to stdio) | ||
| if cfg.Type == "" || cfg.Type == "stdio" || cfg.Type == "local" { | ||
| logValidation.Printf("Checking stdio server: name=%s, command=%s", name, cfg.Command) | ||
|
|
||
| // Check if command is Docker | ||
| if cfg.Command != "docker" { | ||
| logValidation.Printf("Validation failed: stdio server using non-Docker command, name=%s, command=%s", name, cfg.Command) | ||
| return fmt.Errorf( | ||
| "server '%s': stdio servers must use containerized execution (command must be 'docker', got '%s'). "+ | ||
| "This is required by MCP Gateway Specification Section 3.2.1 (Containerization Requirement). "+ | ||
| "See: https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/mcp-gateway.md#321-containerization-requirement", | ||
| name, cfg.Command) | ||
| } | ||
| } |
There was a problem hiding this comment.
validateTOMLStdioContainerization only enforces the Docker requirement when cfg.Type is "", "stdio", or "local". However, the launcher treats any non-"http" type as a stdio server, so a TOML config can bypass this check by setting type = "HTTP" (case mismatch) or any unknown value and using a non-docker command. Consider normalizing Type (e.g., strings.ToLower/TrimSpace) and enforcing Docker for all non-HTTP servers (or explicitly rejecting unsupported type values in TOML).
| configFile := createTempConfig(t, map[string]interface{}{ | ||
| "testserver": map[string]interface{}{ | ||
| "command": "echo", | ||
| "args": []string{}, | ||
| "command": "docker", | ||
| "args": []string{"run", "--rm", "-i", "alpine:latest", "echo"}, | ||
| }, |
There was a problem hiding this comment.
These integration tests now depend on Docker and on pulling/running alpine:latest, but the test doesn’t check Docker availability (other integration tests skip when docker version fails). Add a Docker availability check (and ideally use a pinned image tag/digest instead of alpine:latest to reduce flakiness).
| configFile := createTempConfigWithPayloadDir(t, payloadDir, map[string]interface{}{ | ||
| "echo": map[string]interface{}{ | ||
| "command": "echo", | ||
| "args": []string{}, | ||
| "command": "docker", | ||
| "args": []string{"run", "--rm", "-i", "alpine:latest", "echo"}, | ||
| }, |
There was a problem hiding this comment.
This test now launches the backend via docker run ... alpine:latest ... but does not skip when Docker is unavailable. Add a Docker availability check (and consider pinning the Alpine image tag/digest instead of latest to avoid nondeterministic failures).
| func TestValidateTOMLStdioContainerization(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| servers map[string]*ServerConfig | ||
| shouldErr bool | ||
| errorMsg string | ||
| }{ | ||
| { | ||
| name: "valid Docker command for stdio server", | ||
| servers: map[string]*ServerConfig{ | ||
| "github": { | ||
| Type: "stdio", | ||
| Command: "docker", | ||
| Args: []string{"run", "--rm", "-i", "ghcr.io/github/github-mcp-server:latest"}, | ||
| }, | ||
| }, | ||
| shouldErr: false, | ||
| }, | ||
| { | ||
| name: "valid Docker command with empty type (defaults to stdio)", | ||
| servers: map[string]*ServerConfig{ | ||
| "github": { | ||
| Type: "", | ||
| Command: "docker", | ||
| Args: []string{"run", "--rm", "-i", "ghcr.io/github/github-mcp-server:latest"}, | ||
| }, | ||
| }, | ||
| shouldErr: false, | ||
| }, | ||
| { | ||
| name: "valid Docker command with local type (alias for stdio)", | ||
| servers: map[string]*ServerConfig{ | ||
| "github": { | ||
| Type: "local", | ||
| Command: "docker", | ||
| Args: []string{"run", "--rm", "-i", "ghcr.io/github/github-mcp-server:latest"}, | ||
| }, | ||
| }, | ||
| shouldErr: false, | ||
| }, |
There was a problem hiding this comment.
The new test suite doesn’t cover bypass cases like Type: "HTTP" (case mismatch) or an unknown Type value. Given the launcher treats any non-"http" type as stdio, add test coverage ensuring TOML validation still enforces Docker (or rejects unsupported types) for these inputs.
TOML configurations accepted arbitrary commands (
node,python,npx) for stdio servers, violating MCP Gateway Specification Section 3.2.1 which mandates containerization. This was a silent compliance failure with no validation.Changes
validateTOMLStdioContainerization()rejecting stdio servers wherecommand != "docker"LoadFromFile()after TOML parsing, fails fast with spec referenceExample
Before (silently accepted):
After (rejected with clear error):
Valid configuration:
HTTP servers and JSON stdin format (which uses
containerfield) are unaffected.Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
example.com/tmp/go-build2949333/b275/launcher.test /tmp/go-build2949333/b275/launcher.test -test.testlogfile=/tmp/go-build2949333/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true ache/go/1.25.7/x64/src/runtime/c-p t.go x_amd64/compile(dns block)invalid-host-that-does-not-exist-12345.com/tmp/go-build2949333/b260/config.test /tmp/go-build2949333/b260/config.test -test.testlogfile=/tmp/go-build2949333/b260/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true p/go-build user.email x_amd64/vet abis 64/src/runtime/cstart(dns block)nonexistent.local/tmp/go-build2949333/b275/launcher.test /tmp/go-build2949333/b275/launcher.test -test.testlogfile=/tmp/go-build2949333/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true ache/go/1.25.7/x64/src/runtime/c-p t.go x_amd64/compile(dns block)slow.example.com/tmp/go-build2949333/b275/launcher.test /tmp/go-build2949333/b275/launcher.test -test.testlogfile=/tmp/go-build2949333/b275/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true ache/go/1.25.7/x64/src/runtime/c-p t.go x_amd64/compile(dns block)this-host-does-not-exist-12345.com/tmp/go-build2949333/b284/mcp.test /tmp/go-build2949333/b284/mcp.test -test.testlogfile=/tmp/go-build2949333/b284/testlog.txt -test.paniconexit0 -test.timeout=10m0s -test.v=true 64/src/net ache/go/1.25.7/x64/src/crypto/des/block.go x_amd64/vet -p internal/reflect-o -lang=go1.25 x_amd64/vet ap 64/src/net ap 64/pkg/tool/linu-lang=go1.25 -p ache/go/1.25.7/x--version -lang=go1.25 64/pkg/tool/linu-dwarf=false(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
This section details on the original issue you should resolve
<issue_title>[compliance] TOML Configuration Violates Containerization Requirement</issue_title>
<issue_description># MCP Gateway Compliance Review - February 17, 2026
Summary
Found 1 critical compliance issue during daily review of commit
bb96a20419067e109cc24e21b725f78f2eca663e.The gateway's TOML configuration format allows arbitrary command execution (e.g.,
command = "node",command = "python"), which directly violates the MCP Gateway Specification requirement that all stdio-based MCP servers MUST be containerized.Critical Issue: TOML Configuration Allows Non-Containerized Commands
Specification Section
3.2.1 Containerization Requirement
Deep Link: https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/mcp-gateway.md#321-containerization-requirement
Specification Requirement
Current State
TOML configuration (
config.toml) allows arbitrary commands:From
README.mdlines 88-102:The code only WARNS but does NOT reject non-containerized commands:
This warning only triggers when the gateway itself is running inside a container AND a non-Docker command is used. It does NOT enforce the specification requirement.
Gap
Compliance Violation:
command = "node",command = "python", etc.containerfield, rejectscommandfield)Severity: Critical (MUST/SHALL violation)
File References
internal/config/config_core.go:92-93-Commandfield inServerConfigstructinternal/launcher/launcher.go:127-133- Warning logic instead of rejectioninternal/launcher/log_helpers.go-logSecurityWarningfunctionREADME.md:88-102- Documents arbitrary command supportREADME.md:146-147- Notes command field support in TOMLconfig.example.toml:45-47- Example showingcommand = "docker"Impact Analysis
Specification Compliance:
Security Impact:
User Impact:
Suggested Remediation Tasks
Task 1: Add TOML Validation to Reject Non-Docker Commands
Description: Extend validation to reject TOML configurations where
command != "docker"for stdio serversImplementation Approach:
Add validation function in
internal/config/validation.go:Call validation in
LoadFromFile()after TOML parsingReturn error with clear specification reference
Files to Modify:
internal/config/validation.go- Add validation functioninternal/config/config_core.go- Call validation inLoadFromFile()Specification Reference: https://github.co...
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.