feat: granular access tokens — scoped, least-privilege tokens for bots/CI/integrations #761
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add support for granular access tokens — tokens scoped to specific permissions (and ideally specific repos/orgs) instead of broad full-account access. Bots, CI, deploy automation, and MCP integrations should run with least privilege.
Motivation
The forge's automation increasingly relies on long-lived tokens:
moko-deploy/ the Actions bot (push + force-push on dev/rc — see the org branch-protection standard)MOKOGITEA_TOKEN(registry + API, org secret, used by every deploy/release workflow)Today these are coarse: a leaked/misused token can act as the full account across every repo. Granular tokens limit blast radius — a deploy bot that can only push to named branches, a read-only mirror token that can't write, an MCP token scoped to the operations it actually performs.
Scope (to design)
read:/write:for repo, issue, package, admin, org, notification, user, etc.) in both the UI and the token-create API; expose the full scope surface if any is missing.GET .../tokensAPI for auditing.MOKOGITEA_TOKENin compose).Open questions
Related: org branch-protection standard (bots/actions whitelist), plaintext
MOKOGITEA_TOKENrotation follow-up.Investigated — sharpening the scope
Category-scoped tokens already exist (Gitea 1.26,
models/auth/access_token_scope.go):read:/write:forrepository,issue,package,organization,admin,user,notification,activitypub,misc+ a fork-specificlicensingcategory, plusallandpublic-only. So token granularity by operation category is done.The real gap = resource-level (per-repo / per-org) restriction. Gitea scopes are category-wide:
write:repositorygrants write to every repo the user can access — there is no way to bind a token to specific repositories or orgs. That is the least-privilege gap (e.g. a deploy bot that should push to exactly one repo, an MCP token limited to the repos it touches).Refined scope
AccessToken(+ migration). Empty = unrestricted (back-compat).reqToken), deny if the target repo/org is not in the token's allowlist — combined with (not replacing) the existing category scopes. A token must pass BOTH its category scope AND its resource allowlist.POST /users/{u}/tokensaccepts a repo/org list;GET .../tokensreturns it).Category scopes stay as-is; resource restriction is orthogonal. So this issue is effectively "add resource-level access-token restriction." Suggest a first PR = data model + migration + enforcement (the security-critical core), with UI/API following.