diff --git a/models/perm/access/actions_repo_permission_test.go b/models/perm/access/actions_repo_permission_test.go index aee097cf93e..9fdd8ae7f43 100644 --- a/models/perm/access/actions_repo_permission_test.go +++ b/models/perm/access/actions_repo_permission_test.go @@ -95,6 +95,40 @@ func TestGetActionsUserRepoPermission(t *testing.T) { assert.False(t, perm.CanRead(unit.TypeCode)) }) + t.Run("CollaborativeOwner_ForkPR_Denied", func(t *testing.T) { + // Target repo15 trusts repo2's owner as a collaborative owner. + repo15ActionsUnit := repo15.MustGetUnit(ctx, unit.TypeActions) + repo15ActionsUnit.ActionsConfig().AddCollaborativeOwner(owner2.ID) + require.NoError(t, repo_model.UpdateRepoUnitConfig(ctx, repo15ActionsUnit)) + + // Owner cross-repo policy does not allow repo15, so the only branch that + // could grant access is the collaborative-owner one. + require.NoError(t, actions_model.SetOwnerActionsConfig(ctx, owner2.ID, actions_model.OwnerActionsConfig{})) + + task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53}) + + // Non-fork task is legitimately granted code-read via collaborative owner. + task53.IsForkPullRequest = false + require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request")) + perm, err := GetActionsUserRepoPermission(ctx, repo15, actionsUser, task53.ID) + require.NoError(t, err) + assert.True(t, perm.CanRead(unit.TypeCode)) + + // Fork PR must NOT be able to read a third private repo through the + // collaborative-owner branch. + task53.IsForkPullRequest = true + require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request")) + perm, err = GetActionsUserRepoPermission(ctx, repo15, actionsUser, task53.ID) + require.NoError(t, err) + assert.False(t, perm.CanRead(unit.TypeCode)) + + // Restore state for subsequent subtests. + task53.IsForkPullRequest = false + require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request")) + repo15ActionsUnit.ActionsConfig().RemoveCollaborativeOwner(owner2.ID) + require.NoError(t, repo_model.UpdateRepoUnitConfig(ctx, repo15ActionsUnit)) + }) + t.Run("Inheritance_And_Clamping", func(t *testing.T) { task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53}) task53.IsForkPullRequest = false diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 2d53b0699f8..69d54b9020e 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -369,7 +369,9 @@ func GetActionsUserRepoPermission(ctx context.Context, repo *repo_model.Reposito // 2. The Actions Bot user has been explicitly granted access and repository is private // 3. The repository is public (handled by botPerm above) - if taskRepo.IsPrivate { + // Fork PRs are never allowed cross-repo access to other private repositories, + // matching the discriminator enforced by checkSameOwnerCrossRepoAccess above. + if taskRepo.IsPrivate && !task.IsForkPullRequest { actionsUnit := repo.MustGetUnit(ctx, unit.TypeActions) if actionsUnit.ActionsConfig().IsCollaborativeOwner(taskRepo.OwnerID) { return maxPerm, nil