Skip to content

Conversation

@Excellencedev
Copy link

@Excellencedev Excellencedev commented Dec 17, 2025

Overview

This PR introduces granular permission controls for Gitea Actions tokens (GITEA_TOKEN), aligning Gitea's security model with GitHub Actions standards while maintaining compatibility with Gitea's unique repository unit system.

It addresses the need for finer access control by allowing administrators and repository owners to define default token permissions, set maximum permission ceilings, and control cross-repository access within organizations.

Key Features

1. Granular Token Permissions
  • Standard Keyword Support: Implements support for the permissions: keyword in workflow and job YAML files (e.g., contents: read, issues: write).
  • Permission Modes:
    • Permissive: Default write access for most units (backwards compatible).
    • Restricted: Default read-only access for contents and packages, with no access to other units.
    • Custom: Allows defining specific default levels for each unit type (Code, Issues, PRs, Packages, etc.).
  • Clamping Logic: Workflow-defined permissions are automatically "clamped" by repository or organization-level maximum settings. Workflows cannot escalate their own permissions beyond these limits.
2. Organization & Repository Settings
  • Settings UI: Added new settings pages at both Organization and Repository levels to manage Actions token defaults and maximums.
  • Inheritance: Repositories can be configured to "Follow organization-level configuration," simplifying management across large organizations.
  • Cross-Repository Access: Added a policy to control whether Actions workflows can access other repositories or packages within the same organization. This can be set to "None," "All," or restricted to a "Selected" list of repositories.
3. Security Hardening
  • Fork Pull Request Protection: Tokens for workflows triggered by pull requests from forks are strictly enforced as read-only, regardless of repository settings.
  • Package Access: Actions tokens can now only access packages explicitly linked to a repository, with cross-repo access governed by the organization's security policy.
  • Git Hook Integration: Propagates Actions Task IDs to git hooks to ensure that pushes performed by Actions tokens respect the specific permissions granted at runtime.
4. Technical Implementation
  • Permission Persistence: Parsed permissions are calculated at job creation and stored in the action_run_job table. This ensures the token's authority is deterministic throughout the job's lifecycle.
  • Parsing Priority: Implemented a priority system in the YAML parser where the broad contents scope is applied first, allowing granular scopes like code or releases to override it for precise control.
  • Re-runs: Permissions are re-evaluated during a job re-run to incorporate any changes made to repository settings in the interim.

How to Test

  1. Unit Tests: Run go test ./services/actions/... and go test ./models/repo/... to verify parsing logic and permission clamping.
  2. Integration Tests: Comprehensive tests have been added to tests/integration/actions_job_token_test.go covering:
    • Permissive vs. Restricted mode behavior.
    • YAML permissions: keyword evaluation.
    • Organization cross-repo access policies.
    • Resource access (Git, API, and Packages) under various permission configs.
  3. Manual Verification:
    • Navigate to Site/Org/Repo Settings -> Actions -> General.
    • Change "Default Token Permissions" and verify that newly triggered workflows reflect these changes in their GITEA_TOKEN capabilities.
    • Attempt a cross-repo API call from an Action and verify the Org policy is enforced.

Documentation

Added a PR in gitea's docs for this : https://gitea.com/gitea/docs/pulls/318

/fixes #24635
/claim #24635

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Dec 17, 2025
@github-actions github-actions bot added modifies/translation modifies/go Pull requests that update Go code modifies/templates This PR modifies the template files labels Dec 17, 2025
@Excellencedev
Copy link
Author

@lunny @wxiaoguang Please review this

@wxiaoguang
Copy link
Contributor

Thank you for asking me to review, but I don't use Actions. You can invite the maintainers from the original issue to review.

@Excellencedev
Copy link
Author

Thank you for asking me to review, but I don't use Actions. You can invite the maintainers from the original issue to review.

@silverwind Please review

@lunny lunny requested a review from Zettat123 December 17, 2025 16:53
@silverwind
Copy link
Member

