diff --git a/docs/public/schemas/mcp-gateway-config.schema.json b/docs/public/schemas/mcp-gateway-config.schema.json index e3606d9f9d2..0c7119e6877 100644 --- a/docs/public/schemas/mcp-gateway-config.schema.json +++ b/docs/public/schemas/mcp-gateway-config.schema.json @@ -246,6 +246,15 @@ "type": "string", "description": "Optional path prefix for payload file paths as seen from within agent containers. Use this when the payload directory is mounted at a different path inside the container than on the host.", "minLength": 1 + }, + "trustedBots": { + "type": "array", + "description": "Additional trusted bot identity strings passed to the gateway and merged with its built-in internal trusted identity list. This field is additive and cannot remove entries from the gateway's built-in list. Typically GitHub bot usernames such as 'github-actions[bot]' or 'copilot-swe-agent[bot]'.", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, "required": ["port", "domain", "apiKey"], diff --git a/docs/src/content/docs/reference/mcp-gateway.md b/docs/src/content/docs/reference/mcp-gateway.md index 03fcb1e4956..af0e8d361f0 100644 --- a/docs/src/content/docs/reference/mcp-gateway.md +++ b/docs/src/content/docs/reference/mcp-gateway.md @@ -7,7 +7,7 @@ sidebar: # MCP Gateway Specification -**Version**: 1.8.0 +**Version**: 1.9.0 **Status**: Draft Specification **Latest Version**: [mcp-gateway](/gh-aw/reference/mcp-gateway/) **JSON Schema**: [mcp-gateway-config.schema.json](/gh-aw/schemas/mcp-gateway-config.schema.json) @@ -248,6 +248,7 @@ The `gateway` section is required and configures gateway-specific behavior: | `payloadDir` | string | No | Directory path for storing large payload JSON files for authenticated clients | | `payloadPathPrefix` | string | No | Path prefix to remap payload paths for agent containers (e.g., /workspace/payloads) | | `payloadSizeThreshold` | integer | No | Size threshold in bytes for storing payloads to disk (default: 524288 = 512KB) | +| `trustedBots` | array[string] | No | Additional GitHub bot identity strings (e.g., `github-actions[bot]`) passed to the gateway and merged with its built-in trusted identity list. This field is additive — it extends the internal list but cannot remove built-in entries. | #### 4.1.3.1 Payload Directory Path Validation @@ -367,6 +368,46 @@ payload_size_threshold = 262144 # 256KB - more aggressive disk storage - Gateway MUST compare actual payload size against threshold before deciding storage method - Threshold MAY be adjusted based on deployment needs (memory vs disk I/O trade-offs) +#### 4.1.3.4 Trusted Bot Identity Configuration + +The optional `trustedBots` field in the gateway configuration passes an additional list of GitHub bot identity strings to the gateway. The gateway merges this list with its own built-in trusted identity list to form the effective set of identities it recognises. + +> **Important**: `trustedBots` is **additive**. The gateway maintains its own internal list of trusted bot identities. The `trustedBots` field extends that internal list with additional identities; it cannot remove or override the gateway's built-in trusted identities. + +**Configuration Example**: + +```json +{ + "gateway": { + "port": 8080, + "domain": "localhost", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "trustedBots": [ + "github-actions[bot]", + "copilot-swe-agent[bot]" + ] + } +} +``` + +**Frontmatter Example** (workflow author): + +```yaml +sandbox: + mcp: + trusted-bots: + - github-actions[bot] + - copilot-swe-agent[bot] +``` + +**Requirements**: +- `trustedBots` MUST be a non-empty array of strings when present +- Each entry MUST be a non-empty string (typically a GitHub username such as `github-actions[bot]`) +- `trustedBots` entries are **merged** with the gateway's internal built-in trusted identities to form the effective list passed to the gateway +- `trustedBots` MUST NOT be used to remove or override entries in the gateway's internal built-in trusted identity list + +**Compliance Test**: T-AUTH-006 - Trusted Bot Identity Configuration + #### 4.1.3a Top-Level Configuration Fields The following fields MAY be specified at the top level of the configuration: @@ -943,6 +984,14 @@ The following endpoints MUST NOT require authentication: - `/health` +### 7.5 Trusted Bot Identity Configuration + +The `gateway.trustedBots` field allows workflow authors to pass additional trusted bot identity strings to the gateway via the generated gateway configuration file. The gateway merges these entries with its own built-in trusted identity list. + +`gateway.trustedBots` is **additive** — it extends the gateway's built-in list but cannot remove entries from it. + +Workflow authors set this via the `sandbox.mcp.trusted-bots` frontmatter field; the compiler translates it into the `trustedBots` array in the generated `gateway` section of the MCP config file. + --- ## 8. Health Monitoring @@ -1130,6 +1179,7 @@ A conforming implementation MUST pass the following test categories: - **T-AUTH-003**: Missing token rejection - **T-AUTH-004**: Health endpoint exemption - **T-AUTH-005**: Token rotation support +- **T-AUTH-006**: Trusted bot identity configuration — `trustedBots` entries are present in the generated gateway config and merged with the gateway's built-in list #### 10.1.5 Timeout Tests @@ -1469,6 +1519,18 @@ Content-Type: application/json ## Change Log +### Version 1.9.0 (Draft) + +- **Added**: `trustedBots` field to gateway configuration (Section 4.1.3, 4.1.3.4) + - Optional array of GitHub bot identity strings passed to the gateway via the generated config + - Merged with the gateway's built-in trusted identity list (additive — cannot remove built-in entries) + - Workflow authors configure via `sandbox.mcp.trusted-bots` in frontmatter; the compiler translates it into the gateway config +- **Added**: Section 7.5 — Trusted Bot Identity Configuration + - Describes `trustedBots` as a config-passing mechanism from frontmatter to gateway config +- **Added**: Compliance test for trusted bot identity configuration (Section 10.1.4) + - T-AUTH-006: Trusted bot identity configuration +- **Updated**: JSON Schema with `trustedBots` property in `gatewayConfig` definition + ### Version 1.8.0 (Draft) - **Added**: `payloadDir` field to gateway configuration (Section 4.1.3) diff --git a/pkg/workflow/frontmatter_extraction_security.go b/pkg/workflow/frontmatter_extraction_security.go index 183c8d52595..c9427844995 100644 --- a/pkg/workflow/frontmatter_extraction_security.go +++ b/pkg/workflow/frontmatter_extraction_security.go @@ -479,6 +479,22 @@ func (c *Compiler) extractMCPGatewayConfig(mcpVal any) *MCPGatewayRuntimeConfig } } + // Extract trustedBots / trusted-bots (additional bot identities to pass to the gateway) + for _, key := range []string{"trustedBots", "trusted-bots"} { + if trustedBotsVal, hasTrustedBots := mcpObj[key]; hasTrustedBots { + if trustedBotsSlice, ok := trustedBotsVal.([]any); ok { + for _, bot := range trustedBotsSlice { + if botStr, ok := bot.(string); ok { + mcpConfig.TrustedBots = append(mcpConfig.TrustedBots, botStr) + } + } + } + if len(mcpConfig.TrustedBots) > 0 { + break + } + } + } + return mcpConfig } diff --git a/pkg/workflow/frontmatter_extraction_security_test.go b/pkg/workflow/frontmatter_extraction_security_test.go index 761afbfdf01..8345b259a7e 100644 --- a/pkg/workflow/frontmatter_extraction_security_test.go +++ b/pkg/workflow/frontmatter_extraction_security_test.go @@ -218,3 +218,37 @@ func TestExtractMCPGatewayConfigPayloadFields(t *testing.T) { assert.Equal(t, 0, config.PayloadSizeThreshold, "PayloadSizeThreshold should be 0 when not specified") }) } + +// TestExtractMCPGatewayConfigTrustedBots tests extraction of trustedBots from MCP gateway frontmatter +func TestExtractMCPGatewayConfigTrustedBots(t *testing.T) { + compiler := &Compiler{} + + t.Run("extracts trustedBots using camelCase key", func(t *testing.T) { + mcpObj := map[string]any{ + "container": "ghcr.io/github/gh-aw-mcpg", + "trustedBots": []any{"github-actions[bot]", "copilot-swe-agent[bot]"}, + } + config := compiler.extractMCPGatewayConfig(mcpObj) + require.NotNil(t, config, "Should extract MCP gateway config") + assert.Equal(t, []string{"github-actions[bot]", "copilot-swe-agent[bot]"}, config.TrustedBots, "Should extract trustedBots") + }) + + t.Run("extracts trustedBots using kebab-case key", func(t *testing.T) { + mcpObj := map[string]any{ + "container": "ghcr.io/github/gh-aw-mcpg", + "trusted-bots": []any{"github-actions[bot]"}, + } + config := compiler.extractMCPGatewayConfig(mcpObj) + require.NotNil(t, config, "Should extract MCP gateway config") + assert.Equal(t, []string{"github-actions[bot]"}, config.TrustedBots, "Should extract trusted-bots") + }) + + t.Run("leaves trustedBots nil when not specified", func(t *testing.T) { + mcpObj := map[string]any{ + "container": "ghcr.io/github/gh-aw-mcpg", + } + config := compiler.extractMCPGatewayConfig(mcpObj) + require.NotNil(t, config, "Should extract MCP gateway config") + assert.Nil(t, config.TrustedBots, "TrustedBots should be nil when not specified") + }) +} diff --git a/pkg/workflow/mcp_gateway_config.go b/pkg/workflow/mcp_gateway_config.go index 459653c89a8..42aa59a0127 100644 --- a/pkg/workflow/mcp_gateway_config.go +++ b/pkg/workflow/mcp_gateway_config.go @@ -137,6 +137,7 @@ func buildMCPGatewayConfig(workflowData *WorkflowData) *MCPGatewayRuntimeConfig PayloadDir: "${MCP_GATEWAY_PAYLOAD_DIR}", // Gateway variable expression for payload directory PayloadPathPrefix: workflowData.SandboxConfig.MCP.PayloadPathPrefix, // Optional path prefix for agent containers PayloadSizeThreshold: payloadSizeThreshold, // Size threshold in bytes + TrustedBots: workflowData.SandboxConfig.MCP.TrustedBots, // Additional trusted bot identities from frontmatter } } diff --git a/pkg/workflow/mcp_gateway_config_test.go b/pkg/workflow/mcp_gateway_config_test.go index 55dab161c2c..9eef167e15c 100644 --- a/pkg/workflow/mcp_gateway_config_test.go +++ b/pkg/workflow/mcp_gateway_config_test.go @@ -315,6 +315,24 @@ func TestBuildMCPGatewayConfig(t *testing.T) { PayloadSizeThreshold: constants.DefaultMCPGatewayPayloadSizeThreshold, }, }, + { + name: "propagates trustedBots from frontmatter config", + workflowData: &WorkflowData{ + SandboxConfig: &SandboxConfig{ + MCP: &MCPGatewayRuntimeConfig{ + TrustedBots: []string{"github-actions[bot]", "copilot-swe-agent[bot]"}, + }, + }, + }, + expected: &MCPGatewayRuntimeConfig{ + Port: int(DefaultMCPGatewayPort), + Domain: "${MCP_GATEWAY_DOMAIN}", + APIKey: "${MCP_GATEWAY_API_KEY}", + PayloadDir: "${MCP_GATEWAY_PAYLOAD_DIR}", + PayloadSizeThreshold: constants.DefaultMCPGatewayPayloadSizeThreshold, + TrustedBots: []string{"github-actions[bot]", "copilot-swe-agent[bot]"}, + }, + }, } for _, tt := range tests { @@ -330,6 +348,7 @@ func TestBuildMCPGatewayConfig(t *testing.T) { assert.Equal(t, tt.expected.PayloadDir, result.PayloadDir, "PayloadDir should match") assert.Equal(t, tt.expected.PayloadPathPrefix, result.PayloadPathPrefix, "PayloadPathPrefix should match") assert.Equal(t, tt.expected.PayloadSizeThreshold, result.PayloadSizeThreshold, "PayloadSizeThreshold should match") + assert.Equal(t, tt.expected.TrustedBots, result.TrustedBots, "TrustedBots should match") } }) } diff --git a/pkg/workflow/mcp_renderer.go b/pkg/workflow/mcp_renderer.go index 6f6968d35f5..13152cc0b65 100644 --- a/pkg/workflow/mcp_renderer.go +++ b/pkg/workflow/mcp_renderer.go @@ -193,6 +193,16 @@ func RenderJSONMCPConfig( if options.GatewayConfig.PayloadDir != "" { fmt.Fprintf(&configBuilder, ",\n \"payloadDir\": \"%s\"", options.GatewayConfig.PayloadDir) } + if len(options.GatewayConfig.TrustedBots) > 0 { + configBuilder.WriteString(",\n \"trustedBots\": [") + for i, bot := range options.GatewayConfig.TrustedBots { + if i > 0 { + configBuilder.WriteString(", ") + } + fmt.Fprintf(&configBuilder, "%q", bot) + } + configBuilder.WriteString("]") + } configBuilder.WriteString("\n") configBuilder.WriteString(" }\n") } else { diff --git a/pkg/workflow/schemas/mcp-gateway-config.schema.json b/pkg/workflow/schemas/mcp-gateway-config.schema.json index 7c31da0a930..99749c51bba 100644 --- a/pkg/workflow/schemas/mcp-gateway-config.schema.json +++ b/pkg/workflow/schemas/mcp-gateway-config.schema.json @@ -215,6 +215,15 @@ "type": "string", "description": "Optional path prefix for payload file paths as seen from within agent containers. Use this when the payload directory is mounted at a different path inside the container than on the host.", "minLength": 1 + }, + "trustedBots": { + "type": "array", + "description": "Additional trusted bot identity strings passed to the gateway and merged with its built-in internal trusted identity list. This field is additive and cannot remove entries from the gateway's built-in list. Typically GitHub bot usernames such as 'github-actions[bot]' or 'copilot-swe-agent[bot]'.", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, "required": ["port", "domain", "apiKey"], diff --git a/pkg/workflow/tools_types.go b/pkg/workflow/tools_types.go index 22f666171b3..c8943d4f826 100644 --- a/pkg/workflow/tools_types.go +++ b/pkg/workflow/tools_types.go @@ -396,6 +396,7 @@ type MCPGatewayRuntimeConfig struct { PayloadDir string `yaml:"payload-dir,omitempty"` // Directory path for storing large payload JSON files (must be absolute path) PayloadPathPrefix string `yaml:"payload-path-prefix,omitempty"` // Path prefix to remap payload paths for agent containers (e.g., /workspace/payloads) PayloadSizeThreshold int `yaml:"payload-size-threshold,omitempty"` // Size threshold in bytes for storing payloads to disk (default: 524288 = 512KB) + TrustedBots []string `yaml:"trusted-bots,omitempty"` // Additional bot identity strings to pass to the gateway, merged with its built-in list } // HasTool checks if a tool is present in the configuration