services/actions/token_permission_design.md
This document details the design of the Actions Token Permission system within Gitea, originally proposed in #24635.
Gitea Actions uses a strict clamping mechanism for token permissions.
While workflows can request explicit permissions that exceed the repository's default baseline
(e.g., requesting write when the default mode is Restricted),
these requests are always bounded by a hard ceiling.
The maximum allowable permissions (MaxTokenPermissions) are set at the Repository or Organization level.
Any permissions requested by a workflow are strictly clamped by this ceiling policy.
This ensures that workflows cannot bypass organizational or repository-level security restrictions.
GITEA_TOKENpermissions: block is present in a workflow.write access to most repository scopes by default.read access (or none) to repository scopes by default.contents in workflow syntax),
Issues, PullRequests, Actions, Wiki, and Projects.Packages scope is supported in workflow/job permissions: blocks
but is currently hidden from the settings UI.AllowedCrossRepoIDs list in their owner-level settings
to grant the token read-only access to other private/internal repositories they own.AllowedCrossRepoIDs list is empty, there is no cross-repository access
to other private repositories (default for enhanced security).permissions: none).When a job starts, Gitea evaluates the requested permissions for the GITEA_TOKEN through a multistep clamping process:
permissions: block, Gitea parses it.permissions: block, Gitea parses that.permissions: block is specified, or no explicit permissions are defined at all,
Gitea falls back to using the repository's default TokenPermissionMode (Permissive or Restricted)
to generate base permissions.MaxTokenPermissions in their Actions settings.Issues: read and the workflow requests Issues: write, the final token gets Issues: read.UserActionsConfig) containing MaxTokenPermissions,
and these restrictions cascade down.OverrideOwnerConfig.OverrideOwnerConfig is false, and the owner sets MaxTokenPermissions to read for all scopes,
no repository under that owner can grant write access, regardless of their own settings or the workflow's request.In GitHub Actions compatibility, the contents scope maps to multiple granular scopes in Gitea.
contents: write maps to Code: write and Releases: write.contents and a more granular scope (e.g., code),
the granular scope takes absolute priority.Example YAML:
permissions:
contents: write
code: read
Result: The token gets Code: read (from granular) and Releases: write (from contents).
permissions: {})none for all scopes.Restricted mode for security reasons.read (or none),
preventing untrusted code from modifying the repository.AllowedCrossRepoIDs configuration.
Fork PRs can only read the target repository and truly public repositories.AllowedCrossRepoIDs setting. The allowed list only governs access
to private/internal repositories owned by the same user or organization."Packages" belong to "owner" but not "repository". Although there is a function "linking a package to a repository", in most cases it doesn't really work. When accessing a package, usually there is no information about a repository. So the "packages" permission should be designed separately from other permissions.
A possible approach is like this: let owner set packages permissions, and make the repositories follow.
On owner-level:
On repository-level:
Maybe reusing the "org teams" permission system is a good choice: bind a repository's Actions token to a team.