Skip to content

Permission Sets

github/token (see Reference: API) mints a token scoped to whatever the caller asks for in the request — that's fine when the caller is trusted with a broad grant, but it means anyone who can reach that path can request any scope the App itself holds. A permission set moves that decision to the operator: it stores a fixed token request under a name, and a caller mints against the name instead of specifying its own scope.

Creating a permission set

bao write github/permissionset/ci-release \
    org_name=my-org \
    repositories=my-repo \
    permissions=contents=write,pull_requests=write

Or by installation ID directly, which avoids the extra GitHub round trip to resolve an organization name:

bao write github/permissionset/ci-release \
    installation_id=87654321 \
    repositories=my-repo \
    permissions=contents=write,pull_requests=write

Either installation_id or org_name is required; if both are given, installation_id takes precedence. The same repositories, repository_ids, and permissions fields from github/token apply here — they're stored, not requested.

Minting a token from the set takes no scope arguments at all:

bao read github/token/ci-release

The response is identical in shape to a direct github/token call, using the stored request.

repositories is scoped within an installation, not across it

A permission set (like a direct token request) resolves to exactly one GitHub App installation — either the installation_id you gave it, or the one that org_name resolves to. repositories and repository_ids then narrow the token to a subset of repositories within that installation.

This means repositories cannot be used to reach across organizations or accounts: an installation belongs to one org/user, and every repository name or ID in the set must belong to that same installation. If you need to grant access to repositories under a different owner, you change the permission set's installation_id (or org_name) — a different installation, not a longer repository list on the same one. Two owners means two permission sets, each pointing at its own installation.

Managing permission sets

bao read github/permissionset/ci-release      # inspect the stored request
bao list github/permissionsets                # list every stored permission set name
bao delete github/permissionset/ci-release     # remove it

CREATE and UPDATE on github/permissionset/<name> behave identically — writing to a name that already exists overwrites the stored token request in place.

Why bother, when github/token can do the same thing

A permission set exists so the shape of a token — which installation, which repositories, which permissions — is fixed by whoever manages the OpenBao policy, not by whoever holds a token to call the API. Combined with an OpenBao policy that grants a caller access to github/token/ci-release but not the bare github/token path, a caller can mint tokens all day and never be able to ask for anything beyond what that name was built to grant. See Security for the full argument for denying the bare path by policy.