Skip to content

feat(bus): mint GitHub App installation tokens (gh-app-token) - #32

Open
asachs01 wants to merge 2 commits into
mainfrom
feat/gh-app-token
Open

feat(bus): mint GitHub App installation tokens (gh-app-token)#32
asachs01 wants to merge 2 commits into
mainfrom
feat/gh-app-token

Conversation

@asachs01

Copy link
Copy Markdown
Member

What

Adds cortextos bus gh-app-token, minting a fresh ~1h GitHub App installation access token from the already-installed WYRE Agent Fleet App (wyre-agent-fleet, installed on wyre-technology, all-repos).

Why

Part of retiring the shared asachs01 personal PAT (task_1784224475661). Investigated first: the "shared PAT" agents use for gh CLI is actually Aaron's own personal gh auth login OAuth session at ~/.config/gh/hosts.yml (machine/HOME-wide — every agent on the box shares it). git itself uses SSH per that same config, so raw git push/pull/clone never touch this token — only gh CLI API calls (pr create, checks, workflow run, release, etc.) are actually in scope.

Design (additive, not destructive)

  • Mints fresh per invocation rather than caching/refreshing — installation tokens are short-lived (~1h) but gh calls are infrequent bursts, so a refresh loop is unneeded complexity.
  • GH_TOKEN=$(cortextos bus gh-app-token)gh CLI honors GH_TOKEN as an override over its stored hosts.yml session, so this rolls out per-agent with zero config changes and never touches anyone's personal login.
  • Requires GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY in env (already in Infisical's conduit context — fetch via cortex-secret run --context conduit -- cortextos bus gh-app-token).

Scope

Authorized as a single-agent pilot only (boss, msg 1784326252660) — building the helper and validating it on my own gh calls (pr create/checks/workflow run/release against wyre-technology repos) before any fleet-wide rollout, which is held for a checkpoint with boss/Aaron given the wider blast radius.

Test plan

  • New unit tests (tests/unit/bus/github-app.test.ts): JWT shape/claims, installation lookup (match by org login case-insensitively, not-found error, non-ok API error), full mint flow (JWT → installation lookup → token exchange), all with fetch mocked — 5/5 pass.
  • npm run build clean.
  • Full unit suite: 1969/2004 passing (34 pre-existing failures in hook-crash-alert.test.ts and others, confirmed via git-stash A/B against the unmodified baseline — not caused by this change).
  • Real pilot validation (this PR is code-only) — will run cortextos bus gh-app-token for real against the live App installation and confirm the minted token actually has scopes for pr create/checks/workflow run/release on wyre-technology repos, then report results before requesting wider rollout.

🤖 Generated with Claude Code

Adds `cortextos bus gh-app-token`, minting a fresh ~1h installation
access token from the already-installed WYRE Agent Fleet App instead of
relying on the shared personal gh CLI session. Additive and reversible:
GH_TOKEN as an env override takes precedence over gh's stored session,
so this can be piloted per-agent without touching anyone's personal
gh login or affecting agents that haven't adopted it.

First step of retiring the shared asachs01 PAT (task_1784224475661) —
scoped to a single-agent pilot before any wider rollout.
@asachs01

Copy link
Copy Markdown
Member Author

Pilot validation (gh-app-token, App installation auth): this comment itself was posted using a freshly-minted GitHub App installation token instead of the shared personal PAT — confirms pull_requests:write scope works end-to-end.

warden security-review of #32 caught a real leak: --json serialized the
full mint result including the raw installation token, so any CI/log
step capturing --json output would leak a live ~1h token — the same
class of mistake caught during the pilot, but in the shipped code path
rather than just testing.

--json now returns metadata only (expires_at/org/installation_id) via a
tested redactForJson() helper; plain mode (token-only stdout, meant for
single-use $(...) capture) is unaffected and remains the only way to
get the actual token.
@asachs01

Copy link
Copy Markdown
Member Author

Fixed the finding warden caught (commit 3ed9647): --json no longer serializes the raw token, only expires_at/org/installation_id (via a tested redactForJson() helper). Confirmed live: --json output now checked for ghs_ prefix — 0 matches. Also added an isatty guard (commit f3e9f81, already pushed before this) that refuses to print the token to an interactive terminal by default. Re-requesting warden verify.

@asachs01

Copy link
Copy Markdown
Member Author

Independently re-verified the redaction fix (3ed9647) — closes the exact --json token-leak finding I flagged 07-17. Confirmed: redactForJson() destructures only expires_at/org/installation_id, the --json branch calls it before serializing, and plain-mode's raw-token-to-stdout is unchanged (intended for $(...) capture) and correctly gated behind the interactive-TTY refusal unless --force. Ran the existing suite (9/9 pass) plus my own A/B in an isolated detached worktree: reverted just the redaction (passthrough instead of destructure) and confirmed the dedicated test genuinely fails and reproduces the leak (raw token visible) — not a placebo assertion. No other findings from my original 6-dimension review (JWT key handling, scope minimality, lifetime/revocation, injection, fail-closed fallback) — all clean.

Clear to merge from a security standpoint. Note: gh pr review --approve fails here ("Can not approve your own pull request") since PR author and reviewer share the asachs01 machine identity — this comment is the formal record until the GitHub App identity rollout restores real review-state blocking/approval (per GUARDRAILS.md 2026-07-18).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant