Skip to content

Repository files navigation

clagentic:gatekeeper

Role-scoped GitHub App tokens. Built for builders.

clagentic.ai License: FSL-1.1-MIT Go 1.22+ Support on Ko-fi

Role-scoped GitHub App installation tokens for automated agents. Part of the clagentic suite.


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 four 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
security Post security review comments, request changes Push code, merge

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.

You can also define custom roles in config.yaml without forking code — for example a releaser scoped only to tagging, or a deployer with deployment write access. See docs/ROLES.md under "Adding a custom role" for the config schema.

Why it exists

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 separate build, review, and merge actors. An optional security role can add an independent security review gate on top of that core trail.

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.

What this is NOT

  • 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.

Composes with

Gatekeeper serves any consumer that can call its mint path — nothing here imports or assumes a particular caller. clagentic: loadout is the reference consumer: its TokenProvider seam treats Gatekeeper as one interchangeable minting provider among several (a static token, a bring-your-own minting command, or no provider at all each work equally well there). Neither project imports the other.

Concretely, the seam works like this: Gatekeeper mints a scoped, short-lived GitHub App installation token for a role (see "Usage" below); a consumer calls gatekeeper mint --role <role> and uses the returned token for the git/API operations that role permits. --json mode additionally returns the broker-verified App slug alongside the token (see "Structured output" below and docs/ROLES.md) so a consumer that needs to know which App/bot identity a role maps to can adopt that value instead of separately declaring it.

This is optional in both directions. A Gatekeeper user who never touches loadout loses nothing — Gatekeeper's entire contract is the CLI/JSON output documented in this README, usable by any caller that can shell out or read JSON. A loadout user who never touches Gatekeeper loses nothing either — the Forgejo path works fully standalone with a static token, and GitHub's App-token path accepts any TokenProvider implementation, not only Gatekeeper's.

Attestation substrate for agent-to-agent (A2A) callers

Gatekeeper's attestation layer (internal/attestation) resolves who is asking before anything is minted (see docs/SETUP.md). Two additions extend that substrate for a remote-facing, agent-to-agent caller — a caller whose minted credential crosses a trust boundary to a peer, rather than being used purely locally:

  • Structured sidecar records (attestation.sidecars[].identity_field): a sidecar entry can opt into parsing its file as a structured (JSON or YAML) record and reading a named field as the attested identity, instead of treating the whole file as the identity string. The rest of the record — a parent-session id, a spawn id, a generic caller-type classification, a spawn timestamp — is captured for cross-attribution and audit whenever present. A structured record that is present but malformed (unparseable, or missing/empty/non-string in the named field) is a hard, fail-closed error naming the field — never treated as "no identity."
  • Domain-aware fail-closed MISS: for a remote-facing (A2A) mint request, a per-spawn attestation miss now refuses outright rather than falling through to a session-scoped identity — closing a confused-deputy path where a spawn with no attestation of its own would otherwise mint a peer-facing credential under its parent's (higher-trust) identity. Local GitHub/reader mints are unaffected: a per-spawn miss still falls through to the session sidecar exactly as before, since a long-lived lead session legitimately has no per-spawn sidecar of its own.

What this repository ships today: the attestation substrate above — structured-record parsing, the attribution fields it carries, and the domain-aware resolution policy (internal/attestation.DomainResolver). What it does NOT yet ship: an actual A2A token-minting command in gatekeeper itself. That mint path is a separate, gated epic; this substrate is what it will consume once it lands. See docs/SETUP.md for the published required-fields contract a sidecar producer implements, and docs/SIDECAR-READ-CONTRACT.md for the generalized, tool-agnostic read-contract sections this substrate follows.

Usage

# 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.

Structured output: inheriting the verified App slug

When a role has the App-slug verification gate configured (app_slug + app_slug_path, see docs/ROLES.md), Gatekeeper already reads the App's actual slug from the broker and checks it against the configured expectation before minting. --json surfaces that verified value instead of discarding it:

gatekeeper mint --role builder --json
# {"token":"ghs_...","expires_at":"2026-01-01T00:00:00Z","app_slug":"your-builder-app-slug"}

app_slug is the broker-verified identity, not a copy of the configured expectation — a consumer that needs to know which App/bot identity a role maps to (for example, to attribute a commit or a review to the right bot login) can inherit this value instead of separately declaring it in its own configuration. Optional, additive, backward compatible: the default gatekeeper mint invocation (no --json) is unchanged, and a role with no App-slug binding configured simply omits app_slug from the JSON object. Gatekeeper does not require any particular consumer to exist — this is a plain, documented CLI output shape any tool can read, or ignore.

Configuration

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, merger

See config.example.yaml for the full reference.

One-time setup (per installer, manual)

Registering a GitHub App requires a one-time manual step — Gatekeeper cannot script first-time App creation.

  1. Register four GitHub Apps on your org: one each for builder, reviewer, merger, and security, with the per-role permissions in docs/ROLES.md.
  2. Install each App on the target repos.
  3. Store each App's app-id, installation-id, and private-key in your broker at the paths your config.yaml points to.
  4. 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.

Secret broker

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

  • docs/SETUP.md — attested identity, the fail-closed trust model, and configuring your own attestation source
  • docs/ROLES.md — per-role GitHub App permission tables
  • docs/GOVERNANCE.md — branch ruleset and CODEOWNERS reference
  • docs/DESIGN.md — module architecture and security invariants

Build

go build ./cmd/gatekeeper

Requires Go 1.22+. No external dependencies beyond the standard library.

Install

make install

Builds the gatekeeper binary and installs it to /usr/local/bin by default. Override the destination with PREFIX and/or DESTDIR — no paths are hardcoded to any specific environment:

# Install under your home directory instead of /usr/local
PREFIX=$HOME/.local make install

# Stage into a packaging root without touching the live prefix
make install DESTDIR=/tmp/staging PREFIX=/usr

Support

If clagentic:gatekeeper is useful to you: ko-fi.com/clagentic

Disclaimer

Not affiliated with Anthropic or OpenAI. Claude is a trademark of Anthropic. Codex is a trademark of OpenAI. Provided "as is" without warranty. Users are responsible for complying with their AI provider's terms of service.

License

FSL-1.1-MIT — Functional Source License 1.1, with MIT as the Change License.

Free for personal, internal-business, evaluation, research, and non-commercial use. Not free for offering this tool (or a substantial fork) as a competing commercial product. Each release auto-converts to MIT on its second anniversary.

About

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.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages