-
Notifications
You must be signed in to change notification settings - Fork 318
Safe-outputs write-sink MCP must use a distinct, scoped bearer token not shared with the read GitHub MCP #23740
Description
Summary
In Copilot-engine (--allow-all-tools) github-edit workflows, the MCP gateway API key is shared between the GitHub read MCP server and the safe-outputs write-sink MCP server, and is written in plaintext to /home/runner/.copilot/mcp-config.json where it is readable by all runner-user processes. Any bash code with access to the runner filesystem can read this key, open a safeoutputs MCP session over HTTP, and invoke write tools (create_issue, add_comment, etc.), queuing entries directly into the safeoutputs JSONL queue — without involving the AI agent at all. A noop canary call from a standalone curl invocation was confirmed to succeed (HTTP 200) and write to the queue. create_issue is on the identical code path (tools/list confirmed its presence on the same session).
The gh-aw overview states write operations happen "only through sanitized safe-outputs," implying the AI agent is the authorized caller. The actual enforcement boundary is the bearer token, which is shared and filesystem-readable, not bound to the agent process.
Affected Area
Safe-outputs write-sink MCP trust boundary — session key management and caller-identity enforcement.
Reproduction Outline
- In a Copilot-engine workflow with
safe-outputs: create-issueandbashtool access, read the shared MCP API key:python3 -c "import json; c=json.load(open('/home/runner/.copilot/mcp-config.json')); print(c['mcpServers']['safeoutputs']['headers']['Authorization'])" - Initialize a safeoutputs MCP session directly via
curlusing that key against thesafeoutputsMCP server URL from the same config. - Send the
notifications/initializedhandshake. - Call
tools/callwithname: "noop"(canary) orname: "create_issue"with a title and body. - Inspect
/home/runner/work/_temp/gh-aw/safeoutputs/outputs.jsonl— the entry is written immediately.
Observed Behavior
HTTP 200 with {"result":"success"} on both noop and (by code-path parity) create_issue. The JSONL file is updated immediately. The safeoutputs processing job runs after agent exit, holds issues: write, and will execute any queued entries.
Expected Behavior
The safeoutputs MCP server should reject requests not originating from the authorized agent process — returning HTTP 403 or a JSON-RPC error. The safeoutputs and github entries in the MCP config should use separate, scoped tokens; the safeoutputs token should additionally be restricted by process identity or Unix socket so bash subprocesses cannot impersonate the agent as a write-sink caller.
Security Relevance
Any code executing in the runner with bash access (including a prompt-injection payload, a malicious dependency, or a compromised tool) can queue write operations directly. The safeoutputs gateway does apply content sanitization (title prefix, label injection) on queued writes, so the impact is unauthorized but sanitized GitHub writes rather than raw API access — however, the authorized-caller requirement that anchors the permission-separation model is not enforced. This is closely related to the upstream fix in #22908 (filed from gh-aw-security #1519), which should be extended to cover write-sink key separation.
gh-aw version: v0.64.4 (finding reproduced against this version; researcher comment updated to v0.65.0)
Original finding: https://github.com/githubnext/gh-aw-security/issues/1649
Generated by File Issue · ◷