A personal GitHub App-based credential broker: can replace long-lived PATs
and SSH keys with short-lived, auto-refreshing GitHub App user access
tokens, for GitHub repositories where the App is installed, usable by both
git (via a credential helper) and gh (via GH_TOKEN).
github-credentials uses GitHub App user access tokens as the authentication mechanism for interactive Git over HTTPS, via Git's standard credential-helper protocol — an alternative to personal access tokens and SSH keys for supported repositories.
Identity stays the authenticated user — the App is an authorization boundary, not a replacement identity. Effective permissions are the intersection of: your own permissions on a repo, the App's granted permissions, and the installation's repository scope. It does not replace SSH keys or PATs for repositories the App isn't installed on, organizations that don't allow it, or non-GitHub services.
See docs/adr/ for the architectural decisions behind this
design — why GitHub App user tokens instead of PATs/SSH, why device flow,
why a git credential helper, the authorization-boundary security model,
and why no client secret is required. For the investigation and
experiments that led to these decisions, see
docs/history/investigation-log.md.
device flow login (one-time, browser)
│
▼
refresh_token ──refresh (no secret needed)──▶ access_token (~8h)
│ │
▼ ▼
stored locally with used by git (credential
restrictive file perms helper) and gh (GH_TOKEN)
Official release/Chocolatey binaries already know which GitHub App to authenticate against — ADR-0017 — so setup is just:
- Install the
cpm-github-credentialsGitHub App on the account/repos you want to access:https://github.com/apps/cpm-github-credentials/installations/new(prefer "Only select repositories" over "All repositories"). - Download/install the binary (see Distribution below).
token-broker login— no Client ID configuration needed.
For forks, development, or if you don't want to depend on the official
App. Go to https://github.com/settings/apps/new and configure:
| Setting | Value |
|---|---|
| Callback URL | blank (not used) |
| Expire user authorization tokens | ✅ enabled (required for refresh tokens) |
| Enable Device Flow | ✅ enabled |
| Webhook → Active | ❌ disabled |
| Repository permissions → Contents | Read and write |
| Repository permissions → Issues/Pull requests/Actions | add as needed |
| Where can this be installed | Only on this account, or public if you need to install it on organizations you own — see note below |
Or use manifest.json via the
App Manifest flow.
Note on installing to organizations: an owner-only-installable GitHub
App (public: false) can only be installed on the account that owns it.
To install on an organization you own, the App must be switched to
publicly installable first (Settings → General → "Make this GitHub App
public"). This does not submit it to the Marketplace or make it
discoverable — it only lifts the owner-account-only install restriction.
Do not generate/keep a Client Secret unless you disable "Expire user
authorization tokens" — no client secret is required for GitHub App user
tokens obtained through device flow; refresh uses client_id +
refresh_token only.
Install the App (Settings → "Install App" tab → choose account/org →
select specific repositories), build (go build -o token-broker.exe ./cmd/token-broker), then log in with your App's Client ID:
./token-broker.exe login --client-id <your-client-id>Or set it once via $env:TOKEN_BROKER_CLIENT_ID / setx TOKEN_BROKER_CLIENT_ID <id> to avoid passing it every time — it takes
priority over the built-in official-App default either way.
%APPDATA%\github-token-broker\tokens.json, restrictive file permissions
(0600 in Go; on Windows this is an approximation of Unix semantics,
actual protection comes from the file's NTFS ACL, which this project
does not explicitly configure beyond Go's default).
- Releases: prebuilt binaries for Windows/Linux/macOS — Releases page
- Chocolatey (Windows): see chocolatey-packages
See ADR-0010/ADR-0011/ADR-0012 for the release/distribution model.
git config --global credential.https://github.com.helper `
"!'C:/path/to/token-broker.exe' credential-helper"git clone/push/pull over HTTPS now authenticate automatically,
refreshing the token as needed.
$env:GH_TOKEN = & .\token-broker.exe token
gh repo view owner/repo
Remove-Item Env:GH_TOKENDo not set GH_TOKEN globally/in a shell profile — it overrides gh's own
authentication for every invocation in that shell, and this token is
scoped to whatever permissions the GitHub App was granted, not full gh
scope (e.g. no Actions/Secrets access unless the App was explicitly
granted those).
Tracked as a compatibility surface under ADR-0009.
--help/-hon any command, ortoken-broker version/--version, always exits0.- Exit codes:
0success,1runtime failure (login/refresh/persistence/ helper-protocol),2usage error (unknown command, missing required flag, bad arguments). - stdout carries only machine-readable output — a token
(
token/credential-helper get) or nothing at all (login/store/eraseon success). All human-facing prompts and diagnostics go to stderr. credential-helper getonly ever emits a token forprotocol=https/host=github.com; any other target produces no output and exits0(git's convention for "no credential available"), regardless of how the credential helper is configured ingit config.
Run token-broker --help or token-broker <command> --help for full
per-command usage.
tokens.jsonand any downloaded.pemare gitignored — never commit them.- Access tokens: ~8h lifetime. Refresh tokens: ~6 months, rotate on each use.
- No Client Secret is required or stored by this design.
- Revoke access anytime by uninstalling the App from an account/org, or deleting the App entirely. This prevents further token refresh and invalidates access granted through the App; whether already-issued access tokens stop working immediately is enforced by GitHub, not this project — in practice moot given their ~8h lifetime.
Apache 2.0 — see LICENSE.