Skip to content

on.roles rejects custom org repository roles despite base write permission (regression from #37959) #45536

Description

@siyo-rms

Summary

Since #37959 (commit 79a7eb48b, 2026-06-08), on.roles authorization prefers the repository role_name over the base permission. This unintentionally makes custom organization repository roles unauthorizable: an actor whose access is granted through a custom role (e.g. Security Champions) that confers write is rejected, even though their base permission is write. There is no frontmatter configuration that fixes it, because the roles: schema enum rejects custom role-name strings.

This is a fail-closed regression, distinct from the already-tracked #38140 (docs) and #37853 (maintain/triage enum). Neither of those covers custom org roles.

Reproduction

  1. In an org, create a custom repository role (base = Write), e.g. Security Champions.
  2. Grant a user that custom role on a repo using a command/auto agentic workflow (default roles: [admin, maintainer, write]).
  3. Have that user trigger the workflow (e.g. a slash command on a PR).

Observed pre_activation log:

Checking if user '<user>' has required permissions for <org>/<repo>
Required permissions: admin, maintainer, write
Repository permission level: write (role: Security Champions)
Warning: User permission 'Security Champions' does not meet requirements: admin, maintainer, write

Expected: authorized — base permission is write, which is in the required set.
Actual: denied.

Root cause

actions/setup/js/check_permissions_utils.cjs, checkRepositoryPermission:

const effectiveRole = normalizedRoleName || normalizedPermission; // role_name wins whenever present
const hasPermission = requiredPermissions.some(requiredPerm => {
  const normalizedRequired = requiredPerm === "maintainer" ? "maintain" : requiredPerm;
  return normalizedRequired === effectiveRole;                     // "write" === "Security Champions" → false
});

For GitHub's custom org repository roles, getCollaboratorPermissionLevel returns permission: "write" and role_name: "<custom role name>". Because role_name is always preferred when present, the base permission is never consulted, so any custom role whose name is not literally one of admin/maintainer/maintain/write/triage/read fails.

Introduced in #37959, whose intent was to honor precise maintain/triage roles (base API collapses maintain→write, triage→read). The custom-role case was not considered; the added tests only cover maintain/triage.

Why it is not configurable

The natural workaround — list the custom role name in roles: — does not compile. The schema enum-validates before runtime:

error: value must be one of 'admin', 'maintainer', 'maintain', 'write', 'triage', 'read'

So a custom-role-only actor cannot be authorized by any roles: value. (the runtime check_permissions_utils.cjs uses plain string equality and would match a custom name if it could reach GH_AW_REQUIRED_ROLES — but the compiler blocks it.)

Suggested fix (either)

Add a test fixture with { permission: "write", role_name: "<custom>" } to lock the custom-role case.

Environment

Activity

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions