From d4b802cb6049a9acefc5de5fe4b309dcfbc4a8bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:31:07 +0000 Subject: [PATCH 1/5] Initial plan From da445d38d0f133186d87594b10e9da220f472c3f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:09:40 +0000 Subject: [PATCH 2/5] Fix MCP gateway tool allowlist enforcement and restrict config file permissions Agent-Logs-Url: https://github.com/github/gh-aw/sessions/46ec2473-c5b5-46c3-a749-6b1ba368ae57 Co-authored-by: szabta89 <1330202+szabta89@users.noreply.github.com> --- actions/setup/sh/convert_gateway_config_claude.sh | 10 ++++++++-- actions/setup/sh/convert_gateway_config_codex.sh | 5 +++++ actions/setup/sh/convert_gateway_config_copilot.sh | 6 ++++++ actions/setup/sh/convert_gateway_config_gemini.sh | 10 ++++++++-- actions/setup/sh/start_mcp_gateway.sh | 6 ++++++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/actions/setup/sh/convert_gateway_config_claude.sh b/actions/setup/sh/convert_gateway_config_claude.sh index 639147cafe1..a12ed95df8e 100755 --- a/actions/setup/sh/convert_gateway_config_claude.sh +++ b/actions/setup/sh/convert_gateway_config_claude.sh @@ -63,7 +63,8 @@ echo "Target domain: $MCP_GATEWAY_DOMAIN:$MCP_GATEWAY_PORT" # # The main differences: # 1. Claude uses "type": "http" for HTTP-based MCP servers -# 2. The "tools" field is removed as it's Copilot-specific +# 2. The "tools" field is preserved from the gateway config to enforce the tool allowlist +# at the gateway layer (not removed, unlike older versions that treated it as Copilot-specific) # 3. URLs must use the correct domain (host.docker.internal) for container access # Build the correct URL prefix using the configured domain and port @@ -73,13 +74,18 @@ jq --arg urlPrefix "$URL_PREFIX" ' .mcpServers |= with_entries( .value |= ( (.type = "http") | - (del(.tools)) | # Fix the URL to use the correct domain .url |= (. | sub("^http://[^/]+/mcp/"; $urlPrefix + "/mcp/")) ) ) ' "$MCP_GATEWAY_OUTPUT" > /tmp/gh-aw/mcp-config/mcp-servers.json +# Restrict permissions so only the runner process owner can read this file. +# mcp-servers.json contains the bearer token for the MCP gateway; an attacker +# who reads it could bypass the --allowed-tools constraint by issuing raw +# JSON-RPC calls directly to the gateway. +chmod 600 /tmp/gh-aw/mcp-config/mcp-servers.json + echo "Claude configuration written to /tmp/gh-aw/mcp-config/mcp-servers.json" echo "" echo "Converted configuration:" diff --git a/actions/setup/sh/convert_gateway_config_codex.sh b/actions/setup/sh/convert_gateway_config_codex.sh index b89ac2fa2a2..08c390342eb 100755 --- a/actions/setup/sh/convert_gateway_config_codex.sh +++ b/actions/setup/sh/convert_gateway_config_codex.sh @@ -85,6 +85,11 @@ jq -r --arg urlPrefix "$URL_PREFIX" ' "http_headers = { Authorization = \"\(.value.headers.Authorization)\" }\n" ' "$MCP_GATEWAY_OUTPUT" >> /tmp/gh-aw/mcp-config/config.toml +# Restrict permissions so only the runner process owner can read this file. +# config.toml contains the bearer token for the MCP gateway; an attacker +# who reads it could issue raw JSON-RPC calls directly to the gateway. +chmod 600 /tmp/gh-aw/mcp-config/config.toml + echo "Codex configuration written to /tmp/gh-aw/mcp-config/config.toml" echo "" echo "Converted configuration:" diff --git a/actions/setup/sh/convert_gateway_config_copilot.sh b/actions/setup/sh/convert_gateway_config_copilot.sh index 14f471bbd69..8f52fc12e3c 100755 --- a/actions/setup/sh/convert_gateway_config_copilot.sh +++ b/actions/setup/sh/convert_gateway_config_copilot.sh @@ -82,6 +82,12 @@ jq --arg urlPrefix "$URL_PREFIX" ' ) ' "$MCP_GATEWAY_OUTPUT" > /home/runner/.copilot/mcp-config.json +# Restrict permissions so only the runner process owner can read this file. +# mcp-config.json contains the bearer token for the MCP gateway; an attacker +# who reads it could bypass the --allowed-tools constraint by issuing raw +# JSON-RPC calls directly to the gateway. +chmod 600 /home/runner/.copilot/mcp-config.json + echo "Copilot configuration written to /home/runner/.copilot/mcp-config.json" echo "" echo "Converted configuration:" diff --git a/actions/setup/sh/convert_gateway_config_gemini.sh b/actions/setup/sh/convert_gateway_config_gemini.sh index 4b2b14d5711..c53a3df9dd7 100644 --- a/actions/setup/sh/convert_gateway_config_gemini.sh +++ b/actions/setup/sh/convert_gateway_config_gemini.sh @@ -74,7 +74,8 @@ echo "Target domain: $MCP_GATEWAY_DOMAIN:$MCP_GATEWAY_PORT" # # The main differences: # 1. Remove "type" field (Gemini uses transport auto-detection from url/httpUrl) -# 2. Remove "tools" field (Copilot-specific) +# 2. The "tools" field is preserved from the gateway config to enforce the tool allowlist +# at the gateway layer (not removed, unlike older versions that treated it as Copilot-specific) # 3. URLs must use the correct domain (host.docker.internal) for container access # Build the correct URL prefix using the configured domain and port @@ -90,7 +91,6 @@ jq --arg urlPrefix "$URL_PREFIX" ' .mcpServers |= with_entries( .value |= ( (del(.type)) | - (del(.tools)) | # Fix the URL to use the correct domain .url |= (. | sub("^http://[^/]+/mcp/"; $urlPrefix + "/mcp/")) ) @@ -99,6 +99,12 @@ jq --arg urlPrefix "$URL_PREFIX" ' .context.includeDirectories = ["/tmp/"] ' "$MCP_GATEWAY_OUTPUT" > "$GEMINI_SETTINGS_FILE" +# Restrict permissions so only the runner process owner can read this file. +# settings.json contains the bearer token for the MCP gateway; an attacker +# who reads it could bypass the --allowed-tools constraint by issuing raw +# JSON-RPC calls directly to the gateway. +chmod 600 "$GEMINI_SETTINGS_FILE" + echo "Gemini configuration written to $GEMINI_SETTINGS_FILE" echo "" echo "Converted configuration:" diff --git a/actions/setup/sh/start_mcp_gateway.sh b/actions/setup/sh/start_mcp_gateway.sh index 1da8cdc9242..c518cdba492 100755 --- a/actions/setup/sh/start_mcp_gateway.sh +++ b/actions/setup/sh/start_mcp_gateway.sh @@ -30,6 +30,9 @@ fi # Create logs directory for gateway mkdir -p /tmp/gh-aw/mcp-logs mkdir -p /tmp/gh-aw/mcp-config +# Restrict directory permissions so only the runner process owner can read config files +# (which contain bearer tokens and API keys) +chmod 700 /tmp/gh-aw/mcp-config # Validate container syntax first (before accessing files) # Container should be a valid docker command starting with "docker run" @@ -310,6 +313,9 @@ if [ ! -s /tmp/gh-aw/mcp-config/gateway-output.json ]; then exit 1 fi +# Restrict gateway output file permissions - it contains the bearer token / API key +chmod 600 /tmp/gh-aw/mcp-config/gateway-output.json + # Check if output contains an error payload instead of valid configuration # Per MCP Gateway Specification v1.0.0 section 9.1, errors are written to stdout as error payloads if jq -e '.error' /tmp/gh-aw/mcp-config/gateway-output.json >/dev/null 2>&1; then From 075acc806746d840ec156f416b1a7f023edae153 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 03:45:46 +0000 Subject: [PATCH 3/5] Add changeset --- .changeset/patch-enforce-mcp-gateway-allowlist.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/patch-enforce-mcp-gateway-allowlist.md diff --git a/.changeset/patch-enforce-mcp-gateway-allowlist.md b/.changeset/patch-enforce-mcp-gateway-allowlist.md new file mode 100644 index 00000000000..6f90c77f74f --- /dev/null +++ b/.changeset/patch-enforce-mcp-gateway-allowlist.md @@ -0,0 +1,5 @@ +--- +"gh-aw": patch +--- + +Enforce MCP gateway tool allowlists at the gateway layer and harden permissions on credential-bearing gateway config files. From 5b10eec70b6f6f761252940c2528af89eb93ace0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 04:31:52 +0000 Subject: [PATCH 4/5] Eliminate umask race: add umask 077 to all gateway scripts; add symlink guard Agent-Logs-Url: https://github.com/github/gh-aw/sessions/7e6848f9-2bdf-49a1-8e70-ff7336112133 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/sh/convert_gateway_config_claude.sh | 6 ++++++ actions/setup/sh/convert_gateway_config_codex.sh | 6 ++++++ actions/setup/sh/convert_gateway_config_copilot.sh | 6 ++++++ actions/setup/sh/convert_gateway_config_gemini.sh | 6 ++++++ actions/setup/sh/start_mcp_gateway.sh | 14 ++++++++++++++ 5 files changed, 38 insertions(+) diff --git a/actions/setup/sh/convert_gateway_config_claude.sh b/actions/setup/sh/convert_gateway_config_claude.sh index a12ed95df8e..926d47065ee 100755 --- a/actions/setup/sh/convert_gateway_config_claude.sh +++ b/actions/setup/sh/convert_gateway_config_claude.sh @@ -5,6 +5,12 @@ set -e +# Restrict default file creation mode to owner-only (rw-------) for all new files. +# This prevents the race window between file creation via output redirection and +# a subsequent chmod, which would leave credential-bearing files world-readable +# (mode 0644) with a typical umask of 022. +umask 077 + # Required environment variables: # - MCP_GATEWAY_OUTPUT: Path to gateway output configuration file # - MCP_GATEWAY_DOMAIN: Domain to use for MCP server URLs (e.g., host.docker.internal) diff --git a/actions/setup/sh/convert_gateway_config_codex.sh b/actions/setup/sh/convert_gateway_config_codex.sh index 08c390342eb..f172c0b206f 100755 --- a/actions/setup/sh/convert_gateway_config_codex.sh +++ b/actions/setup/sh/convert_gateway_config_codex.sh @@ -5,6 +5,12 @@ set -e +# Restrict default file creation mode to owner-only (rw-------) for all new files. +# This prevents the race window between file creation via output redirection and +# a subsequent chmod, which would leave credential-bearing files world-readable +# (mode 0644) with a typical umask of 022. +umask 077 + # Required environment variables: # - MCP_GATEWAY_OUTPUT: Path to gateway output configuration file # - MCP_GATEWAY_DOMAIN: Domain to use for MCP server URLs (e.g., host.docker.internal) diff --git a/actions/setup/sh/convert_gateway_config_copilot.sh b/actions/setup/sh/convert_gateway_config_copilot.sh index 8f52fc12e3c..5544165dae9 100755 --- a/actions/setup/sh/convert_gateway_config_copilot.sh +++ b/actions/setup/sh/convert_gateway_config_copilot.sh @@ -5,6 +5,12 @@ set -e +# Restrict default file creation mode to owner-only (rw-------) for all new files. +# This prevents the race window between file creation via output redirection and +# a subsequent chmod, which would leave credential-bearing files world-readable +# (mode 0644) with a typical umask of 022. +umask 077 + # Required environment variables: # - MCP_GATEWAY_OUTPUT: Path to gateway output configuration file # - MCP_GATEWAY_DOMAIN: Domain to use for MCP server URLs (e.g., host.docker.internal) diff --git a/actions/setup/sh/convert_gateway_config_gemini.sh b/actions/setup/sh/convert_gateway_config_gemini.sh index c53a3df9dd7..353060b9c24 100644 --- a/actions/setup/sh/convert_gateway_config_gemini.sh +++ b/actions/setup/sh/convert_gateway_config_gemini.sh @@ -11,6 +11,12 @@ set -e +# Restrict default file creation mode to owner-only (rw-------) for all new files. +# This prevents the race window between file creation via output redirection and +# a subsequent chmod, which would leave credential-bearing files world-readable +# (mode 0644) with a typical umask of 022. +umask 077 + # Required environment variables: # - MCP_GATEWAY_OUTPUT: Path to gateway output configuration file # - MCP_GATEWAY_DOMAIN: Domain to use for MCP server URLs (e.g., host.docker.internal) diff --git a/actions/setup/sh/start_mcp_gateway.sh b/actions/setup/sh/start_mcp_gateway.sh index c518cdba492..055f5db3cd0 100755 --- a/actions/setup/sh/start_mcp_gateway.sh +++ b/actions/setup/sh/start_mcp_gateway.sh @@ -8,6 +8,12 @@ set -e +# Restrict default file creation mode to owner-only (rw-------) for all new files. +# This prevents the race window between file creation via output redirection and +# a subsequent chmod, which would leave credential-bearing files world-readable +# (mode 0644) with a typical umask of 022. +umask 077 + # Timing helper functions print_timing() { local start_time=$1 @@ -29,6 +35,14 @@ fi # Create logs directory for gateway mkdir -p /tmp/gh-aw/mcp-logs + +# Guard against symlink attacks on the predictable /tmp/gh-aw/mcp-config path. +# An attacker who can create files in /tmp could pre-create this path as a symlink +# and redirect our credential writes to an arbitrary location. +if [ -L /tmp/gh-aw/mcp-config ]; then + echo "ERROR: /tmp/gh-aw/mcp-config is a symlink — possible symlink attack, aborting" + exit 1 +fi mkdir -p /tmp/gh-aw/mcp-config # Restrict directory permissions so only the runner process owner can read config files # (which contain bearer tokens and API keys) From d67fae1963859a9229586b552a802e9524e1bf29 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 04:33:14 +0000 Subject: [PATCH 5/5] Harden symlink guard: check /tmp/gh-aw parent dir and add post-mkdir TOCTOU check Agent-Logs-Url: https://github.com/github/gh-aw/sessions/7e6848f9-2bdf-49a1-8e70-ff7336112133 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/sh/start_mcp_gateway.sh | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/actions/setup/sh/start_mcp_gateway.sh b/actions/setup/sh/start_mcp_gateway.sh index 055f5db3cd0..979fb86aa4d 100755 --- a/actions/setup/sh/start_mcp_gateway.sh +++ b/actions/setup/sh/start_mcp_gateway.sh @@ -37,13 +37,24 @@ fi mkdir -p /tmp/gh-aw/mcp-logs # Guard against symlink attacks on the predictable /tmp/gh-aw/mcp-config path. -# An attacker who can create files in /tmp could pre-create this path as a symlink -# and redirect our credential writes to an arbitrary location. +# An attacker who can create files in /tmp could pre-create /tmp/gh-aw or +# /tmp/gh-aw/mcp-config as symlinks and redirect our credential writes to an +# arbitrary location. Check both path components before and after creation. +if [ -L /tmp/gh-aw ]; then + echo "ERROR: /tmp/gh-aw is a symlink — possible symlink attack, aborting" + exit 1 +fi if [ -L /tmp/gh-aw/mcp-config ]; then echo "ERROR: /tmp/gh-aw/mcp-config is a symlink — possible symlink attack, aborting" exit 1 fi mkdir -p /tmp/gh-aw/mcp-config +# Post-creation check: verify neither path was replaced with a symlink after mkdir +# (mitigates the TOCTOU window between the pre-check and mkdir). +if [ -L /tmp/gh-aw ] || [ -L /tmp/gh-aw/mcp-config ]; then + echo "ERROR: /tmp/gh-aw/mcp-config was replaced with a symlink — possible symlink attack, aborting" + exit 1 +fi # Restrict directory permissions so only the runner process owner can read config files # (which contain bearer tokens and API keys) chmod 700 /tmp/gh-aw/mcp-config