Credential Management¶
VERGIL agents authenticate as GitHub Apps. There are no stored
tokens, no classic PATs, and no gh auth account juggling in the
agent path. The tooling holds an App's private key, mints a
short-lived installation token on demand, and discards it. Each
operation runs under the App identity whose permission shape bounds
what it can do.
For App registration and initial setup, see Account Setup. For the identity model that credentials serve, see Identity Architecture.
The credential is the App¶
An agent's only credential is its GitHub App private key, recorded
in identities.toml as private_key_path. From that key the
tooling derives everything else at runtime:
- No static tokens. Installation tokens live ~1 hour and are minted per operation. There is nothing long-lived to leak or rotate beyond the private key itself.
- No
gh authdependency in the agent path. The agent does not rely on a logged-in account or a-vergilsuffix. The App ID and private key are injected into the agent VM by provisioning. - Permission shape is the boundary. The token a given App mints can only do what that App's declared permissions allow. The user App's token cannot write PRs; the audit App's token cannot write code. See Identity Architecture for the inverted shapes.
How a token is minted¶
The implementation lives in src/vergil_tooling/lib/github.py. The
flow is:
- Read the App config.
VRG_APP_IDandVRG_PRIVATE_KEY_PATHidentify the App and locate its private key. In an agent VM these come from the identity'sapp_idandprivate_key_pathinidentities.toml. - Generate a JWT. A short-lived JWT is signed with the private
key, using the App ID as the
issclaim (iatbackdated 60s,exp10 minutes out). - Resolve the installation. The JWT authenticates a call to
GET /app/installations; the tooling matches the installation for the target org. - Mint an installation token. The matched installation yields a
~1-hour token, which is injected as
GH_TOKENinto theghsubprocess environment for that operation.
The token is never written to disk and never persisted across operations. Parallel sessions each mint their own token independently — no shared state, no lock contention.
App ID, not Client ID — for now
The tooling authenticates using the numeric App ID today (it
becomes the JWT iss claim via VRG_APP_ID). GitHub is
gradually migrating GitHub App authentication toward the Client
ID, but that migration is only partially rolled out. Record the
Client ID during setup so the eventual switch needs no return
trip to the GitHub UI, but the App ID is what the tooling uses
until the migration lands.
Credential selection¶
The tool selects the credential, not the caller¶
There is no flag, no env var override, no way for the caller to
choose a different identity. Which App a VM authenticates as is
fixed by provisioning (VRG_APP_ID / VRG_PRIVATE_KEY_PATH for that
VM). The user VM is the user App; the audit VM is the audit App.
The permission shape decides what succeeds¶
Because each App's token is bounded by its declared permissions, the identity boundary is enforced server-side, not by the wrapper:
- The user App can push code and read PRs, but cannot open,
comment on, approve, or merge PRs — its token holds
pull_requests: read. - The audit App can write PR reviews, but cannot write code or
merge — its token holds
contents: read, and merging through the API requirescontents: write.
The vrg-gh/vrg-git wrappers add a soft ergonomic layer (clear
errors, allowlists) on top of this hard gate, but the App permission
shape is the real boundary.
Graceful degradation¶
If App config is absent (no VRG_APP_ID / VRG_PRIVATE_KEY_PATH),
the token-minting path returns nothing and the gh subprocess
inherits the parent environment. This lets the tooling run in CI
environments where credentials are provided through other mechanisms
(for example, an Actions-provided token).
Security model¶
Credential security does not rest on token scope. Defense in depth, in priority order:
- App permission shape — the installation token can only perform what the App declares. This is the primary, server-side boundary. A user-App token physically cannot merge a PR.
- Branch protection — rulesets prevent merging without review, prevent direct pushes to protected branches, and require CI to pass.
vrg-gh/vrg-gitwrappers — gate which operations an agent may attempt and surface clear errors. A soft ergonomic layer, bypassable by root, not the security boundary.- Short-lived tokens — ~1-hour installation tokens minted on demand, with no long-lived secret beyond the private key.
Multi-org support¶
A single App installed on multiple accounts works across every org the contributor operates in. Installation tokens are minted per-org from the same private key, so adding a new org requires only installing the existing App on it — no new keys, no credential reconfiguration.
Related¶
- Account Setup — registering the Apps,
generating private keys, and configuring
identities.toml - Identity Architecture — the agent-App model and naming conventions
- Permission Model — enforcement layers beyond credential selection
- Credential management design spec — full decision rationale and alternatives considered