I review mostly frontend stuff and am not much of an actions user myself, so please be patient until someone finds time to review it properly.

@Excellencedev
Copy link
Author

I review mostly frontend stuff and am not much of an actions user myself, so please be patient until someone finds time to review it properly.

No problem

@wxiaoguang
Copy link
Contributor

By the way, I see another (older) PR: Feat/actions token permissions #36113 , it added more than 2000 lines of code.

What are the differences? Which PR would win ....... @Zettat123

@Zettat123
Copy link
Contributor

By the way, I see another (older) PR: Feat/actions token permissions #36113 , it added more than 2000 lines of code.

What are the differences? Which PR would win ....... @Zettat123

This PR doesn't fully implement the proposal in #24635. (For example, it doesn't support configuring actions access between repositories in the same organization)

It seems that #36113 implemented these features, but I think its code needs improvement.

@silverwind
Copy link
Member

silverwind commented Dec 18, 2025

Issues I see on this screenshot:

image
  • Header text is black on dark theme
  • Contrast on light text is too low

I can probably help fix those, the first one may be a missing override of the fomantic CSS.

@Excellencedev
Copy link
Author

@Zettat123 @silverwind Pls give me a few hours(15-20 hours) and this PR will be ready to go
I will make sure to address all your comments and make sure I do everything from the issue
Drafting until then
When it is done, I will undraft it and notify you

@Excellencedev Excellencedev marked this pull request as draft December 18, 2025 01:53
@wxiaoguang
Copy link
Contributor

By the way, I see another (older) PR: Feat/actions token permissions #36113 , it added more than 2000 lines of code.
What are the differences? Which PR would win ....... @Zettat123

This PR doesn't fully implement the proposal in #24635. (For example, it doesn't support configuring actions access between repositories in the same organization)

It seems that #36113 implemented these features, but I think its code needs improvement.

But "PR: Feat/actions token permissions #36113" came first, and it is more complete, why not respect the first author, but only review this second one?

@Excellencedev
Copy link
Author

By the way, I see another (older) PR: Feat/actions token permissions #36113 , it added more than 2000 lines of code.
What are the differences? Which PR would win ....... @Zettat123

This PR doesn't fully implement the proposal in #24635. (For example, it doesn't support configuring actions access between repositories in the same organization)
It seems that #36113 implemented these features, but I think its code needs improvement.

But "PR: Feat/actions token permissions #36113" came first, and it is more complete, why not respect the first author, but only review this second one?

@wxiaoguang should i close my pr ?

@wxiaoguang
Copy link
Contributor

By the way, I see another (older) PR: Feat/actions token permissions #36113 , it added more than 2000 lines of code.
What are the differences? Which PR would win ....... @Zettat123

This PR doesn't fully implement the proposal in #24635. (For example, it doesn't support configuring actions access between repositories in the same organization)
It seems that #36113 implemented these features, but I think its code needs improvement.

But "PR: Feat/actions token permissions #36113" came first, and it is more complete, why not respect the first author, but only review this second one?

@wxiaoguang should i close my pr ?

I don't know. Reviewers decide.

@Zettat123
Copy link
Contributor

By the way, I see another (older) PR: Feat/actions token permissions #36113 , it added more than 2000 lines of code.
What are the differences? Which PR would win ....... @Zettat123

This PR doesn't fully implement the proposal in #24635. (For example, it doesn't support configuring actions access between repositories in the same organization)
It seems that #36113 implemented these features, but I think its code needs improvement.

But "PR: Feat/actions token permissions #36113" came first, and it is more complete, why not respect the first author, but only review this second one?

I reviewed both PRs, but did not receive responses to my comments in #36113. If @Excellencedev will address the review comments, I think we should keep this PR.

@silverwind
Copy link
Member

silverwind commented Dec 18, 2025

Imho, the only sensible thing we can do is race these 2 PRs.

@Excellencedev
Copy link
Author

Excellencedev commented Dec 18, 2025

Adressed most your comments in my latest commit, now i just need to make sure i fully implement the proposal in #24635

// DefaultTokenPermissions defines the default permissions for workflow tokens
DefaultTokenPermissions *ActionsTokenPermissions `json:"default_token_permissions,omitempty"`
// MaxTokenPermissions defines the maximum permissions (cannot be exceeded by workflow permissions keyword)
MaxTokenPermissions *ActionsTokenPermissions `json:"max_token_permissions,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find a form on the settings page to configure MaxTokenPermissions, is it unused?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fully implemented it now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why cannot I find this anywhere in org and repository settings pages? Unresolved this myself.

@Zettat123
Copy link
Contributor

According to the solution in #24635, I think this PR does not implement:

  • Support configuring the permissions
  • Support configuring access between repositories
  • Private packages can be accessed by Actions only when they have been linked to repositories

@Zettat123
Copy link
Contributor

I found a TODO in services/context/package.go. Do we need to implement it, or should we remove it?

func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) {
if setting.Service.RequireSignInViewStrict && (doer == nil || doer.IsGhost()) {
return perm.AccessModeNone, nil
}
if doer != nil && !doer.IsGhost() && (!doer.IsActive || doer.ProhibitLogin) {
return perm.AccessModeNone, nil
}
// TODO: ActionUser permission check
accessMode := perm.AccessModeNone

@Excellencedev
Copy link
Author

I found a TODO in services/context/package.go. Do we need to implement it, or should we remove it?

func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) {
if setting.Service.RequireSignInViewStrict && (doer == nil || doer.IsGhost()) {
return perm.AccessModeNone, nil
}
if doer != nil && !doer.IsGhost() && (!doer.IsActive || doer.ProhibitLogin) {
return perm.AccessModeNone, nil
}
// TODO: ActionUser permission check
accessMode := perm.AccessModeNone

@Zettat123 fixed f8a0b25

@Excellencedev
Copy link
Author

ready for another review @Zettat123

@Excellencedev
Copy link
Author

@Zettat123 @ChristopherHX pls review this

TokenPermissionMode ActionsTokenPermissionMode `json:"token_permission_mode,omitempty"`
// DefaultTokenPermissions defines the specific permissions for workflow tokens when TokenPermissionMode is set to "custom"
// and no "permissions" keyword is defined in the workflow YAML.
DefaultTokenPermissions *ActionsTokenPermissions `json:"default_token_permissions,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed the code. If I understand correctly, the steps you listed are for MaxTokenPermissions, not DefaultTokenPermissions. I'm still not sure what DefaultTokenPermissions actually does. If it's a necessary field, we need a form to configure it on the UI. If not, we should remove it.

@Zettat123
Copy link
Contributor

Based on the recent comments, it appears that the current implementation still contains logical inconsistencies and redundant code. This suggests that the PR requires a more thorough analysis of the existing code and a more comprehensive integration of the review comments provided so far.

To ensure we use everyone’s time effectively, I am converting this PR back to Draft status. Since maintainer resources are limited, we expect contributors to perform a rigorous self-review before requesting a formal review. Please ensure the logic is fully closed, redundant elements are removed, and similar issues throughout the PR are addressed consistently.


While I would like to help move this PR forward, I am currently tied up with several urgent tasks and do not have the bandwidth to review a PR of this size at this moment. Once you feel the PR is truly ready and you have switched the status back to "Ready for review," I welcome other reviewers to review this PR and provide their feedback. I would appreciate any comments on this.

@lunny lunny marked this pull request as draft January 9, 2026 22:50
@Excellencedev
Copy link
Author

Excellencedev commented Jan 10, 2026

Okay understood but I hope this PR won't just amount to nothing as it's been open for almost a month with several review comments from different maintainers

I will work throughout next week to make this "reviewable" an snake sure maintainers have a good time reviewing this

