Permission Model
GitHub does not have one permission system. Classic tokens use OAuth scopes, fine-grained tokens and Apps use resource permissions, people get roles, and keys have no scopes at all. This page lines them up: every way access can be granted, how one request turns into effective access, and the same capability written in each vocabulary. The Access Tiers pages tier each of them.
Five vocabularies
The short answer to "do roles, fine-grained tokens and Apps follow the same pattern?" is no. Fine-grained PATs, GitHub Apps and GITHUB_TOKEN share one family of permission names. Classic PATs and OAuth Apps use another. Roles, including custom roles, use a third list whose names do not match either. They only meet when GitHub checks a request.
Classic OAuth scopes
Classic PATs, OAuth Apps
A short list of coarse scopes such as repo, workflow and admin:org. The token reaches every organization the user belongs to, limited only by what the user can do there.
Resource permissions
Fine-grained PATs, GitHub Apps, GITHUB_TOKEN
One permission per resource (Contents, Issues, Administration, Secrets, Workflows) set to none, read or write. Tied to one resource owner and a selection of repositories. GITHUB_TOKEN uses a subset of the same names.
Roles and role permissions
Enterprise, organization, team and repository roles, including custom roles
A ladder of roles (Read, Triage, Write, Maintain, Admin; Member, Owner) plus action-level extras such as Manage webhooks or Delete issues. The names do not match the resource permission list.
Keys and certificates
SSH keys, SSH certificates, deploy keys, App private keys, webhook secrets
No scopes at all. Access is whatever the key is attached to: a user's full Git access, one repository, or every installation of an App.
Settings and policies
Enterprise and organization policies, environment rules, account settings
Not a grant on their own. They decide which grants are allowed, and they gate every token and role at request time.
How a request becomes access
Every request passes through the same five steps. The principal holds grants, presents one credential, and policy decides whether the request goes through. The credential can only narrow what the grants allow.
Principal
Who or what acts
- Human (EMU or personal account)
- GitHub App
- Workflow run
- Machine with a deploy key
- AI agent
Grant
What it has been given
- Enterprise role or custom role
- Org role, pre-defined or custom
- Org base permission
- Team membership
- Repo role or custom repo role
- Bypass actor, env reviewer, code owner
- App installation
Credential
The ceiling of one request
- Classic PAT, OAuth token (scopes)
- Fine-grained PAT (resource permissions)
- App installation or user token
- GITHUB_TOKEN (one repo, one job)
- Web session
- SSH key, SSH certificate, deploy key
Gates
Policy at request time
- SAML SSO authorization
- IP allow list
- PAT and OAuth app policies
- Rulesets and branch protection
- Enterprise policies
Resource
What is reached
- Repositories and code
- Secrets and variables
- Runners
- Packages and releases
- Org and enterprise settings
One path skips the gates
A workflow with id-token: write gets an OIDC token and trades it for cloud credentials. From there the cloud provider's trust policy is the only check. See GITHUB_TOKEN: id-token: write.
Effective access = what the principal is (role grants) ∩ what the credential allows (token ceiling) ∩ what policy lets through (gates).
- A token never adds access the user does not have. A fine-grained PAT with Contents: write on a repo where the user only has Read can still only read.
- App installation tokens and GITHUB_TOKEN have no user behind them, so the role part drops out and the App or workflow permissions are the whole grant.
- An OIDC token from id-token: write leaves GitHub. The cloud trust policy is then the only gate, and none of the GitHub grants above apply.
- Rulesets apply to everyone except the bypass list, so a bypass entry outranks any role.
Every way to grant access
25 mechanisms, from enterprise roles down to a single deploy key. The Workload rows are grants that only exist while a workflow or agent runs. Use this as the inventory list for an access review: each row is a place someone can hold access that another row will not show you.
| Mechanism | Plane | Granted by | Reaches | Lifetime | Vocabulary | On Access Tiers |
|---|---|---|---|---|---|---|
| Enterprise role: Owner, Billing manager, App manager, Security manager, User | Enterprise | Enterprise owner, or the IdP for EMU | Every organization | Until removed | Role | |
| Guest collaborator (EMU only) | Enterprise | IdP provisioning, then an org or repo admin grants access | Only the repositories and orgs they are added to | Until deprovisioned | Role | |
| Enterprise custom role and enterprise teams | Enterprise | Enterprise owner | The enterprise settings the role covers | Until removed | Role | |
| Organization role: Owner, Member, Moderator, Billing manager | Organization | Organization owner | One organization | Until removed | Role | |
| Pre-defined organization role: Security manager, App manager, CI/CD admin, All-repository read to admin | Organization | Organization owner | One organization, every repository for the all-repository roles | Until removed | Role | |
| Custom organization role: org permissions plus an optional base repository role | Organization | Organization owner, or defined once for the enterprise | One organization | Until removed | Role | |
| Organization base permission: none, read, write or admin | Organization | Organization owner | Every repository, for every member | Until changed | Role | |
| Team membership: member or maintainer, inherited through child teams | Organization | Organization owner, team maintainer, or IdP group sync | The repositories the team was given | Until removed | Role | |
| Direct repository role: Read, Triage, Write, Maintain, Admin | Organization | Repository admin | One repository | Until removed | Role | |
| Custom repository role: an inherited role plus additional permissions | Organization | Organization owner defines it, repository admin assigns it | One repository per assignment | Until removed | Role | |
| Outside collaborator | Organization | Repository admin, if org policy allows it | The named repositories only, with any repository role up to Admin | Until removed by hand. SSO is not required for outside collaborators, so IdP offboarding and SCIM do not remove them | Role | |
| Trigger-borrowed access: a fork pull request or issue comment that starts a workflow | Workload | Whoever wrote the workflow and chose its trigger (pull_request_target, issue_comment, workflow_run) | What that workflow's GITHUB_TOKEN, secrets and runner can reach, used by someone who holds no role at all | One workflow run | Resource permission | |
| Special grants: ruleset bypass actor, environment required reviewer, code owner | Organization | Repository admin or org owner; anyone who can edit CODEOWNERS | The rules, environments or paths they cover | Until removed | Role | |
| Classic personal access token | User | The user | Every organization the user belongs to | Expiry optional | OAuth scope | |
| Fine-grained personal access token | User | The user, plus org approval when required | One resource owner, selected repositories | Expiry required | Resource permission | |
| OAuth App authorization | Application | User consent, plus org approval when OAuth access is restricted | Every organization the user lets it reach | Usually no expiry | OAuth scope | |
| GitHub App installation | Application | Org owner, App manager, or repo admin for selected repos | All repositories or a selection | 1-hour tokens; the private key never expires | Resource permission | |
| GitHub App user access token | Application | A user signing in to the App | The App's permissions and the user's own access, whichever is smaller | 8 hours, refreshable for 6 months | Resource permission | |
| GITHUB_TOKEN | Workload | The workflow's permissions block, or the org or repo default | One repository, one job | The job's duration | Resource permission | |
| OIDC token from id-token: write | Workload | The workflow file, and the cloud trust policy that accepts it | Whatever the cloud role allows | Minutes | Resource permission | |
| Self-hosted runner and runner group access | Workload | Repo admin, org owner or CI/CD admin | The repositories and workflows allowed to use the runner | Until removed | Setting | |
| Copilot cloud agent, MCP servers and AI steps in workflows | Workload | A user assigning the task, within org and enterprise policy | Whatever token and tools it is given | One task or one job | Resource permission | |
| SSH key on a user account | User | The user | All the user's Git access | No expiry | Key | |
| SSH certificate authority | Organization | Organization or enterprise owner | Git access as any member the certificate names | Certificate validity | Key | |
| Deploy key | Organization | Repository admin | One repository, read-only or read-write | No expiry | Key |
Crosswalk: one capability, every vocabulary
The same action written as a classic scope, a fine-grained or App permission, a GITHUB_TOKEN permission and a role. A dash means that vocabulary cannot grant it. When you review a token or role, find the capability here to compare it with the others; every tier on the right is on the same scale.
| Capability | Classic PAT / OAuth | Fine-grained PAT / App | GITHUB_TOKEN | Role | On Access Tiers |
|---|---|---|---|---|---|
| Read code | repo (public_repo for public repos) | Contents: read | contents: read | Read |
|
| Push code | repo | Contents: write | contents: write | Write | |
| Change files under .github/workflows GITHUB_TOKEN has no workflows permission, so a workflow cannot rewrite workflow files with its own token. | workflow | Workflows: write | – | Write | |
| Start or re-run workflows | repo | Actions: write | actions: write | Write | |
| Open, approve and merge pull requests GITHUB_TOKEN can only approve when the org or repo allows Actions to create and approve pull requests. | repo | Pull requests: write | pull-requests: write | Write | |
| Change repository settings and branch protection | repo, as an admin | Administration: write | – | Admin | |
| Delete a repository or change its visibility | delete_repo (delete); repo, as an admin (visibility) | Administration: write | – | Admin, if org policy lets admins do it |
|
| Manage Actions secrets | repo (repository); admin:org (organization) | Secrets: write | – | Admin, CI/CD admin, or a custom role with secrets permissions | |
| Register self-hosted runners | repo (repository); admin:org (organization); manage_runners:enterprise | Administration: write (repository); Self-hosted runners: write (organization) | – | Repository admin, org owner, CI/CD admin | |
| Publish packages One of the few places where a fine-grained PAT is not an option, so teams fall back to long-lived classic tokens. | write:packages | – (GitHub Packages only accepts classic PATs) | packages: write | Write | |
| Get cloud credentials through OIDC | – | – | id-token: write | Anyone who can change a workflow on a branch the cloud trust policy accepts | |
| Manage organization members and teams | admin:org | Members: write (organization) | – | Org owner; team maintainer for their own team | |
| Manage organization webhooks | admin:org_hook | Webhooks: write (organization) | – | Org owner, or a custom organization role | |
| Install or change GitHub Apps Installing an App is done in the browser, not with a token scope, so only roles control it. | – | – | – | Org owner, App manager; repo admins for Apps that ask for no org or Administration permission, unless the org turns that off | |
| Push past branch rules | – (follows the user) | – (follows the user or App) | – | Ruleset bypass actor | |
| Enterprise administration | admin:enterprise | Limited; check GitHub's list before relying on it | – | Enterprise owner |
Credential model per type
Every kind of grant has a fixed credential model: who holds it, how far it reaches, how long it lives and who hands it out.
| Type | Vocabulary | Principal | Reaches | Lifetime | Granted by |
|---|---|---|---|---|---|
| classic-pat-scope | OAuth scope | Human | Every organization the user belongs to; SAML SSO orgs also need the token authorized | Expiry optional; an enterprise or org can set a maximum | The user, for themselves |
| oauth-app-scope | OAuth scope | Human, through an app | Every organization the user grants it, unless the org restricts OAuth app access | Usually no expiry; lives until revoked | The user, on a consent screen |
| actions-token-permission | Resource permission | Workflow run | The one repository the workflow runs in | The job's duration | The workflow file's permissions block, falling back to the org or repo default |
| ai-agent | Resource permission | Agent working for a user | The repository it was assigned to, plus any tools and MCP servers it is configured with | One task session | A user assigning it work; org and enterprise policy decide whether it may run |
| app-token | Resource permission | App, or App acting for a user | The App's permissions, cut down to the installation's repositories (and the user's own access for user tokens) | 1 hour for installation tokens; 8 hours for user tokens, with a 6-month refresh token | Minted by the App with its private key, or by a user signing in to the App |
| fine-grained-pat-permission | Resource permission | Human | One resource owner (a user or an org) and the repositories picked for the token | Expiry required; 366 days by default, an enterprise or org can lower it | The user, plus org approval when the org requires it |
| github-app-permission | Resource permission | App | The repositories each installation was given (all, or a selection) | Installation tokens last 1 hour; the private key that mints them does not expire | Whoever installs the App: an org owner, an App manager, or a repo admin for selected repos |
| codeowners | Role | Human or team | The file paths listed for them, on branches that require code owner review | As long as the CODEOWNERS file names them | Anyone who can change the CODEOWNERS file on the protected branch |
| custom-org-role | Role | Human or team | One organization; a base repository role applies to every repository | Until removed | An organization owner; the role can also be defined once for the whole enterprise |
| custom-repo-role | Role | Human or team | One repository per assignment | Until removed | An organization owner defines it; a repository admin assigns it |
| enterprise-role | Role | Human | The whole enterprise, or the part a custom role covers | Until removed, or until the IdP deprovisions the user (EMU) | An enterprise owner, or the IdP for EMU |
| org-role | Role | Human | One organization, often every repository in it | Until removed | An organization owner |
| repo-role | Role | Human or team | One repository | Until removed | A repository admin or an organization owner |
| ruleset-bypass | Role | Role, team or App | The branches, tags or pushes the ruleset targets | Until removed from the bypass list | A repository admin, or an organization owner for org rulesets |
| team-role | Role | Human | Every repository the team, and its child teams, have been given | Until removed; can be synced from an IdP group | An organization owner or a team maintainer |
| app-credential | Key | App | Every installation of the App, or every delivery of a webhook | No expiry until rotated | The App owner or an App manager |
| deploy-key | Key | Machine | One repository, read-only or read-write | No expiry | A repository admin |
| gpg-key | Key | Human | Proves authorship of commits; grants no access | Key's own expiry, if set | The user |
| ssh-certificate-authority | Key | Whoever holds the CA private key | Git access as any member of the organization or enterprise that trusts the CA | Each certificate's validity; the CA key itself does not expire | An organization or enterprise owner uploads the CA |
| ssh-key | Key | Human | All Git access the user has, in every organization | No expiry | The user |
| account-setting | Setting | Human | The user's own account | Until changed | The user, within what the org or enterprise enforces |
| enterprise-policy | Setting | Enterprise | Every organization in the enterprise | Until changed | An enterprise owner |
| environment-setting | Setting | Repository | Deployments to one environment | Until changed | A repository admin |
| runner | Setting | Machine | The repositories and workflows allowed to use the runner or runner group | Until removed; non-ephemeral runners keep state between jobs | A repository admin, an org owner or a CI/CD admin |