Gatekeeper stands at the GitHub gate. When an automated agent needs to act on a repository, Gatekeeper mints a short-lived, role-scoped GitHub App installation token narrowed to exactly what that role is allowed to do — and nothing more.
It ships three generic roles out of the box:
| Role | Can do | Cannot do |
|---|---|---|
builder |
Push feature branches, open/update PRs | Merge, push to the default branch |
reviewer |
Submit PR reviews (approve / request changes), comment | Push code, merge |
merger |
Merge PRs, push to the default branch | Open PRs, author feature work |
The roles are generic. Gatekeeper does not know or care what agents you run. You map your own agents to roles in your own configuration. Gatekeeper's only job is: given a role, return a token scoped to that role's permissions.
GitHub forbids an actor from approving its own pull request. A workflow where one identity builds, reviews, and merges therefore cannot produce a credible, auditable "built → reviewed → merged by separate actors" trail. Gatekeeper solves this by minting distinct, role-narrowed tokens from distinct GitHub Apps, so every PR visibly flows through three independent automated actors.
The App private keys never touch the agent. Gatekeeper reads them from a pluggable secret broker (OpenBao by default), signs the App JWT server-side, and hands the agent only a ≤1-hour installation token narrowed to its role.
- It is not a dispatcher, a queue, or an agent framework. It mints tokens. That is the whole surface.
- It is not coupled to any specific set of agents. Agent→role mapping lives in the consumer, not here.
- It does not store long-lived secrets. The broker does.
# Mint a token for the builder role, scoped to one repo.
gatekeeper mint --role builder --repo owner/name
# Returns a short-lived installation token on stdout.A consumer (e.g. an agent dispatcher) calls gatekeeper mint --role <role> with the role mapped to its agent, then uses the returned token for the git/API operations that role permits.
Copy config.example.yaml to config.yaml and fill in your values. All deployment-specific values — org name, broker endpoint, broker secret paths, role→app bindings — live there. No hardcoded org names, hostnames, paths, or identities exist in the code.
github:
owner: your-org-name
api_base: https://api.github.com
broker:
type: openbao # openbao | vault | env | file
endpoint: https://broker.example.com
auth: approle # approle | token
roles:
builder:
app_id_path: secret/gatekeeper/builder/app-id
installation_id_path: secret/gatekeeper/builder/installation-id
private_key_path: secret/gatekeeper/builder/private-key
# ... reviewer, mergerSee config.example.yaml for the full reference.
Registering a GitHub App requires a one-time manual step — Gatekeeper cannot script first-time App creation.
- Register three GitHub Apps on your org: one each for
builder,reviewer,merger, with the per-role permissions indocs/ROLES.md. - Install each App on the target repos.
- Store each App's
app-id,installation-id, andprivate-keyin your broker at the paths yourconfig.yamlpoints to. - Apply a branch ruleset (see
docs/GOVERNANCE.md) that requires PR + review and restricts who may push the default branch.
After that, everything is code.
Gatekeeper's broker is pluggable:
| Type | Use case | Credentials from |
|---|---|---|
openbao |
Production | BROKER_ROLE_ID + BROKER_SECRET_ID (AppRole) or BROKER_TOKEN |
vault |
Production (Vault) | Same env vars |
env |
Local dev / CI | Env var name is the secret path |
file |
Local dev / CI | File path is the secret path |
The private key is read server-side only, used to sign the App JWT, and never returned, logged, or persisted.
docs/ROLES.md— per-role GitHub App permission tablesdocs/GOVERNANCE.md— branch ruleset and CODEOWNERS referencedocs/DESIGN.md— module architecture and security invariants
go build ./cmd/gatekeeperRequires Go 1.22+. No external dependencies beyond the standard library.
FSL-1.1-MIT. See LICENSE.
