Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
3a10e8f
feat: Add configurable permissions for Actions automatic tokens
Excellencedev Dec 17, 2025
249794c
Merge branch 'main' into fix-24635
Excellencedev Dec 17, 2025
e20d12e
Merge branch 'main' into fix-24635
Excellencedev Dec 18, 2025
9a69f65
Adress all review comments
Excellencedev Dec 18, 2025
43e96d5
WIP
Excellencedev Dec 18, 2025
2a204e3
WIP
Excellencedev Dec 18, 2025
297ecef
Final core implementation changes
Excellencedev Dec 18, 2025
bd4420e
Merge branch 'main' into fix-24635
Excellencedev Dec 18, 2025
5317bb0
Fix lints
Excellencedev Dec 18, 2025
0682fd8
Fix test
Excellencedev Dec 18, 2025
fd1afc5
Fixing Test Failures for Token Permissions
Excellencedev Dec 18, 2025
a4aae82
Fix test
Excellencedev Dec 18, 2025
65051b1
Fix checks
Excellencedev Dec 18, 2025
a6b6e70
update tesr
Excellencedev Dec 18, 2025
b900c5c
Merge branch 'main' into fix-24635
Excellencedev Dec 19, 2025
5eb2f12
wip
Excellencedev Dec 19, 2025
92506da
Merge branch 'fix-24635' of https://github.com/Excellencedev/gitea in…
Excellencedev Dec 19, 2025
8daef63
Adress all reviewer feedback
Excellencedev Dec 19, 2025
5fd6d0e
Merge branch 'main' into fix-24635
Excellencedev Dec 19, 2025
af229dc
Merge branch 'main' into fix-24635
Excellencedev Dec 19, 2025
79a5d07
Completely redesign UI
Excellencedev Dec 20, 2025
d97712c
Merge branch 'fix-24635' of https://github.com/Excellencedev/gitea in…
Excellencedev Dec 20, 2025
058fc07
fix conflixt
Excellencedev Dec 20, 2025
04a6658
Merge remote-tracking branch 'origin/main' into fix-24635
Excellencedev Dec 20, 2025
64c2147
Adapt to JSON format
Excellencedev Dec 20, 2025
eca961e
Minor fixes
Excellencedev Dec 20, 2025
a5163fa
minor nitpick
Excellencedev Dec 20, 2025
9c5b278
Fix all bugs I found in the code
Excellencedev Dec 20, 2025
b0811fe
Formatting issues
Excellencedev Dec 20, 2025
b2f05ff
fix test
Excellencedev Dec 20, 2025
06b3db5
Improve test coverage
Excellencedev Dec 20, 2025
b0c2a95
Format
Excellencedev Dec 20, 2025
5628ab7
lint
Excellencedev Dec 20, 2025
38f384a
fmt
Excellencedev Dec 20, 2025
6cc6fd7
lint
Excellencedev Dec 20, 2025
463c670
issue fix
Excellencedev Dec 20, 2025
d25de6f
test fix
Excellencedev Dec 20, 2025
6d94723
regression
Excellencedev Dec 20, 2025
663d9b2
empty commit
Excellencedev Dec 20, 2025
34d13de
Merge branch 'main' into fix-24635
Excellencedev Dec 20, 2025
a72803d
Merge branch 'main' into fix-24635
Excellencedev Dec 22, 2025
5da2b95
Merge branch 'main' into fix-24635
Excellencedev Dec 22, 2025
302f888
fix ui
Excellencedev Dec 22, 2025
fe5230c
more ui fixes
Excellencedev Dec 23, 2025
50f300b
Merge branch 'main' into fix-24635
Excellencedev Dec 23, 2025
9bd8b81
Review comment fixes
Excellencedev Dec 24, 2025
9ada493
Add cross repo package access test
Excellencedev Dec 24, 2025
43279bf
Use correct authentication
Excellencedev Dec 24, 2025
ebf7e2e
fix test
Excellencedev Dec 24, 2025
973de05
review comment
Excellencedev Dec 24, 2025
e52ff98
Fix updated test
Excellencedev Dec 24, 2025
7df7f72
Merge branch 'main' into fix-24635
Excellencedev Dec 26, 2025
4ccb766
Re-implement changes for feedback
Excellencedev Dec 26, 2025
c7d2080
LOGS
Excellencedev Dec 26, 2025
26e47a5
....
Excellencedev Dec 26, 2025
640004f
test fix
Excellencedev Dec 26, 2025
621c0e2
...
Excellencedev Dec 26, 2025
ae43a09
...
Excellencedev Dec 26, 2025
f9f24dd
Make all CI green again
Excellencedev Dec 26, 2025
4fb4989
Merge branch 'main' into fix-24635
Excellencedev Dec 30, 2025
efb93b5
Fixes
Excellencedev Dec 30, 2025
de0d8da
Make sure pre-receive hook can properly check Actions token permissio…
Excellencedev Dec 30, 2025
4a9a54e
...
Excellencedev Dec 30, 2025
dcfe19c
fmt
Excellencedev Dec 30, 2025
f367039
make lint fixes
Excellencedev Dec 30, 2025
1ff75aa
Implement Workflow Level Permissions
Excellencedev Dec 31, 2025
2e7bd47
chore: fix ci
Excellencedev Dec 31, 2025
f3b1457
Feedback
Excellencedev Dec 31, 2025
3af786c
Merge branch 'main' into fix-24635
Excellencedev Jan 1, 2026
cdbed1d
Merge branch 'main' into fix-24635
Excellencedev Jan 2, 2026
a944be1
improve test
Excellencedev Jan 2, 2026
91f8298
comile errors
Excellencedev Jan 2, 2026
285f366
...
Excellencedev Jan 2, 2026
be52d4a
...
Excellencedev Jan 2, 2026
3e95499
green up ci
Excellencedev Jan 3, 2026
9faf677
Merge branch 'main' into fix-24635
Excellencedev Jan 3, 2026
13660f2
cleanup
Excellencedev Jan 3, 2026
942ea3d
ui fix
Excellencedev Jan 3, 2026
766b742
Merge branch 'main' into fix-24635
Excellencedev Jan 4, 2026
494f467
Merge branch 'main' into fix-24635
Excellencedev Jan 5, 2026
b92e2eb
adding action permissions test case to `tests/integration/actions_job…
Excellencedev Jan 5, 2026
a2583d0
Merge branch 'fix-24635' of https://github.com/Excellencedev/gitea in…
Excellencedev Jan 5, 2026
f5d4ce6
compile errors
Excellencedev Jan 5, 2026
012c9e0
...
Excellencedev Jan 5, 2026
b37a967
logs
Excellencedev Jan 5, 2026
15c19f6
...
Excellencedev Jan 5, 2026
350bcab
fix bug
Excellencedev Jan 5, 2026
d262948
Merge branch 'main' into fix-24635
Excellencedev Jan 6, 2026
9610d7f
Implement all requested changes
Excellencedev Jan 6, 2026
b4a17df
lints
Excellencedev Jan 6, 2026
3fb03fb
lint
Excellencedev Jan 6, 2026
dcf43af
fix ui issues
Excellencedev Jan 6, 2026
ba3a3a7
Feedback
Excellencedev Jan 7, 2026
655733e
fix
Excellencedev Jan 7, 2026
28dff2a
update parser
Excellencedev Jan 7, 2026
43931dc
refactor the parser
Excellencedev Jan 7, 2026
a5debd9
add releases and projects units
Excellencedev Jan 7, 2026
f8a0b25
resolve todo
Excellencedev Jan 7, 2026
1dfc172
fixes
Excellencedev Jan 7, 2026
1840f0a
formatting issues
Excellencedev Jan 7, 2026
7364953
...
Excellencedev Jan 7, 2026
4e524d3
improve comments
Excellencedev Jan 8, 2026
79c38fe
Merge branch 'main' into fix-24635
Excellencedev Jan 11, 2026
f7fc879
Merge branch 'main' into fix-24635
Excellencedev Jan 16, 2026
feb791c
feedback fixes
Excellencedev Jan 16, 2026
7cccb84
fixes
Excellencedev Jan 16, 2026
6d14a69
ui fixes
Excellencedev Jan 16, 2026
87d9f86
Merge branch 'main' into fix-24635
Excellencedev Jan 16, 2026
01a328b
fixes
Excellencedev Jan 16, 2026
5067f1d
fixes
Excellencedev Jan 16, 2026
27a4055
fix
Excellencedev Jan 16, 2026
2317080
logs
Excellencedev Jan 16, 2026
130b94b
..
Excellencedev Jan 16, 2026
4ed81fe
fix
Excellencedev Jan 17, 2026
a8c2d13
refactor ui
Excellencedev Jan 17, 2026
068ef6d
lints
Excellencedev Jan 17, 2026
1934c55
Merge branch 'main' into fix-24635
Excellencedev Jan 17, 2026
9352cc2
update test
Excellencedev Jan 17, 2026
493d51d
fix test
Excellencedev Jan 17, 2026
b6bf0a1
Merge branch 'fix-24635' of https://github.com/Excellencedev/gitea in…
Excellencedev Jan 17, 2026
c770087
fix formatting
Excellencedev Jan 17, 2026
2480c30
CrossRepoMode
Excellencedev Jan 17, 2026
f5c5775
CrossRepoMode tests
Excellencedev Jan 17, 2026
77871ef
fixes
Excellencedev Jan 18, 2026
c9ea591
format
Excellencedev Jan 18, 2026
4410d73
use ToString
Excellencedev Jan 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ Gitea or set your environment appropriately.`, "")
prID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPRID), 10, 64)
deployKeyID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvDeployKeyID), 10, 64)
actionPerm, _ := strconv.Atoi(os.Getenv(repo_module.EnvActionPerm))
actionsTaskID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvActionsTaskID), 10, 64)

