From 1f4b1a4cb8f460f47bc3ca1e2d6a84556d370140 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:53:46 +0000 Subject: [PATCH 1/5] Add trustedBots field to MCP Gateway spec and schema Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/ddc0ea39-4d99-4212-bfe5-a571981cc2bd --- .../schemas/mcp-gateway-config.schema.json | 9 ++ .../src/content/docs/reference/mcp-gateway.md | 90 ++++++++++++++++++- .../schemas/mcp-gateway-config.schema.json | 9 ++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/docs/public/schemas/mcp-gateway-config.schema.json b/docs/public/schemas/mcp-gateway-config.schema.json index e3606d9f9d2..575b46ac78e 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": "List of trusted bot identity strings that are permitted to call the gateway. When configured, only requests whose 'X-GitHub-Actor' header matches one of these identities will be accepted. Typically GitHub bot usernames such as 'github-actions[bot]' or 'copilot-swe-agent[bot]'. When omitted, bot identity is not checked and any authenticated caller may invoke the gateway.", + "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..b07033d092e 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 | List of GitHub bot identity strings (e.g., `github-actions[bot]`) permitted to call the gateway. When configured, only callers whose `X-GitHub-Actor` header matches an entry are accepted. When omitted, bot identity is not checked. | #### 4.1.3.1 Payload Directory Path Validation @@ -367,6 +368,48 @@ 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 provides an identity-based allowlist of GitHub bot accounts that are permitted to call the gateway. This is independent of the `apiKey` mechanism and operates at the HTTP request level by inspecting the `X-GitHub-Actor` header. + +**How it works**: + +1. The caller includes an `X-GitHub-Actor` header in each request identifying the GitHub actor (bot or user) making the request +2. The gateway compares the header value against every entry in the `trustedBots` list +3. If the list is configured and the actor does not match any entry, the gateway rejects the request with HTTP 403 +4. If the list is not configured, bot identity is not checked and any authenticated caller (i.e., one that passes API-key authentication) may invoke the gateway + +**Configuration Example**: + +```json +{ + "gateway": { + "port": 8080, + "domain": "localhost", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "trustedBots": [ + "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]`) +- When `trustedBots` is configured, the gateway MUST reject requests where `X-GitHub-Actor` is absent or does not match any entry (HTTP 403) +- When `trustedBots` is omitted, the gateway MUST NOT enforce bot identity checks +- Bot identity checks are applied **after** API key authentication; a request must pass both checks when both are configured +- Entries are compared case-sensitively + +**Security Considerations**: +- `trustedBots` provides defense-in-depth: even if an API key is leaked, callers that cannot supply a matching `X-GitHub-Actor` header are denied +- The `X-GitHub-Actor` header MUST be treated as an untrusted claim unless the deployment ensures that only the gateway's own infrastructure can set it (e.g., the gateway runs inside a trusted network boundary) +- Implementations SHOULD log rejected bot identity mismatches at the warning level without logging the header value in plaintext + +**Compliance Test**: T-AUTH-006, T-AUTH-007 - Trusted Bot Identity Enforcement + #### 4.1.3a Top-Level Configuration Fields The following fields MAY be specified at the top level of the configuration: @@ -943,6 +986,32 @@ The following endpoints MUST NOT require authentication: - `/health` +### 7.5 Trusted Bot Identity Authentication + +When `gateway.trustedBots` is configured, the gateway enforces an additional identity check **after** API key authentication: + +1. The gateway inspects the `X-GitHub-Actor` request header on all RPC requests to `/mcp/{server-name}` and `/close` endpoints +2. The header value MUST match one of the entries in `trustedBots` (case-sensitive) +3. If the header is absent or does not match any entry, the gateway MUST reject the request with HTTP 403 + +**Example request** (both API key and bot identity supplied): + +```http +POST /mcp/github HTTP/1.1 +Authorization: my-secret-api-key-12345 +X-GitHub-Actor: github-actions[bot] +Content-Type: application/json +``` + +**Error responses**: + +| Condition | HTTP Status | Description | +|-----------|-------------|-------------| +| `X-GitHub-Actor` header missing | 403 | Bot identity required but not supplied | +| `X-GitHub-Actor` does not match any trusted bot | 403 | Caller is not in the trusted bot list | + +**Security Note**: The `/health` endpoint MUST remain exempt from trusted bot identity checks (same as API key exemption). + --- ## 8. Health Monitoring @@ -1130,6 +1199,8 @@ 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 acceptance — request with matching `X-GitHub-Actor` header is accepted when `trustedBots` is configured +- **T-AUTH-007**: Trusted bot identity rejection — request with absent or non-matching `X-GitHub-Actor` header is rejected with HTTP 403 when `trustedBots` is configured #### 10.1.5 Timeout Tests @@ -1469,6 +1540,23 @@ 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 permitted to call the gateway + - When configured, the gateway enforces an `X-GitHub-Actor` header check on all RPC requests + - Requests whose `X-GitHub-Actor` does not match any entry are rejected with HTTP 403 + - When omitted, bot identity is not checked (any authenticated caller is accepted) + - Entries are compared case-sensitively + - The `/health` endpoint is exempt from bot identity checks +- **Added**: Section 7.5 — Trusted Bot Identity Authentication + - Specifies HTTP 403 error behavior for absent or non-matching `X-GitHub-Actor` headers + - Bot identity check runs after API key authentication +- **Added**: Compliance tests for trusted bot identities (Section 10.1.4) + - T-AUTH-006: Trusted bot identity acceptance + - T-AUTH-007: Trusted bot identity rejection +- **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/schemas/mcp-gateway-config.schema.json b/pkg/workflow/schemas/mcp-gateway-config.schema.json index 7c31da0a930..6b8a7fb4ddb 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": "List of trusted bot identity strings that are permitted to call the gateway. When configured, only requests whose 'X-GitHub-Actor' header matches one of these identities will be accepted. Typically GitHub bot usernames such as 'github-actions[bot]' or 'copilot-swe-agent[bot]'. When omitted, bot identity is not checked and any authenticated caller may invoke the gateway.", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, "required": ["port", "domain", "apiKey"], From cc60fe748e79b16a2f73a3e1bffab16fb05c732f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:21:43 +0000 Subject: [PATCH 2/5] Clarify trustedBots is additive over gateway's built-in trusted identity list Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/a5b2237b-cc7a-4b21-9d14-1eb8e8e03bf2 --- .../schemas/mcp-gateway-config.schema.json | 2 +- .../src/content/docs/reference/mcp-gateway.md | 26 ++++++++++++------- .../schemas/mcp-gateway-config.schema.json | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/public/schemas/mcp-gateway-config.schema.json b/docs/public/schemas/mcp-gateway-config.schema.json index 575b46ac78e..53b679cb607 100644 --- a/docs/public/schemas/mcp-gateway-config.schema.json +++ b/docs/public/schemas/mcp-gateway-config.schema.json @@ -249,7 +249,7 @@ }, "trustedBots": { "type": "array", - "description": "List of trusted bot identity strings that are permitted to call the gateway. When configured, only requests whose 'X-GitHub-Actor' header matches one of these identities will be accepted. Typically GitHub bot usernames such as 'github-actions[bot]' or 'copilot-swe-agent[bot]'. When omitted, bot identity is not checked and any authenticated caller may invoke the gateway.", + "description": "Additional trusted bot identities that are permitted to call the gateway, merged with the gateway's built-in internal trusted identity list. When bot identity enforcement is active, only requests whose 'X-GitHub-Actor' header matches an entry in the combined list (built-in + this field) are accepted. Typically GitHub bot usernames such as 'github-actions[bot]' or 'copilot-swe-agent[bot]'. This field is additive and cannot remove entries from the gateway's internal built-in trusted identity list.", "items": { "type": "string", "minLength": 1 diff --git a/docs/src/content/docs/reference/mcp-gateway.md b/docs/src/content/docs/reference/mcp-gateway.md index b07033d092e..ba52a4bf376 100644 --- a/docs/src/content/docs/reference/mcp-gateway.md +++ b/docs/src/content/docs/reference/mcp-gateway.md @@ -248,7 +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 | List of GitHub bot identity strings (e.g., `github-actions[bot]`) permitted to call the gateway. When configured, only callers whose `X-GitHub-Actor` header matches an entry are accepted. When omitted, bot identity is not checked. | +| `trustedBots` | array[string] | No | Additional GitHub bot identity strings (e.g., `github-actions[bot]`) added to the gateway's built-in trusted identity list. This field is additive — it extends the internal list but cannot remove built-in entries. When bot identity enforcement is active, only callers whose `X-GitHub-Actor` header matches an entry in the combined list are accepted. | #### 4.1.3.1 Payload Directory Path Validation @@ -370,14 +370,16 @@ payload_size_threshold = 262144 # 256KB - more aggressive disk storage #### 4.1.3.4 Trusted Bot Identity Configuration -The optional `trustedBots` field in the gateway configuration provides an identity-based allowlist of GitHub bot accounts that are permitted to call the gateway. This is independent of the `apiKey` mechanism and operates at the HTTP request level by inspecting the `X-GitHub-Actor` header. +The optional `trustedBots` field in the gateway configuration provides an identity-based allowlist of additional GitHub bot accounts that are permitted to call the gateway. This is independent of the `apiKey` mechanism and operates at the HTTP request level by inspecting the `X-GitHub-Actor` header. + +> **Important**: `trustedBots` is **additive**. The gateway maintains its own internal list of trusted bot identities that are always permitted regardless of this field. The `trustedBots` field extends that internal list with additional identities; it cannot remove or override the gateway's built-in trusted identities. **How it works**: 1. The caller includes an `X-GitHub-Actor` header in each request identifying the GitHub actor (bot or user) making the request -2. The gateway compares the header value against every entry in the `trustedBots` list -3. If the list is configured and the actor does not match any entry, the gateway rejects the request with HTTP 403 -4. If the list is not configured, bot identity is not checked and any authenticated caller (i.e., one that passes API-key authentication) may invoke the gateway +2. The gateway checks the header value against its **combined** list of trusted identities: the gateway's internal built-in list **plus** any entries in `trustedBots` +3. If bot identity enforcement is active and the actor does not match any entry in the combined list, the gateway rejects the request with HTTP 403 +4. If `trustedBots` is not configured, the gateway still permits callers that match its internal built-in identities; no additional identities are added **Configuration Example**: @@ -398,13 +400,16 @@ The optional `trustedBots` field in the gateway configuration provides an identi **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]`) -- When `trustedBots` is configured, the gateway MUST reject requests where `X-GitHub-Actor` is absent or does not match any entry (HTTP 403) -- When `trustedBots` is omitted, the gateway MUST NOT enforce bot identity checks +- `trustedBots` entries are **merged** with the gateway's internal built-in trusted identities to form the effective allowlist +- `trustedBots` MUST NOT be used to remove or override entries in the gateway's internal built-in trusted identity list +- When bot identity enforcement is active, the gateway MUST reject requests where `X-GitHub-Actor` is absent or does not match any entry in the combined list (HTTP 403) +- When `trustedBots` is omitted, only the gateway's built-in trusted identities are consulted - Bot identity checks are applied **after** API key authentication; a request must pass both checks when both are configured - Entries are compared case-sensitively **Security Considerations**: - `trustedBots` provides defense-in-depth: even if an API key is leaked, callers that cannot supply a matching `X-GitHub-Actor` header are denied +- Because `trustedBots` is additive, the internal built-in identities cannot be narrowed through configuration — only expanded - The `X-GitHub-Actor` header MUST be treated as an untrusted claim unless the deployment ensures that only the gateway's own infrastructure can set it (e.g., the gateway runs inside a trusted network boundary) - Implementations SHOULD log rejected bot identity mismatches at the warning level without logging the header value in plaintext @@ -988,11 +993,12 @@ The following endpoints MUST NOT require authentication: ### 7.5 Trusted Bot Identity Authentication -When `gateway.trustedBots` is configured, the gateway enforces an additional identity check **after** API key authentication: +When bot identity enforcement is active (either via `gateway.trustedBots` or the gateway's internal built-in trusted identity list), the gateway enforces an additional identity check **after** API key authentication: 1. The gateway inspects the `X-GitHub-Actor` request header on all RPC requests to `/mcp/{server-name}` and `/close` endpoints -2. The header value MUST match one of the entries in `trustedBots` (case-sensitive) -3. If the header is absent or does not match any entry, the gateway MUST reject the request with HTTP 403 +2. The header value is checked against the **combined** list of trusted identities: the gateway's built-in internal list **plus** any entries supplied in `gateway.trustedBots` +3. `gateway.trustedBots` is **additive** — it extends the built-in list but cannot remove entries from it +4. If the header is absent or does not match any entry in the combined list, the gateway MUST reject the request with HTTP 403 **Example request** (both API key and bot identity supplied): diff --git a/pkg/workflow/schemas/mcp-gateway-config.schema.json b/pkg/workflow/schemas/mcp-gateway-config.schema.json index 6b8a7fb4ddb..3b20307ad31 100644 --- a/pkg/workflow/schemas/mcp-gateway-config.schema.json +++ b/pkg/workflow/schemas/mcp-gateway-config.schema.json @@ -218,7 +218,7 @@ }, "trustedBots": { "type": "array", - "description": "List of trusted bot identity strings that are permitted to call the gateway. When configured, only requests whose 'X-GitHub-Actor' header matches one of these identities will be accepted. Typically GitHub bot usernames such as 'github-actions[bot]' or 'copilot-swe-agent[bot]'. When omitted, bot identity is not checked and any authenticated caller may invoke the gateway.", + "description": "Additional trusted bot identities that are permitted to call the gateway, merged with the gateway's built-in internal trusted identity list. When bot identity enforcement is active, only requests whose 'X-GitHub-Actor' header matches an entry in the combined list (built-in + this field) are accepted. Typically GitHub bot usernames such as 'github-actions[bot]' or 'copilot-swe-agent[bot]'. This field is additive and cannot remove entries from the gateway's internal built-in trusted identity list.", "items": { "type": "string", "minLength": 1 From 80d9f1e3441f3da3bd77661a922456ac60eed236 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:37:01 +0000 Subject: [PATCH 3/5] Remove X-GitHub-Actor header logic; trustedBots is config-only; wire frontmatter to renderer Co-authored-by: lpcox <15877973+lpcox@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/a5944233-9f4e-4300-907c-57f2cb82dafe --- .../schemas/mcp-gateway-config.schema.json | 2 +- .../src/content/docs/reference/mcp-gateway.md | 86 ++++++------------- .../frontmatter_extraction_security.go | 16 ++++ .../frontmatter_extraction_security_test.go | 34 ++++++++ pkg/workflow/mcp_gateway_config.go | 1 + pkg/workflow/mcp_gateway_config_test.go | 19 ++++ pkg/workflow/mcp_renderer.go | 10 +++ .../schemas/mcp-gateway-config.schema.json | 2 +- pkg/workflow/tools_types.go | 1 + 9 files changed, 110 insertions(+), 61 deletions(-) diff --git a/docs/public/schemas/mcp-gateway-config.schema.json b/docs/public/schemas/mcp-gateway-config.schema.json index 53b679cb607..0c7119e6877 100644 --- a/docs/public/schemas/mcp-gateway-config.schema.json +++ b/docs/public/schemas/mcp-gateway-config.schema.json @@ -249,7 +249,7 @@ }, "trustedBots": { "type": "array", - "description": "Additional trusted bot identities that are permitted to call the gateway, merged with the gateway's built-in internal trusted identity list. When bot identity enforcement is active, only requests whose 'X-GitHub-Actor' header matches an entry in the combined list (built-in + this field) are accepted. Typically GitHub bot usernames such as 'github-actions[bot]' or 'copilot-swe-agent[bot]'. This field is additive and cannot remove entries from the gateway's internal built-in trusted identity list.", + "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 diff --git a/docs/src/content/docs/reference/mcp-gateway.md b/docs/src/content/docs/reference/mcp-gateway.md index ba52a4bf376..af0e8d361f0 100644 --- a/docs/src/content/docs/reference/mcp-gateway.md +++ b/docs/src/content/docs/reference/mcp-gateway.md @@ -248,7 +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]`) added to the gateway's built-in trusted identity list. This field is additive — it extends the internal list but cannot remove built-in entries. When bot identity enforcement is active, only callers whose `X-GitHub-Actor` header matches an entry in the combined list are accepted. | +| `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 @@ -370,16 +370,9 @@ payload_size_threshold = 262144 # 256KB - more aggressive disk storage #### 4.1.3.4 Trusted Bot Identity Configuration -The optional `trustedBots` field in the gateway configuration provides an identity-based allowlist of additional GitHub bot accounts that are permitted to call the gateway. This is independent of the `apiKey` mechanism and operates at the HTTP request level by inspecting the `X-GitHub-Actor` header. +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 that are always permitted regardless of this field. The `trustedBots` field extends that internal list with additional identities; it cannot remove or override the gateway's built-in trusted identities. - -**How it works**: - -1. The caller includes an `X-GitHub-Actor` header in each request identifying the GitHub actor (bot or user) making the request -2. The gateway checks the header value against its **combined** list of trusted identities: the gateway's internal built-in list **plus** any entries in `trustedBots` -3. If bot identity enforcement is active and the actor does not match any entry in the combined list, the gateway rejects the request with HTTP 403 -4. If `trustedBots` is not configured, the gateway still permits callers that match its internal built-in identities; no additional identities are added +> **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**: @@ -397,23 +390,23 @@ The optional `trustedBots` field in the gateway configuration provides an identi } ``` +**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 allowlist +- `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 -- When bot identity enforcement is active, the gateway MUST reject requests where `X-GitHub-Actor` is absent or does not match any entry in the combined list (HTTP 403) -- When `trustedBots` is omitted, only the gateway's built-in trusted identities are consulted -- Bot identity checks are applied **after** API key authentication; a request must pass both checks when both are configured -- Entries are compared case-sensitively - -**Security Considerations**: -- `trustedBots` provides defense-in-depth: even if an API key is leaked, callers that cannot supply a matching `X-GitHub-Actor` header are denied -- Because `trustedBots` is additive, the internal built-in identities cannot be narrowed through configuration — only expanded -- The `X-GitHub-Actor` header MUST be treated as an untrusted claim unless the deployment ensures that only the gateway's own infrastructure can set it (e.g., the gateway runs inside a trusted network boundary) -- Implementations SHOULD log rejected bot identity mismatches at the warning level without logging the header value in plaintext -**Compliance Test**: T-AUTH-006, T-AUTH-007 - Trusted Bot Identity Enforcement +**Compliance Test**: T-AUTH-006 - Trusted Bot Identity Configuration #### 4.1.3a Top-Level Configuration Fields @@ -991,32 +984,13 @@ The following endpoints MUST NOT require authentication: - `/health` -### 7.5 Trusted Bot Identity Authentication - -When bot identity enforcement is active (either via `gateway.trustedBots` or the gateway's internal built-in trusted identity list), the gateway enforces an additional identity check **after** API key authentication: - -1. The gateway inspects the `X-GitHub-Actor` request header on all RPC requests to `/mcp/{server-name}` and `/close` endpoints -2. The header value is checked against the **combined** list of trusted identities: the gateway's built-in internal list **plus** any entries supplied in `gateway.trustedBots` -3. `gateway.trustedBots` is **additive** — it extends the built-in list but cannot remove entries from it -4. If the header is absent or does not match any entry in the combined list, the gateway MUST reject the request with HTTP 403 - -**Example request** (both API key and bot identity supplied): - -```http -POST /mcp/github HTTP/1.1 -Authorization: my-secret-api-key-12345 -X-GitHub-Actor: github-actions[bot] -Content-Type: application/json -``` +### 7.5 Trusted Bot Identity Configuration -**Error responses**: +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. -| Condition | HTTP Status | Description | -|-----------|-------------|-------------| -| `X-GitHub-Actor` header missing | 403 | Bot identity required but not supplied | -| `X-GitHub-Actor` does not match any trusted bot | 403 | Caller is not in the trusted bot list | +`gateway.trustedBots` is **additive** — it extends the gateway's built-in list but cannot remove entries from it. -**Security Note**: The `/health` endpoint MUST remain exempt from trusted bot identity checks (same as API key exemption). +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. --- @@ -1205,8 +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 acceptance — request with matching `X-GitHub-Actor` header is accepted when `trustedBots` is configured -- **T-AUTH-007**: Trusted bot identity rejection — request with absent or non-matching `X-GitHub-Actor` header is rejected with HTTP 403 when `trustedBots` is configured +- **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 @@ -1549,18 +1522,13 @@ Content-Type: application/json ### 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 permitted to call the gateway - - When configured, the gateway enforces an `X-GitHub-Actor` header check on all RPC requests - - Requests whose `X-GitHub-Actor` does not match any entry are rejected with HTTP 403 - - When omitted, bot identity is not checked (any authenticated caller is accepted) - - Entries are compared case-sensitively - - The `/health` endpoint is exempt from bot identity checks -- **Added**: Section 7.5 — Trusted Bot Identity Authentication - - Specifies HTTP 403 error behavior for absent or non-matching `X-GitHub-Actor` headers - - Bot identity check runs after API key authentication -- **Added**: Compliance tests for trusted bot identities (Section 10.1.4) - - T-AUTH-006: Trusted bot identity acceptance - - T-AUTH-007: Trusted bot identity rejection + - 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) 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 3b20307ad31..99749c51bba 100644 --- a/pkg/workflow/schemas/mcp-gateway-config.schema.json +++ b/pkg/workflow/schemas/mcp-gateway-config.schema.json @@ -218,7 +218,7 @@ }, "trustedBots": { "type": "array", - "description": "Additional trusted bot identities that are permitted to call the gateway, merged with the gateway's built-in internal trusted identity list. When bot identity enforcement is active, only requests whose 'X-GitHub-Actor' header matches an entry in the combined list (built-in + this field) are accepted. Typically GitHub bot usernames such as 'github-actions[bot]' or 'copilot-swe-agent[bot]'. This field is additive and cannot remove entries from the gateway's internal built-in trusted identity list.", + "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 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 From 3b21cacc7cdb8eb7a2e87ff702ec818226d1bd8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 02:36:00 +0000 Subject: [PATCH 4/5] Reuse existing on.bots frontmatter field to populate gateway trustedBots Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/8348df8c-466a-4132-8464-c9e8456fc12f --- .../src/content/docs/reference/mcp-gateway.md | 19 ++++++----- .../frontmatter_extraction_security.go | 16 --------- .../frontmatter_extraction_security_test.go | 34 ------------------- pkg/workflow/mcp_gateway_config.go | 2 +- pkg/workflow/mcp_gateway_config_test.go | 8 ++--- pkg/workflow/tools_types.go | 2 +- 6 files changed, 15 insertions(+), 66 deletions(-) diff --git a/docs/src/content/docs/reference/mcp-gateway.md b/docs/src/content/docs/reference/mcp-gateway.md index af0e8d361f0..8be9dd8a3dd 100644 --- a/docs/src/content/docs/reference/mcp-gateway.md +++ b/docs/src/content/docs/reference/mcp-gateway.md @@ -393,13 +393,16 @@ The optional `trustedBots` field in the gateway configuration passes an addition **Frontmatter Example** (workflow author): ```yaml -sandbox: - mcp: - trusted-bots: - - github-actions[bot] - - copilot-swe-agent[bot] +on: + issues: + types: [opened] + bots: + - github-actions[bot] + - copilot-swe-agent[bot] ``` +The compiler reads the `on.bots` field and includes its values in the `trustedBots` array of the generated gateway config section. + **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]`) @@ -990,7 +993,7 @@ The `gateway.trustedBots` field allows workflow authors to pass additional trust `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. +Workflow authors set this via the `on.bots` frontmatter field; the compiler populates the `trustedBots` array in the generated `gateway` section of the MCP config file with the values from that field. --- @@ -1524,9 +1527,9 @@ Content-Type: application/json - **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 + - Workflow authors configure via the existing `on.bots` frontmatter field; the compiler populates `trustedBots` in the gateway config from that field - **Added**: Section 7.5 — Trusted Bot Identity Configuration - - Describes `trustedBots` as a config-passing mechanism from frontmatter to gateway config + - Describes `trustedBots` as a config-passing mechanism from `on.bots` 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 diff --git a/pkg/workflow/frontmatter_extraction_security.go b/pkg/workflow/frontmatter_extraction_security.go index c9427844995..183c8d52595 100644 --- a/pkg/workflow/frontmatter_extraction_security.go +++ b/pkg/workflow/frontmatter_extraction_security.go @@ -479,22 +479,6 @@ 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 8345b259a7e..761afbfdf01 100644 --- a/pkg/workflow/frontmatter_extraction_security_test.go +++ b/pkg/workflow/frontmatter_extraction_security_test.go @@ -218,37 +218,3 @@ 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 42aa59a0127..fde4def9c2f 100644 --- a/pkg/workflow/mcp_gateway_config.go +++ b/pkg/workflow/mcp_gateway_config.go @@ -137,7 +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 + TrustedBots: workflowData.Bots, // Bot identities from on.bots frontmatter field } } diff --git a/pkg/workflow/mcp_gateway_config_test.go b/pkg/workflow/mcp_gateway_config_test.go index 9eef167e15c..a4fd1fbfa4e 100644 --- a/pkg/workflow/mcp_gateway_config_test.go +++ b/pkg/workflow/mcp_gateway_config_test.go @@ -316,13 +316,9 @@ func TestBuildMCPGatewayConfig(t *testing.T) { }, }, { - name: "propagates trustedBots from frontmatter config", + name: "propagates bots from workflowData.Bots to gateway trustedBots", workflowData: &WorkflowData{ - SandboxConfig: &SandboxConfig{ - MCP: &MCPGatewayRuntimeConfig{ - TrustedBots: []string{"github-actions[bot]", "copilot-swe-agent[bot]"}, - }, - }, + Bots: []string{"github-actions[bot]", "copilot-swe-agent[bot]"}, }, expected: &MCPGatewayRuntimeConfig{ Port: int(DefaultMCPGatewayPort), diff --git a/pkg/workflow/tools_types.go b/pkg/workflow/tools_types.go index c8943d4f826..ba6c996ccef 100644 --- a/pkg/workflow/tools_types.go +++ b/pkg/workflow/tools_types.go @@ -396,7 +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 + TrustedBots []string `yaml:"-"` // Bot identities from on.bots, passed to the gateway's trustedBots config } // HasTool checks if a tool is present in the configuration From 0d5251c4d8beee0747140e194b9c4c8a081945e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 03:08:08 +0000 Subject: [PATCH 5/5] Revert 3b21cac: restore sandbox.mcp.trusted-bots frontmatter extraction Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Agent-Logs-Url: https://github.com/github/gh-aw/sessions/1b3cf42e-3fa1-4bcf-8c9e-2edd43ac9d05 --- .../src/content/docs/reference/mcp-gateway.md | 19 +++++------ .../frontmatter_extraction_security.go | 16 +++++++++ .../frontmatter_extraction_security_test.go | 34 +++++++++++++++++++ pkg/workflow/mcp_gateway_config.go | 2 +- pkg/workflow/mcp_gateway_config_test.go | 8 +++-- pkg/workflow/tools_types.go | 2 +- 6 files changed, 66 insertions(+), 15 deletions(-) diff --git a/docs/src/content/docs/reference/mcp-gateway.md b/docs/src/content/docs/reference/mcp-gateway.md index 8be9dd8a3dd..af0e8d361f0 100644 --- a/docs/src/content/docs/reference/mcp-gateway.md +++ b/docs/src/content/docs/reference/mcp-gateway.md @@ -393,16 +393,13 @@ The optional `trustedBots` field in the gateway configuration passes an addition **Frontmatter Example** (workflow author): ```yaml -on: - issues: - types: [opened] - bots: - - github-actions[bot] - - copilot-swe-agent[bot] +sandbox: + mcp: + trusted-bots: + - github-actions[bot] + - copilot-swe-agent[bot] ``` -The compiler reads the `on.bots` field and includes its values in the `trustedBots` array of the generated gateway config section. - **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]`) @@ -993,7 +990,7 @@ The `gateway.trustedBots` field allows workflow authors to pass additional trust `gateway.trustedBots` is **additive** — it extends the gateway's built-in list but cannot remove entries from it. -Workflow authors set this via the `on.bots` frontmatter field; the compiler populates the `trustedBots` array in the generated `gateway` section of the MCP config file with the values from that field. +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. --- @@ -1527,9 +1524,9 @@ Content-Type: application/json - **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 the existing `on.bots` frontmatter field; the compiler populates `trustedBots` in the gateway config from that field + - 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 `on.bots` frontmatter to gateway config + - 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 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 fde4def9c2f..42aa59a0127 100644 --- a/pkg/workflow/mcp_gateway_config.go +++ b/pkg/workflow/mcp_gateway_config.go @@ -137,7 +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.Bots, // Bot identities from on.bots frontmatter field + 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 a4fd1fbfa4e..9eef167e15c 100644 --- a/pkg/workflow/mcp_gateway_config_test.go +++ b/pkg/workflow/mcp_gateway_config_test.go @@ -316,9 +316,13 @@ func TestBuildMCPGatewayConfig(t *testing.T) { }, }, { - name: "propagates bots from workflowData.Bots to gateway trustedBots", + name: "propagates trustedBots from frontmatter config", workflowData: &WorkflowData{ - Bots: []string{"github-actions[bot]", "copilot-swe-agent[bot]"}, + SandboxConfig: &SandboxConfig{ + MCP: &MCPGatewayRuntimeConfig{ + TrustedBots: []string{"github-actions[bot]", "copilot-swe-agent[bot]"}, + }, + }, }, expected: &MCPGatewayRuntimeConfig{ Port: int(DefaultMCPGatewayPort), diff --git a/pkg/workflow/tools_types.go b/pkg/workflow/tools_types.go index ba6c996ccef..c8943d4f826 100644 --- a/pkg/workflow/tools_types.go +++ b/pkg/workflow/tools_types.go @@ -396,7 +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:"-"` // Bot identities from on.bots, passed to the gateway's trustedBots config + 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