Checkpoint Docs
Govern

GitHub Connections

Let users connect their GitHub account to the Checkpoint gateway for OAuth-backed tool calls

Overview

GitHub is the first upstream provider supported by the Checkpoint gateway's connections flow. Once an operator wires up a Checkpoint-side GitHub OAuth App (one-time setup, documented below), end users can click Connect GitHub on any gateway upstream card that points at a GitHub MCP server. Their next tool call through the gateway automatically injects their personal access token as GITHUB_TOKEN — no pasted PATs, no scope guessing, no credential rotation reminders.

User clicks Connect GitHub
  → github.com consent screen
  → redirect back with auth code
  → gateway exchanges code for access token
  → token encrypted at rest
  → next tool call has GITHUB_TOKEN available

"Connections" (outbound) and "delegations" (inbound) are distinct concepts. A delegation is the VC-JWT Checkpoint issues to an MCP client proving "Alice from VouchedID is calling"; see MCP-I delegations. A connection is the OAuth access token Checkpoint holds on behalf of Alice so the gateway can call an upstream provider like GitHub as her. Both involve OAuth; neither is "the" OAuth.

OAuth App vs GitHub App

GitHub offers two OAuth-capable app models and we target OAuth Apps in v1:

DimensionOAuth App (this guide)GitHub App
Token expiryNever expires (until user revokes)Short-lived + refresh token
PKCENot supportedRequired
Installation flowNone — per-user grantPer-installation, finer-grained permissions
Scope modelClassic scopes (repo, user:email)Fine-grained resource permissions
Setup effort~3 minutes~15 minutes + installation UX

Why OAuth App for v1: zero installation friction, no PKCE wiring, no token rotation code path. Users see one consent screen, approve, done. GitHub App support is v1.1+ if customers ask for it — most don't, because OAuth App scopes are already narrow enough for MCP tool-call use cases.

Trade-off: OAuth App tokens are broader (the whole repo scope instead of "these 3 repos"). For customers who need fine-grained per-repo access, GitHub Apps is the right answer and is tracked separately.

One-time operator setup

Already done in KYA's production environment — this section exists for self-hosted deployments or future re-registration.

1. Register the OAuth App on github.com

  1. Go to github.com/settings/developers

  2. Click OAuth Apps → New OAuth App

  3. Fill in:

    • Application name: Checkpoint Gateway (Use a name distinct from any other Checkpoint OAuth apps you may have registered — the name is what users see on the consent screen.)

    • Homepage URL: https://kya.vouched.id

    • Application description: Optional; recommended: Checkpoint routes MCP tool calls through authenticated gateway connections on your behalf.

    • Authorization callback URL: https://kya.vouched.id/api/v1/gateway/connections/github/callback

      Exact match required. Note the /connections/ segment — not /oauth/. The /oauth/ path is reserved for Checkpoint-as-OAuth-server (MCP-I inbound) and will never serve the GitHub callback.

  4. Click Register application

2. Copy credentials

  1. On the new OAuth App's settings page, copy the Client ID
  2. Click Generate a new client secret and copy the secret immediately (GitHub only shows it once)
  3. Treat the secret as sensitive — never commit to source control, never paste into a chat thread, never screenshot

3. Configure Vercel environment variables

Add to both Production and Preview environments in the Vercel project settings:

  • GATEWAY_GITHUB_CLIENT_ID — the Client ID from step 2
  • GATEWAY_GITHUB_CLIENT_SECRET — the client secret from step 2

The env var names are deliberately prefixed GATEWAY_ to avoid collision with any other GITHUB_OAUTH_* variables in the environment. Do not rename them back to GITHUB_OAUTH_CLIENT_ID — the codebase reads the GATEWAY_-prefixed names at request time and a rename would break the authorize route silently.

4. Trigger a redeploy

Vercel env var changes only take effect after a redeploy. Either push a no-op commit or manually redeploy the latest production build from the Vercel dashboard.

