MCP 2b-3: GitHub App credential backend#31
Merged
Merged
Conversation
Third slice of #17's [2b] items: - src/app_token.rs: AppTokenProvider — RS256 JWT (60s backdate, 9min validity) signed with the App private key, exchanged for an installation token; cached and lazily refreshed 5 min before expiry. Installation id explicit or discovered from owner (org, then user) on first mint. - [mcp.github_app] config: app_id + private_key (secret-ref resolved, \n-escape normalized) + installation_id/owner. When present, the MCP path injects installation tokens; the PAT pool remains for REST/GraphQL and as the MCP fallback. - Session pins now carry the exact credential (PinnedCred::Pat by identity / ::App by token value + expiry). Sessions cannot outlive their credential: an expired App-token pin terminates the session (404 → client re-initializes) — completing the session-credential lifetime half of the [2b] binding gate. Provider refresh does not disturb in-flight sessions (pinned token stays valid upstream until its own expiry). - e2e: optional App-backend section (runs when APP_ID/APP_PRIVATE_KEY are present — CI passes the repo secrets): boots a second ghpool in App mode, asserts mint + pin-to-github-app + issue_read. - New dep: jsonwebtoken 9.3 (RS256). 67 tests (8 new: JWT shape/PEM validation/caching+refresh via mock GitHub API; App-mode pinning, pinned-token reuse, expiry termination via mock MCP upstream).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third slice of #17's
[2b]gate items: the GitHub App credential backend — the Revision 2 architecture's recommended credential for the MCP path, resolving the PAT-pooling compliance concern. Closes gate items [2b] GitHub App credential backend and the session-credential-lifetime half of [2b] Session-to-agent binding.Design
src/app_token.rs(new):AppTokenProvider— RS256 JWT (60s clock-skew backdate, 9-minute validity) signed with the App private key →POST /app/installations/{id}/access_tokens. Tokens cached and lazily refreshed 5 minutes before expiry. Installation id set explicitly or discovered fromowner(org endpoint, then user) on first mint.[mcp.github_app]config:app_id,private_key(secret-ref resolved;\n-escapes normalized for env/JSON sources),installation_id/owner. When present, the MCP path injects installation tokens; the PAT pool is untouched for REST/GraphQL and remains the MCP fallback when the section is absent.PinnedCred::Patby identity /::Appby token value + expiry). An expired App-token pin terminates the session (404 → client re-initializes per MCP spec). A provider refresh does not disturb in-flight sessions — the pinned token remains valid upstream until its own expiry, so sessions drain naturally across rotations (conservative choice given MCP proxy Phase 0: compliance & compatibility spike (hosted endpoint contract, App tokens) #22's untested token-switch-mid-session behavior).[via github-app]([agent=… via github-app]in agent mode).jsonwebtoken 9.3(pinned minor; RS256). Test PEM intestdata/is a throwaway generated for JWT-shape tests — not a credential.Tested
Bearer ghs_mock_token, pins by value, reuses the pinned token on follow-ups, and terminates expired-credential sessions (404, pin removed, upstream never called). Clippy-D warningsclean.[mcp.github_app](repo'sAPP_ID/APP_PRIVATE_KEYsecrets), and ghpool signed a real JWT, discovered the openabdev installation, minted a real installation token, and servedissue_readvia the hosted MCP server — 18/18 checks (13 PAT-mode + 5 App-mode). Section skips cleanly when App credentials are absent (verified locally).Remaining 2b
2b-4 durable fail-closed audit → 2b-5 enable writes (+ pin required App permission set docs).