hookOptions := private.HookOptions{
UserID: userID,
Expand All @@ -205,6 +206,7 @@ Gitea or set your environment appropriately.`, "")
PullRequestID: prID,
DeployKeyID: deployKeyID,
ActionPerm: actionPerm,
ActionsTaskID: actionsTaskID,
}

scanner := bufio.NewScanner(os.Stdin)
Expand Down
44 changes: 44 additions & 0 deletions models/actions/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
"context"

repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
)

// GetOrgActionsConfig loads the ActionsConfig for an organization from user settings
// It returns a default config if no setting is found
func GetOrgActionsConfig(ctx context.Context, orgID int64) (*repo_model.ActionsConfig, error) {
val, err := user_model.GetUserSetting(ctx, orgID, "actions.config")
if err != nil {
return nil, err
}

cfg := &repo_model.ActionsConfig{}
if val == "" {
// Return defaults if no config exists
cfg.CrossRepoMode = repo_model.ActionsCrossRepoModeAll
return cfg, nil
}

if err := json.Unmarshal([]byte(val), cfg); err != nil {
return nil, err
}

return cfg, nil
}

// SetOrgActionsConfig saves the ActionsConfig for an organization to user settings
func SetOrgActionsConfig(ctx context.Context, orgID int64, cfg *repo_model.ActionsConfig) error {
bs, err := json.Marshal(cfg)
if err != nil {
return err
}

return user_model.SetUserSetting(ctx, orgID, "actions.config", string(bs))
}
4 changes: 4 additions & 0 deletions models/actions/run_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ type ActionRunJob struct {
ConcurrencyGroup string `xorm:"index(repo_concurrency) NOT NULL DEFAULT ''"` // evaluated concurrency.group
ConcurrencyCancel bool `xorm:"NOT NULL DEFAULT FALSE"` // evaluated concurrency.cancel-in-progress

// TokenPermissions stores the parsed permissions from the workflow YAML (workflow + job level, clamped by repo max settings)
// This is JSON-encoded repo_model.ActionsTokenPermissions
TokenPermissions string `xorm:"TEXT"`

Started timeutil.TimeStamp
Stopped timeutil.TimeStamp
Created timeutil.TimeStamp `xorm:"created"`
Expand Down
1 change: 1 addition & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ func prepareMigrationTasks() []*migration {

newMigration(323, "Add support for actions concurrency", v1_26.AddActionsConcurrency),
newMigration(324, "Fix closed milestone completeness for milestones with no issues", v1_26.FixClosedMilestoneCompleteness),
newMigration(325, "Add TokenPermissions column to ActionRunJob", v1_26.AddTokenPermissionsToActionRunJob),
}
return preparedMigrations
}
Expand Down
15 changes: 15 additions & 0 deletions models/migrations/v1_26/v325.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_26

import (
"xorm.io/xorm"
)

func AddTokenPermissionsToActionRunJob(x *xorm.Engine) error {
type ActionRunJob struct {
TokenPermissions string `xorm:"TEXT"`
}
return x.Sync(new(ActionRunJob))
}
137 changes: 121 additions & 16 deletions models/perm/access/repo_permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
}

func (p *Permission) SetUnitsWithDefaultAccessMode(units []*repo_model.RepoUnit, mode perm_model.AccessMode) {
p.AccessMode = mode
p.units = units
p.unitsMode = make(map[unit.Type]perm_model.AccessMode)
for _, u := range p.units {
Expand Down Expand Up @@ -268,37 +269,141 @@ func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Reposito
return perm, err
}

var accessMode perm_model.AccessMode
if err := repo.LoadUnits(ctx); err != nil {
return perm, err
}

if err := repo.LoadOwner(ctx); err != nil {
return perm, err
}

actionsUnit, err := repo.GetUnit(ctx, unit.TypeActions)
if err != nil {
// If Actions unit doesn't exist, return empty permission
if repo_model.IsErrUnitTypeNotExist(err) {
return perm, nil
}
return perm, err
}
actionsCfg := actionsUnit.ActionsConfig()

if task.RepoID != repo.ID {
taskRepo, exist, err := db.GetByID[repo_model.Repository](ctx, task.RepoID)
if err != nil || !exist {
return perm, err
}
actionsCfg := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
if !actionsCfg.IsCollaborativeOwner(taskRepo.OwnerID) || !taskRepo.IsPrivate {

// Check Organization Cross-Repo Access Policy
if err := repo.LoadOwner(ctx); err != nil {
return perm, err
}

isSameOrg := false
if repo.OwnerID == taskRepo.OwnerID && repo.Owner.IsOrganization() {
isSameOrg = true
orgCfg, err := actions_model.GetOrgActionsConfig(ctx, repo.OwnerID)
if err != nil {
return perm, err
}
if !orgCfg.IsRepoAllowedCrossAccess(repo.ID) {
// Deny access if cross-repo is disabled or not allowed for this specific repo
return perm, nil
}
}

if (!isSameOrg && !actionsCfg.IsCollaborativeOwner(taskRepo.OwnerID)) || !taskRepo.IsPrivate {
// The task repo can access the current repo only if the task repo is private and
// the owner of the task repo is a collaborative owner of the current repo.
// FIXME should owner's visibility also be considered here?
//
// If not, we check if they are in the same org and cross-repo access is allowed.
// If allowed, we grant Read Access (consistent with old behavior and package access).
// If NOT allowed (checked above for sameOrg), we fall through to here.

// check permission like simple user but limit to read-only
perm, err = GetUserRepoPermission(ctx, repo, user_model.NewActionsUser())
if err != nil {
return perm, err
if !isSameOrg && repo.IsPrivate {
return perm, nil
}
}

// Cross-repo access is always read-only
perm.SetUnitsWithDefaultAccessMode(repo.Units, perm_model.AccessModeRead)
return perm, nil
}

// First check if job has explicit permissions stored from workflow YAML
var effectivePerms repo_model.ActionsTokenPermissions
var jobLoaded bool

// Only attempt to load job if JobID is set (non-zero)
if task.JobID != 0 {
if err := task.LoadJob(ctx); err == nil {
jobLoaded = true
} else {
// If loading job fails (e.g. resource doesn't exist), log it but fall back to repo permissions
// This prevents 500 errors if the task has a broken job link
log.Warn("GetActionsUserRepoPermission: failed to load job %d for task %d: %v", task.JobID, task.ID, err)
}
}

if jobLoaded && task.Job != nil && task.Job.TokenPermissions != "" {
// Use permissions parsed from workflow YAML (already clamped by repo max settings during insertion)
effectivePerms, err = repo_model.UnmarshalTokenPermissions(task.Job.TokenPermissions)
if err != nil {
// Fall back to repository settings if unmarshal fails
// If following org config, we need to load it
if !actionsCfg.OverrideOrgConfig && repo.Owner.IsOrganization() {
orgCfg, err := actions_model.GetOrgActionsConfig(ctx, repo.OwnerID)
if err != nil {
log.Error("GetOrgActionsConfig: %v", err)
effectivePerms = actionsCfg.GetEffectiveTokenPermissions(task.IsForkPullRequest) // Fallback to repo config on error
} else {
effectivePerms = orgCfg.GetEffectiveTokenPermissions(task.IsForkPullRequest)
effectivePerms = orgCfg.ClampPermissions(effectivePerms)
}
} else {
effectivePerms = actionsCfg.GetEffectiveTokenPermissions(task.IsForkPullRequest)
effectivePerms = actionsCfg.ClampPermissions(effectivePerms)
}
perm.AccessMode = min(perm.AccessMode, perm_model.AccessModeRead)
return perm, nil
}
accessMode = perm_model.AccessModeRead
} else if task.IsForkPullRequest {
accessMode = perm_model.AccessModeRead
} else {
accessMode = perm_model.AccessModeWrite
// No workflow permissions or job not found, use repository settings
if !actionsCfg.OverrideOrgConfig && repo.Owner.IsOrganization() {
orgCfg, err := actions_model.GetOrgActionsConfig(ctx, repo.OwnerID)
if err != nil {
log.Error("GetOrgActionsConfig: %v", err)
effectivePerms = actionsCfg.GetEffectiveTokenPermissions(task.IsForkPullRequest) // Fallback to repo config on error
effectivePerms = actionsCfg.ClampPermissions(effectivePerms)
} else {
effectivePerms = orgCfg.GetEffectiveTokenPermissions(task.IsForkPullRequest)
effectivePerms = orgCfg.ClampPermissions(effectivePerms)
}
} else {
effectivePerms = actionsCfg.GetEffectiveTokenPermissions(task.IsForkPullRequest)
effectivePerms = actionsCfg.ClampPermissions(effectivePerms)
}
}

if err := repo.LoadUnits(ctx); err != nil {
return perm, err
// Set up per-unit access modes based on configured permissions
perm.units = repo.Units
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
perm.unitsMode[unit.TypeCode] = effectivePerms.Code
perm.unitsMode[unit.TypeIssues] = effectivePerms.Issues
perm.unitsMode[unit.TypePullRequests] = effectivePerms.PullRequests
perm.unitsMode[unit.TypePackages] = effectivePerms.Packages
perm.unitsMode[unit.TypeActions] = effectivePerms.Actions
perm.unitsMode[unit.TypeWiki] = effectivePerms.Wiki
perm.unitsMode[unit.TypeReleases] = effectivePerms.Releases
perm.unitsMode[unit.TypeProjects] = effectivePerms.Projects

// Set base access mode to the maximum of all unit permissions
maxMode := perm_model.AccessModeNone
for _, mode := range perm.unitsMode {
if mode > maxMode {
maxMode = mode
}
}
perm.SetUnitsWithDefaultAccessMode(repo.Units, accessMode)
perm.AccessMode = maxMode

return perm, nil
}

Expand Down
Loading
Loading