Skip to content

Enforce Docker containerization requirement for TOML stdio servers#1029

Merged
lpcox merged 4 commits intomainfrom
copilot/fix-toml-configuration-issue
Feb 17, 2026
Merged

Enforce Docker containerization requirement for TOML stdio servers#1029
lpcox merged 4 commits intomainfrom
copilot/fix-toml-configuration-issue

Conversation

Copy link
Contributor

Copilot AI commented Feb 17, 2026

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

  • Validation: Added validateTOMLStdioContainerization() rejecting stdio servers where command != "docker"
  • Integration: Called from LoadFromFile() after TOML parsing, fails fast with spec reference
  • Error messages: Include specification section and URL for remediation
  • Documentation: Updated README, config.example.toml, and AGENTS.md to reflect Docker-only requirement
  • Tests: 11 test scenarios covering valid/invalid configs, error content, and edge cases
  • Integration tests: Updated to use compliant Docker commands

Example

Before (silently accepted):

[servers.filesystem]
command = "node"
args = ["/path/to/server.js"]

After (rejected with clear error):

server 'filesystem': stdio servers must use containerized execution 
(command must be 'docker', got 'node'). This is required by MCP Gateway 
Specification Section 3.2.1 (Containerization Requirement).

Valid configuration:

[servers.github]
command = "docker"
args = ["run", "--rm", "-i", "ghcr.io/github/github-mcp-server:latest"]

HTTP servers and JSON stdin format (which uses container field) 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
    • Triggering command: /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
    • Triggering command: /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
    • Triggering command: /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
    • Triggering command: /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
    • Triggering command: /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

"Stdio-based MCP servers MUST be containerized. The gateway SHALL NOT support direct command execution without containerization (stdio+command) because:

  1. Containerization provides necessary process isolation and security boundaries
  2. Containers enable reproducible environments across different deployment contexts
  3. Container images provide versioning and dependency management
  4. Containerization ensures portability and consistent behavior

Direct command execution of stdio servers (e.g., command: "node server.js" without a container) is explicitly NOT SUPPORTED by this specification."

Current State

TOML configuration (config.toml) allows arbitrary commands:

[servers.filesystem]
command = "node"
args = ["/path/to/filesystem-server.js"]

From README.md lines 88-102:

"TOML configuration uses command and args fields directly for maximum flexibility"

"Note: In TOML format, you specify the command and args directly. This allows you to use any command (docker, node, python, etc.)."

The code only WARNS but does NOT reject non-containerized commands:

// internal/launcher/launcher.go:127-133
isDirectCommand := serverCfg.Command != "docker"
if l.runningInContainer && isDirectCommand {
    l.logSecurityWarning(serverID, serverCfg)
}

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:

  1. ❌ TOML format allows command = "node", command = "python", etc.
  2. ❌ No validation rejects non-containerized stdio servers
  3. ❌ Documentation explicitly promotes arbitrary command usage
  4. ✅ JSON stdin format IS compliant (requires container field, rejects command field)

Severity: Critical (MUST/SHALL violation)

File References

  • internal/config/config_core.go:92-93 - Command field in ServerConfig struct
  • internal/launcher/launcher.go:127-133 - Warning logic instead of rejection
  • internal/launcher/log_helpers.go - logSecurityWarning function
  • README.md:88-102 - Documents arbitrary command support
  • README.md:146-147 - Notes command field support in TOML
  • config.example.toml:45-47 - Example showing command = "docker"

Impact Analysis

Specification Compliance:

  • Violates Section 3.2.1 (Containerization Requirement) - MUST requirement
  • Non-conforming implementation per Section 2.1 (Conformance Classes)

Security Impact:

  • Breaks isolation guarantees promised by the specification
  • Allows arbitrary process execution without containerization
  • Inconsistent security posture between TOML and JSON configurations

User Impact:

  • Users following TOML documentation will create non-compliant configurations
  • Configurations work but violate spec, creating portability issues
  • Silent compliance violations (no clear error message)

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 servers

Implementation Approach:

  1. Add validation function in internal/config/validation.go:

    func validateStdioCommandIsDocker(servers map[string]*ServerConfig) error {
        for name, cfg := range servers {
            if cfg.Type == "stdio" && cfg.Command != "docker" {
                return fmt.Errorf("server '%s': stdio servers must use containerized execution (command must be 'docker', got '%s'). See specification section 3.2.1", name, cfg.Command)
            }
        }
        return nil
    }
  2. Call validation in LoadFromFile() after TOML parsing

  3. Return error with clear specification reference

Files to Modify:

  • internal/config/validation.go - Add validation function
  • internal/config/config_core.go - Call validation in LoadFromFile()

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.

Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI and others added 2 commits February 17, 2026 13:39
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix TOML configuration to comply with containerization requirement Enforce Docker containerization requirement for TOML stdio servers Feb 17, 2026
Copilot AI requested a review from lpcox February 17, 2026 13:53
@lpcox lpcox marked this pull request as ready for review February 17, 2026 13:58
Copilot AI review requested due to automatic review settings February 17, 2026 13:58
@lpcox lpcox merged commit 9c1f3ab into main Feb 17, 2026
8 checks passed
@lpcox lpcox deleted the copilot/fix-toml-configuration-issue branch February 17, 2026 13:58
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 command is 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 direct echo.

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.

Comment on lines +271 to +285
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)
}
}
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines 31 to 35
configFile := createTempConfig(t, map[string]interface{}{
"testserver": map[string]interface{}{
"command": "echo",
"args": []string{},
"command": "docker",
"args": []string{"run", "--rm", "-i", "alpine:latest", "echo"},
},
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines 36 to 40
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"},
},
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +716 to +755
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,
},
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[compliance] TOML Configuration Violates Containerization Requirement

3 participants