Summary
mcp_setup_generator.go generates mkdir -p "\$\{MCP_GATEWAY_PAYLOAD_DIR}" for /tmp/gh-aw/mcp-payloads/ with no subsequent chmod, resulting in world-readable 0755 permissions. The MCP gateway container then mounts this path and creates a session subdirectory named after the full bearer token, making the token recoverable by any process on the runner host via a simple ls /tmp/gh-aw/mcp-payloads/ — no runner user identity required. This bypasses the intentional 0600 restriction on mcp-servers.json (hardened by prior fixes #1532, #1548, #1502) by leaking the same token through a different, unprotected filesystem path.
Affected Area
MCP gateway token confidentiality boundary — pkg/workflow/mcp_setup_generator.go (directory creation and permission setup for /tmp/gh-aw/mcp-payloads/); secondary concern in the MCP gateway container (ghcr.io/github/gh-aw-mcpg) which uses the raw token as the session directory name.
Reproduction Outline
- Run any gh-aw workflow that uses the MCP gateway (e.g., Claude Code engine), gh-aw v0.68.3.
- Wait approximately 90 seconds for the MCP gateway to process its first authenticated request.
- From any process on the runner host (any user identity — runner user NOT required), run:
ls /tmp/gh-aw/mcp-payloads/
- The output is the full bearer token as a subdirectory name.
- Use the token directly for MCP gateway calls:
TOKEN=$(ls /tmp/gh-aw/mcp-payloads/)
curl -s -X POST <mcp-gateway-endpoint> \
-H "Authorization: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"x","version":"1"}}}'
Observed Behavior
/tmp/gh-aw/mcp-payloads/ is mode 0755 (world-readable). The gateway container (running as root) creates a subdirectory named after the full bearer token with mode 0755. The token is visible to any user on the host from a directory listing.
Expected Behavior
The mcp-payloads/ directory and its parent should be mode 0700 (runner-user-only), consistent with the intent behind mcp-servers.json being 0600. Additionally, the MCP gateway should use a hash or ephemeral random session ID as the directory name so the bearer token never appears as a filesystem path component.
Security Relevance
The bearer token controls access to create_issue, noop, and other safe-outputs write-sink tools via the MCP gateway. Recovery requires only a directory listing on a world-accessible path — no runner user identity, no special privileges. This is distinct from the runner-user-only risk in #25102 (upstream of githubnext/gh-aw-security#1695): that finding required the runner identity to read a 0600 file; this finding requires no special identity at all. Risk is highest on self-hosted or shared runners where other user identities (containers, services, tool processes) coexist on the host.
Suggested Fix
Immediate (smallest fix): Add chmod 700 "\$\{MCP_GATEWAY_PAYLOAD_DIR}" after the mkdir -p line in mcp_setup_generator.go (pkg/workflow/mcp_setup_generator.go, line ~575).
Deeper fix: Change the MCP gateway container to create session subdirectories named after a random session ID or one-way hash of the token — never the token itself — so the bearer token is never a filesystem path component.
Additional Context
If the current behavior (bearer token as directory name in a world-accessible path) is considered an acceptable operational assumption, that assumption should be explicitly documented in the security model. As-is, it contradicts the documented intent of the 0600 restriction on mcp-servers.json.
gh-aw version: v0.68.3 (from compiler_version in lock file)
Original finding: https://github.com/githubnext/gh-aw-security/issues/2125
Generated by File Issue · ● 244.5K · ◷
Summary
mcp_setup_generator.gogeneratesmkdir -p "\$\{MCP_GATEWAY_PAYLOAD_DIR}"for/tmp/gh-aw/mcp-payloads/with no subsequentchmod, resulting in world-readable0755permissions. The MCP gateway container then mounts this path and creates a session subdirectory named after the full bearer token, making the token recoverable by any process on the runner host via a simplels /tmp/gh-aw/mcp-payloads/— no runner user identity required. This bypasses the intentional0600restriction onmcp-servers.json(hardened by prior fixes #1532, #1548, #1502) by leaking the same token through a different, unprotected filesystem path.Affected Area
MCP gateway token confidentiality boundary —
pkg/workflow/mcp_setup_generator.go(directory creation and permission setup for/tmp/gh-aw/mcp-payloads/); secondary concern in the MCP gateway container (ghcr.io/github/gh-aw-mcpg) which uses the raw token as the session directory name.Reproduction Outline
Observed Behavior
/tmp/gh-aw/mcp-payloads/is mode0755(world-readable). The gateway container (running as root) creates a subdirectory named after the full bearer token with mode0755. The token is visible to any user on the host from a directory listing.Expected Behavior
The
mcp-payloads/directory and its parent should be mode0700(runner-user-only), consistent with the intent behindmcp-servers.jsonbeing0600. Additionally, the MCP gateway should use a hash or ephemeral random session ID as the directory name so the bearer token never appears as a filesystem path component.Security Relevance
The bearer token controls access to
create_issue,noop, and other safe-outputs write-sink tools via the MCP gateway. Recovery requires only a directory listing on a world-accessible path — no runner user identity, no special privileges. This is distinct from the runner-user-only risk in #25102 (upstream of githubnext/gh-aw-security#1695): that finding required the runner identity to read a0600file; this finding requires no special identity at all. Risk is highest on self-hosted or shared runners where other user identities (containers, services, tool processes) coexist on the host.Suggested Fix
Immediate (smallest fix): Add
chmod 700 "\$\{MCP_GATEWAY_PAYLOAD_DIR}"after themkdir -pline inmcp_setup_generator.go(pkg/workflow/mcp_setup_generator.go, line ~575).Deeper fix: Change the MCP gateway container to create session subdirectories named after a random session ID or one-way hash of the token — never the token itself — so the bearer token is never a filesystem path component.
Additional Context
If the current behavior (bearer token as directory name in a world-accessible path) is considered an acceptable operational assumption, that assumption should be explicitly documented in the security model. As-is, it contradicts the documented intent of the
0600restriction onmcp-servers.json.gh-aw version: v0.68.3 (from
compiler_versionin lock file)Original finding: https://github.com/githubnext/gh-aw-security/issues/2125