I just hope other maintainers will have on this in the future also

@Excellencedev
Copy link
Author

@Zettat123 It would help if I had a good list of main things wrong in this PR

@Zettat123
Copy link
Contributor

Thank you for your effort. While I’d like to provide a comprehensive list of issues, my limited bandwidth mean the following points are only representative examples, not an exhaustive list.

This PR still contains several issues that should have been identified and resolved during the self-review phase. These include, but are not limited to:

1. UI Issues
These issues are easily detectable through basic manual testing. It appears the changes were either not tested in a real environment or not checked thoroughly:

  • Inconsistency: The styling of the repo-level and org-level settings pages do not match.
  • Broken Functionality: The "Update Settings" button on the org settings page is unresponsive.
  • Missing Translations: There are unresolved locale strings.

I have attached screenshots below highlighting some of these UI issues.

Screenshots image image

2. Code Clarity and Intent
As noted in #36173 (comment), there are sections of code where the purpose remains unclear. While we fully understand that contributors may not be familiar with every module of this large and complex project, a contributor must have a 100% grasp of the code they are submitting. Based on the feedback in #36173 (comment), we cannot confirm that the logic behind this PR is fully understood.

3. Testing Concerns

TestActionsTokenPackagePermission
func TestActionsTokenPackagePermission(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
		user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
		session := loginUser(t, user2.Name)
		user2Token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWritePackage)

		// create a new repo
		apiRepo := createActionsTestRepo(t, user2Token, "actions-permission", false)
		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
		httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository)
		defer doAPIDeleteRepository(httpContext)(t)

		// create a mock runner
		runner := newMockRunner()
		runner.registerAsRepoRunner(t, repo.OwnerName, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false)

		// create a package for test
		packageName := fmt.Sprintf("test-pkg-%d", time.Now().UnixNano())
		packageVersion := "1.0.0"
		packageFileName := "package-test-1.bin"
		packageUploadURL := fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", repo.OwnerName, packageName, packageVersion, packageFileName)
		packageUploadReq := NewRequestWithBody(t, "PUT", packageUploadURL, bytes.NewReader([]byte("package content"))).
			AddTokenAuth(user2Token)
		MakeRequest(t, packageUploadReq, http.StatusCreated)
		pkg := unittest.AssertExistsAndLoadBean(t, &packages_model.Package{
			OwnerID: repo.OwnerID,
			Type:    packages_model.TypeGeneric,
			Name:    packageName,
		})
		linkReq := NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", repo.OwnerName, pkg.Name, repo.Name)).
			AddTokenAuth(user2Token)
		MakeRequest(t, linkReq, http.StatusCreated)

		// set actions token permission to "write" on all units
		req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/settings/actions/general/token_permissions", repo.OwnerName, repo.Name), map[string]string{
			"token_permission_mode": "custom",
			"max_code":              "write",
			"max_issues":            "write",
			"max_pull_requests":     "write",
			"max_wiki":              "write",
			"max_releases":          "write",
			"max_projects":          "write",
			"max_packages":          "write",
			"max_actions":           "write",
		})
		resp := session.MakeRequest(t, req, http.StatusSeeOther)
		require.Equal(t, fmt.Sprintf("/%s/%s/settings/actions/general", repo.OwnerName, repo.Name), test.RedirectURL(resp))

		// create a workflow file
		wfTreePath := ".gitea/workflows/test_permissions.yml"
		wfFileContent := `name: Test Permissions
on:
  push:
    paths:
      - '.gitea/workflows/test_permissions.yml'

jobs:
  test-job:
    runs-on: ubuntu-latest
    steps:
      - run: echo "write"
`
		opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+wfTreePath, wfFileContent)
		createWorkflowFile(t, user2Token, user2.Name, repo.Name, wfTreePath, opts)

		// fetch a task(*runnerv1.Task) and get its token
		runnerTask := runner.fetchTask(t)
		taskToken := runnerTask.Secrets["GITEA_TOKEN"]
		require.NotEmpty(t, taskToken)
		// taskToken should have "write" permission on packages
		newPackageVersion := "2.0.0"
		newPackageFileName := "package-test-2.bin"
		newPackageUploadURL := fmt.Sprintf("/api/packages/%s/generic/%s/%s/%s", repo.OwnerName, packageName, newPackageVersion, newPackageFileName)
		writeReq := NewRequestWithBody(t, "PUT", newPackageUploadURL, bytes.NewReader([]byte("new package content"))).
			AddTokenAuth(taskToken)
		MakeRequest(t, writeReq, http.StatusCreated)
	})
}
  • Best Practices: Multiple instances of direct db.Insert calls were used. Unless absolutely necessary, we should avoid direct DB manipulation. In real-world scenarios, users do not have direct access to db.Insert, so tests should reflect actual usage patterns.

