Fine-grained PATs deny actions the user is allowed to perform
Fine-grained personal access tokens are scoped to one group or project (a
"boundary"). GitLab checks the token owner is a member of it — but
membership isn't how GitLab grants access, so users are denied things they're
allowed to do. An old api-scope token works where a correctly-scoped
fine-grained token is rejected.
Cause: Authz::Boundary#member? only sees group/project membership, while
ProjectPolicy/GroupPolicy grant access more broadly.
Token authorization
Authz::BoundaryPolicy requires membership before granting any of the token's
permissions. The non-member fallback only mirrors anonymous access, which is
read-only.
- Public projects/groups — non-members can create issues, notes, MRs and forks; a gPAT can't. The 403 even names a permission the token already holds.
- Internal projects/groups — anonymous sees nothing there, so the fallback never applies and non-members get zero permissions, reads included.
- Subproject members reading the parent group — allowed normally, 404 with a
gPAT (e.g.
GET /groups/:id/packages). - Organization owners — have access to all projects in their organization without membership; works on group boundaries, denied on project boundaries.
Token minting
The same test gates which boundaries a token may be scoped to (REST and GraphQL creation paths, plus the UI picker). Users therefore can't create a narrow token for any case above — they must fall back to the broadest "all memberships" scope. Fixing authorization alone pushes users toward wider tokens than they need.
Fix
Check a read_boundary permission on the boundary, instead of membership, to
decide whether a token can be authorized against it or scoped to it.