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.