Skip to content

MCP proxy Phase 2 gate: agent authn + default-deny policy + GitHub App backend + writes #17

Description

@chaodu-agent

Part of #15 (see Revision 2 of the RFC — this issue's scope was significantly expanded by the community review). Depends on Phase 1 (#16, shipped in #20) and the Phase 0 compatibility spike (#22key findings incorporated below).

Phase 0 spike results that shape this design (details in #22 findings):

  1. App installation tokens work against the hosted endpoint — the credential backend below is verified viable.
  2. X-MCP-Tools (undocumented, verified) is an exact per-tool allowlist — use it, not X-MCP-Toolsets categories.
  3. X-MCP-Toolsets fails open on invalid names (typo → full tool surface) — never rely on it for enforcement.
  4. Policy headers are honored per-request — ghpool must inject on every request and strip all client X-MCP-* (a client header would widen access mid-session).
  5. Upstream DELETE is a no-op (session remains usable) — ghpool's session cache is the sole session/revocation authority.
  6. Tool surface varies with App permissions (41 vs 44 tools App vs PAT) — the required App permission set must be documented and pinned.

Goal

Introduce agent identity and default-deny per-agent policy, unlocking write access through the MCP proxy. This phase is the security gate: no write tool is exposed until every item below ships.

Delivery plan: two slices (decision 2026-07-12)

Phase 2 as a monolith is ~10x Phase 1's complexity. It ships in two independently valuable slices:

Slice 2a — agent authn + policy, still read-only. Per-agent X-Ghpool-Key, default-deny tool allowlist enforced at the proxy + injected X-MCP-Tools upstream, tools/list pruning, per-agent audit attribution, TLS guidance. Upstream stays /readonly and the PAT pool stays. Immediate value: least privilege and per-agent attribution on reads; no fail-closed audit backend needed yet.

Slice 2b — writes. GitHub App credential backend (mint/refresh installation tokens), repo allowlists + resource resolution (deny-if-unresolvable), session-to-agent binding + revocation, durable fail-closed audit, write classification, safe-retry rules. Only after 2b is complete does any write tool go live. v0.4.0 releases when 2b closes.

Design updates from community review (adopted 2026-07-13):

  1. 2b-5 credential model: per-envelope scoped installation tokens — mint with the API's repositories+permissions parameters so the token is already narrowed to the agent's allowlist; GitHub becomes the authoritative resource boundary, ghpool's argument parser is defense-in-depth. One credential per policy envelope.
  2. Writes require the App backend, enforced in code — write path stays 403 without [mcp.github_app]; PATs never serve writes.
  3. 2b-4 audit records tool-level outcomesisError arrives inside HTTP 200/SSE, so write-call responses are buffered (bounded) and parsed before forwarding; HTTP status alone is not a success signal.
  4. 2b-5 adds a simple per-agent in-flight cap (full quotas remain Phase 3).
  5. Revocation semantics documented: config change = restart = all session pins cleared (in-memory), until hot-reload exists.

Checklist items below are tagged [2a]/[2b].

Gate checklist (from RFC Revision 2 — ALL required before writes)

Policy config sketch

[[mcp.agents]]
id = "openab-bot"
key = "aws:secretsmanager:ghpool/mcp-keys:openab"   # or env: / k8s:
tools = ["issue_read", "create_issue", "add_issue_comment"]
repos = ["openabdev/ghpool", "openabdev/openab"]
readonly = false

Also fold in from Phase 1's accepted limitations

  • allowed_owners-equivalent enforcement on the MCP path (superseded by the per-agent repos allowlist above)
  • Client config example: { "url": "https://ghpool:8080/mcp", "headers": { "X-Ghpool-Key": "${GHPOOL_KEY}" } }

Open questions

  • Audit backend choice for fail-closed writes (local WAL + ship vs. direct to CloudWatch/S3)
  • Identity binding: verify key per request (stateless) vs. bind at session init alongside the pin (RFC leans session binding — required by the session-to-agent gate anyway)
  • Tool→repo argument mapping table maintenance (deny-if-unresolvable covers unknown tools)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions