Tokens and permissions
Scope credentials to the capability that uses them.
Every token-bearing surface in Goobers — the repo token, a per-capability
credentials: grant, daemonIdentity, a webhook secret — is a
reference, never an inline value: token.env, token.file,
token.keychain, or token.store. Capability admission only holds
as a real security boundary if the underlying tokens are actually scoped narrowly. A
single all-powerful PAT reused for every capability grant makes that boundary a formality.
Prefer GitHub's fine-grained personal access tokens over classic PATs. Classic PATs grant access to every repo the user can see under a coarse scope; a fine-grained PAT is scoped to specific repositories and specific permissions.
One token ref per capability, not one token for everything
Mint a separate fine-grained PAT per capability class your gaggle's workflows actually
use, and register each as its own named ref in instance.yaml:
| Capability | Recommended fine-grained permissions | Notes |
|---|---|---|
github:issues:read | Issues: Read-only | Backlog polling, triage stages. |
github:issues:write | Issues: Read and write | Create, claim, comment, ordinary-label, close. |
github:milestones:write | Issues: Read and write | Assign an existing milestone to an issue — keep roadmap mutation out of stages that only perform ordinary issue writes. |
github:issues:approve | Issues: Read and write | Applies the goobers:approved trust decision. Keep out of workflow stages unless self-approval is intentional. |
provider:pr:write | Pull requests: Read and write, Commit statuses: Read-only, Actions: Read |
Provider-neutral pull-request stages such as ci-poll. GitHub's
fine-grained PAT UI has no separate Checks permission, so a private repo whose CI is
entirely GitHub Actions-based is otherwise unreadable on this token type — also
granting Actions: Read lets CI-status checks fall back to
GET /actions/runs on that specific 403.
|
github:pr:write | Pull requests: Read and write, Contents: Read and write | GitHub-specific stages that open or update PRs. |
github:pr:review | Pull requests: Read and write |
Submit native approve/request-changes reviews. Source this from a
different GitHub identity than github:pr:write for
goober-authored PRs — GitHub forbids self-approval.
|
repo:push | Contents: Read and write | Branch + commit + push. The broadest local-tier grant; scope it to the exact target repo(s), never an org-wide token. |
repo:clone | Contents: Read-only | Curation and analysis stages that never push. |
configrepo:read | Contents: Read-only | Runner-only access to the workflow-config repo — configure only through workflowSource.token; stages cannot declare or source it through credentials. |
agent:model | Stored Copilot CLI sign-in, or (Account permissions) Copilot Requests: Read-only for headless use |
Copilot model authentication for agentic stages. An existing per-user CLI sign-in is
the local default; a configured PAT is injected as COPILOT_GITHUB_TOKEN.
|
repos:
- provider: github
owner: your-org
name: your-repo
token:
env: GOOBERS_GITHUB_REPO_TOKEN # Contents: read-only
credentials:
- capability: github:issues:write
token:
env: GOOBERS_GITHUB_ISSUES_TOKEN # Issues: read and write
- capability: github:pr:write
token:
env: GOOBERS_GITHUB_PR_TOKEN # PR/Contents: read-write; commit statuses: read-only
- capability: repo:push
token:
env: GOOBERS_GITHUB_PUSH_TOKEN # Contents: read and write
- capability: agent:model
token:
env: GOOBERS_COPILOT_TOKEN # Copilot Requests: read-only; no repo access
Each credentials: entry sources one capability from its own token ref; an
entry for a capability the repo token would otherwise back overrides it,
so an issues-only stage never receives a token that also carries code or PR authority.
Omit overrides for capabilities no selected workflow declares.
daemonIdentity: one distinct bot identity for authored PRs, reviews, and merges
One instance.yaml block backs the whole daemon-mutation capability set —
repo:push, github:issues:write, github:pr:write,
github:pr:review, github:branch:delete,
github:pr:merge — with one distinct machine-account identity, instead of
repeating a credentials: entry per capability:
daemonIdentity:
kind: pat
token:
env: DAEMON_GITHUB_TOKEN
Mint the machine account's fine-grained PAT with the union of the permissions those
capabilities need, never the operator's own token. Every daemon-authored PR, review, and
merge then carries that account's login instead of being indistinguishable from the
operator's own manual activity. An explicit credentials: entry for any one of
the six capabilities still overrides daemonIdentity for that capability alone.
kind: github-app reuses the same installation-token minting a repo's own
auth.kind: github-app uses.
Why a second identity exists at all: GitHub forbids self-approval. A verdict posts as a native GitHub review only when the reviewing identity differs from the PR's author; on a single-identity instance every daemon-authored PR hits GitHub's self-review rejection and degrades to a documented comment/label handoff that the merge stage reads instead.
Agentic stages: stored login or the agent:model token
The permissions above cover the ordinary GitHub API. They are not
sufficient for an agentic stage (any stage whose goober uses the Copilot or Claude Code
harness) — the Copilot CLI authenticates to its model backend independently of repository
credentials. For an interactive local daemon, sign in with copilot once;
Goobers passes only the profile-location variables needed to find that stored session.
For a headless Windows Service, CI runner, or dedicated account without a stored session,
configure a separate fine-grained PAT with Copilot Requests: Read-only —
an account-level permission grantable only on a personal fine-grained PAT, never
one scoped to an organization's repositories. When the target repo lives in an org, the
agent:model token is necessarily a different token from the repo credential,
with no repository access at all. An org owner must approve personal fine-grained PATs
before they can access org repos, so budget for that approval step.
COPILOT_GITHUB_TOKEN — a token.env or token.file
reference on the agent:model capability alone only reaches the eventual
agentic stage, not daemon startup:
export COPILOT_GITHUB_TOKEN="$GOOBERS_COPILOT_TOKEN"
A mis-scoped token then fails during preflight rather than at the first agentic stage.
Verify harness availability before a live run with
goobers validate --check-harness.
Token storage options
Every token-bearing configuration surface uses the same reference shape. Choose exactly one source:
| Source | Best fit | Tradeoffs |
|---|---|---|
env: NAME | Ephemeral local runs and CI secret injection | Most portable and simple, but present in the daemon's process environment; an already-running process does not pick up a changed shell or service definition. |
file: /path | Headless services with a mounted secret file |
Portable and easy to rotate in place. Goobers fails closed unless the file is
private to its owner (Unix mode 0600, or an owner-only Windows DACL).
|
keychain: SERVICE | Interactive or launchd-managed macOS runners | Encrypted at rest by macOS and read for each resolution rather than retained. macOS-only; the daemon's user session must be able to unlock the login keychain without an interactive prompt. |
store: STORE/SECRET | Team and cloud deployments using a declared external secret store | Keeps secret material out of local configuration and disk, with rotation managed centrally. Requires store identity/network configuration and uses the resolver's bounded cache rather than fetching on every use. |
Create one macOS Keychain item per capability, using a unique service name:
security add-generic-password -U -a "$USER" \
-s "goobers/github-issues" -w Then reference only the service name in instance.yaml:
credentials:
- capability: github:issues:write
token:
keychain: goobers/github-issues Beyond a PAT
GitHub App installation tokens (auth.kind: github-app) mint a
short-lived token per run instead of holding a long-lived PAT — no credential to rotate,
escrow, or leak, and activity attributes to the App's own identity. See
the product's token-scopes guide
.
Azure DevOps repos authenticate through auth.kind:
azure-cli for local development, workload-identity or
managed-identity for unattended Kubernetes/Azure hosts, or a plain pat. See
the ADO authentication guide
.
External secret stores (Azure Key Vault today) let a token ref resolve
through store: storeName/secretName instead of env/file, with rotation
managed centrally. An instance that declares none behaves exactly as before.
Product source:
docs/guides/github-token-scopes.md
and
docs/guides/ado-authentication.md.