@Zettat123
Copy link
Contributor

@lunny Thanks for the review request. However, I’ve spent too much time on this PR addressing issues that should have been caught during the self-review stage. Please ensure this PR receives at least one approval from another maintainer before requesting my review again. This will help ensure the fundamental quality is met first. Thanks for understanding.

Copy link
Contributor

@ChristopherHX ChristopherHX left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do another review later as my time allows.

AW While I looked at package access, I wanted that a repo can create a org/user container without explicit repo permission via private visibility if the resource does not exist yet (I am frequently using such a feature on GitHub). However I agree the linked issue does not contains such requirement anywhere.

"actions.general.token_permissions.access_read": "Read",
"actions.general.token_permissions.access_write": "Write",
"actions.general.token_permissions.code": "Code",
"actions.general.token_permissions.code.description": "Repository contents, commits, branches, downloads, releases, and merges.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Wrong, I believe it has been discussed code excludes releases

<!-- Follow Organization Configuration -->
<div class="field">
<div class="ui checkbox">
<input type="checkbox" name="follow_org_config" id="follow-org-config" {{if .FollowOrgConfig}}checked{{end}} class="js-follow-org-config">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected this to be more like opt-out (instead of opt in), with an optional policy from org admin to prevent that

image

}
}

if (!isSameOrg && !actionsCfg.IsCollaborativeOwner(taskRepo.OwnerID)) || !taskRepo.IsPrivate {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need another look on this, you revoke previous valid access while isSameOrg = true and orgCfg.AllowCrossRepoAccess is off

// DefaultTokenPermissions defines the default permissions for workflow tokens
DefaultTokenPermissions *ActionsTokenPermissions `json:"default_token_permissions,omitempty"`
// MaxTokenPermissions defines the maximum permissions (cannot be exceeded by workflow permissions keyword)
MaxTokenPermissions *ActionsTokenPermissions `json:"max_token_permissions,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why cannot I find this anywhere in org and repository settings pages? Unresolved this myself.

@Excellencedev
Copy link
Author

I will address all issues and do self review.

@Zettat123
Copy link
Contributor

I do another review later as my time allows.

AW While I looked at package access, I wanted that a repo can create a org/user container without explicit repo permission via private visibility if the resource does not exist yet (I am frequently using such a feature on GitHub). However I agree the linked issue does not contains such requirement anywhere.

I agree. I did some testing on GitHub, and when a brand-new, non-existent package is created via GitHub Actions, it is successfully created and automatically linked to the repository running the workflow. I believe Gitea could support this feature as well.

@Excellencedev
Copy link
Author

going to work on this weekend 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🙋 Bounty claim lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. modifies/api This PR adds API routes or modifies them modifies/cli PR changes something on the CLI, i.e. gitea doctor or gitea admin modifies/frontend modifies/go Pull requests that update Go code modifies/migrations modifies/templates This PR modifies the template files topic/gitea-actions related to the actions of Gitea

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Proposal] Support configuring permissions of automatic tokens of Actions jobs

7 participants