What the end user sees

  1. Dashboard → the Gateway section of your org → a GitHub-backed upstream card with "Credentials not configured"
  2. Click the card → the hybrid credential modal opens with a green primary Connect GitHub button
  3. Click Connect GitHub → browser navigates to github.com/login/oauth/authorize?…
  4. If already signed into GitHub (or into an SSO IdP GitHub trusts), the consent screen appears immediately showing the requested scopes
  5. Click Authorize Checkpoint Gateway → GitHub redirects back to https://kya.vouched.id/api/v1/gateway/connections/github/callback?code=…&state=…
  6. The gateway validates the state token, exchanges the code for an access token, encrypts the token, and stores it in user_upstream_oauth_tokens
  7. The modal updates to "Connected as alice@vouched.id via GitHub"
  8. Alice's next gateway tool call that targets the GitHub MCP upstream automatically has GITHUB_TOKEN injected into the request environment

Scopes requested

The GitHub OAuth App requests exactly two scopes:

  • repo — read/write access to public and private repos the user owns or collaborates on. This is the minimum scope the MCP GitHub server needs to actually do anything useful. Lesser scopes like public_repo leave private-repo tool calls broken.
  • user:email — read the authenticated user's email, used solely to display "Connected as alice@example.com" in the credential modal. Never used for an authentication decision.

We deliberately do not request admin:org, delete_repo, workflow, or anything broader. Scope sprawl would make users hesitant to authorize Checkpoint. If a future MCP tool genuinely needs a broader scope, that requires a reviewed PR to apps/web/lib/gateway/oauth-providers.ts with an explicit changelog entry — never a drive-by addition.

Token lifecycle

GitHub OAuth App access tokens never expire under default configuration. A token is only invalidated if:

  1. The user explicitly revokes Checkpoint via github.com/settings/applications
  2. GitHub detects suspicious activity on the user's account and revokes all OAuth grants
  3. An operator revokes the entire OAuth App from the GitHub developer console (mass-revoke for every Checkpoint user)
  4. The user clicks Disconnect in Checkpoint's credential modal — the gateway hard-deletes the row from user_upstream_oauth_tokens (no GitHub-side revoke because OAuth Apps don't expose a REST revoke endpoint; users revoke via GitHub's settings UI)

Because tokens don't expire, the gateway's lazy-refresh path skips the expiry-near-now check entirely for GitHub connections — the tokenExpiryStrategy: 'never_expires' in the provider registry signals this to the vault resolve endpoint.

Troubleshooting

Cause: The callback URL the gateway sends doesn't match the URL registered on github.com for this OAuth App.

Fix: On github.com/settings/developers → your OAuth App → Authorization callback URL, verify the URL is exactly:

https://kya.vouched.id/api/v1/gateway/connections/github/callback

Common mistakes:

  • /oauth/ instead of /connections/ (old path before the namespace rename)
  • Trailing slash
  • http:// instead of https://
  • Wrong environment host (e.g. preview URL in production OAuth App)

GitHub's matcher is strict — character-for-character equality.

503 from POST /api/v1/gateway/connections/github/authorize

Cause: GATEWAY_GITHUB_CLIENT_ID isn't set in the environment the route is running in. The authorize route reads the env var at request time and surfaces a descriptive error when it's missing.

Fix: Check Vercel env vars for both Production and Preview scopes. A common mistake is to set it for Production only, which causes preview deployments to fail with the same 503.

"Bad credentials" on tool calls after a successful Connect GitHub

Cause: The token was revoked externally — either by the user at github.com/settings/applications or by GitHub's automated fraud detection.

Fix: The user must click Disconnect in the Checkpoint credential modal, then Connect GitHub again. The gateway's vault resolve endpoint can't auto-detect revocation (GitHub doesn't push revocation events to OAuth clients), so the first failing tool call after revocation surfaces a bad credentials error to the user and the reconnect is manual.

state mismatch error in the gateway logs on callback

Cause: The CSRF state token the browser returned from GitHub doesn't match any known state in the gateway's Redis store. Either:

  • More than 10 minutes elapsed between authorize and callback (state TTL expired)
  • The user opened the authorize URL in one browser and completed the flow in another
  • Someone is attempting a CSRF attack

Fix: Have the user restart the connect flow. If it keeps happening inside the 10-minute window, check the gateway's Redis client health and the KV_REST_API_URL / KV_REST_API_TOKEN env vars.

Reference

  • GitHub OAuth Apps docs — upstream authoritative source
  • Token expiration and revocation — OAuth App token lifecycle details
  • EPIC #1600 — the full architecture for gateway credential flows
  • apps/web/lib/gateway/oauth-providers.ts — the provider registry entry, with inline comments explaining each field's rationale
  • MCP-I delegations — the complement, for the inbound half of the credential story