cloud api: GitHub token endpoint with a sealed per-principal ledger, revoke and audit
Leo, 2026-10-24, on the "cmux lanes" GitHub App ask: "can this be productionized?" This is PR 1 of the plan in leo-workspace research/2026-10-24-github-app-broker-product.md: the Cloud API's GitHub token endpoint, coded exactly to the gh-app-product contract that PR 2 (lane-gh-broker minter) and PR 3 (cmux github credential / cmux github gh) build against.
Endpoints (backend/apps/api/src/integrations/github-token-route.ts, routed in index.ts)
POST /v1/integrations/github/token {team, repo, permissions, label?}→{token, expires_at, repo, permissions, token_id}. Callers: avminstall for its own live machine (vmGate's checks through CloudDOcloud.vm.self.get), adaemon/cli/macinstall, or a member's session (sameselectTeam+ssoGate+principalForOwnerpath as/v1). Refused:agentclaims,team-vm,ios,web. Permission keys arecontents, pull_requests, issues, workflows, checks, actions, statuses, metadata, valuesread|write, non-empty (an empty map would mean "everything" to GitHub). Errors as the contract lists (400request.invalid, 401auth.required, 403auth.forbidden/policy.denied, 404integration.not_found, 409needs_reauth, 429rate_limited+retry_after, 503integration.unavailable).POST /v1/integrations/github/token/revoke {team, install?}→{revoked: N}; another install's tokens needteam.manage(the personal team's own user counts).GET /v1/integrations/github/status?team=→{configured, app_slug, connection: {id, account, status, repos, allow_workflows} | null}.
Mint (integrations/github-tokens.ts, in the team's ConnectionDO): picks an active GitHub connection the principal may use whose account is the repo's owner and whose repo scope + allowlist include the repo (githubRepoAllowed), then asks GitHub for {repositories: [name], permissions} exactly. workflows: write also needs the new team policy key github.allowWorkflows (default false; slice field allow_workflows). A GitHub answer that grants less than asked is revoked and refused, never handed out downgraded. GitHub 404/suspended → 409 and the connection goes needs_reauth; 422/403 → policy.denied; rate limits → 429.
Ledger = cache: each live token is a ConnectionDO row sealed under INTEGRATIONS_KEK with AAD over {team, principal, install, repo, permissions, token_id, expires_at}; reused for the same (principal, installation, repo, permissions) while >15 min remain, never across principals; dropped at expiry. Install principals register with their UserDO's socket registry, so install.revoke (and a machine delete, which revokes the VM install) calls ConnectionDO.closeInstall, which revokes that install's tokens with DELETE /installation/token; failed DELETEs retry from the alarm until expiry. New mints are rate limited per principal (burst 60, 1/s, memory).
Audit: each mint commits integration.github.token_minted in ConnectionDO, whose outbox appends team.github.token_minted to the team's audit chain (team.audit.list): {at, team, principal_kind, user, install, machine?, label?, repo, permissions, token_id, expires_at}. Never the token; a test checks the ledger, audit and errors for the token text.
github.issue.comment keeps its whole-installation token path unchanged (its in-memory cache never serves callers). Generated files (catalog, clients/ts/cloud/src/generated.ts, cmux-cloud-wire generated) change only because of the new policy key. No client code, setup-github or GitHub App configuration is touched.
Risk: medium. New routes and a new ConnectionDO table; existing integration ops untouched except the TeamIntegrationPolicy.github.allow_workflows optional field (the slice adds it only when on, so existing teams' slice hashes do not change). Overlap check: #16776, #24846, #24849 touch the same files for other features; none mints tokens.
Tests: backend/apps/api/test/github-token.test.ts (narrowed body checked, per-principal reuse, policy/owner/allowlist/workflows refusals, 404 → needs_reauth, 429, revoke route, install revoke → DELETE, VM own-team only and machine delete → DELETE, ios refused, status, audit).
Review: independent Codex (gpt-6-astra, xhigh) SHIP at 58800f49bcb8d891b6f628030180ac42a13afee8 after two CHANGES rounds (mint/revoke race, post-await rechecks, durable refuse, unopenable rows kept).
🤖 Generated with Claude Code
Agent session: cosmic-gray
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.