# ghtier — full reference dump This file inlines every permission, role, scope, key and policy, every incident and attack path, and the hardening checklist, so an LLM or agent can read ghtier in one pass. The same content is available as structured JSON under /data/ (see /llms.txt). Every tier below uses one scale. --- ## Keys & policies (38) Keys take the tier of what they act as; policies the tier of what changing them allows. ### SSH key on a user account [key, user, id ssh-key] Tier 1, context-dependent, data plane Acts as the user's Git access, with no scopes to narrow it. That includes pushing changes to workflow files, which then run with the repository's secrets. Summary: An SSH key registered to the account authenticates git push, pull and clone over SSH without a web-based 2FA challenge. Attack path: If an attacker obtains the private key (from an unencrypted disk or a leaked dotfile), they can push to every repository the account can write to, indefinitely, since SSH Git authentication is not gated by the session 2FA prompts the web UI uses. Mitigations: Always set a passphrase on SSH private keys; Rotate or revoke keys found on shared or unmanaged machines; Use SSH certificates with a short validity where possible Related permissions: contents:write, workflows:write, keys:write Available on: Every setup (https://docs.github.com/en/authentication/connecting-to-github-with-ssh) Docs: GitHub Docs: Connecting to GitHub with SSH (https://docs.github.com/en/authentication/connecting-to-github-with-ssh) ### Deploy key with write access [key, repository, id deploy-key] Tier 1, data plane Pushes to its repository like Contents: write. A key carries no scopes, so nothing in it stops pushes to workflow files either, and it has no user, no 2FA and no expiry. Summary: An SSH key attached to one repository instead of a user. Read-only by default; write access is opt-in when the key is added. Attack path: A write deploy key is a Git credential with no user, no 2FA and no expiry. It survives the departure of whoever created it and does not show up in member reviews. Stolen from a server or CI config, it pushes to that repository until someone deletes it. Mitigations: Prefer read-only deploy keys, or a GitHub App with Contents: write; Delete keys nobody can name an owner for; Turn off deploy keys at the organization level if nothing needs them Related permissions: contents:write, administration:write Available on: Every setup (https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys) Docs: GitHub Docs: Managing deploy keys (https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys) ### SSH certificate authority private key [key, organization, id ssh-ca-key] Tier 0, control plane Signs certificates for any member of the organization or enterprise that trusts it, so whoever holds it can push as anyone. Summary: An organization or enterprise can trust an SSH CA, so members use short-lived certificates instead of personal SSH keys. The CA's private key signs those certificates. Attack path: Short-lived certificates are a good control, but the CA key behind them is now the credential to protect. Whoever holds it can sign a certificate for any member and push as them. It lives outside GitHub, in whatever system issues the certificates. Mitigations: Keep the CA key in an HSM or a managed signing service; Require an expiry on every certificate (upgrade legacy CAs that are exempt) Related permissions: contents:write Available on: Enterprise Cloud, EMU, Server (https://docs.github.com/en/organizations/managing-git-access-to-your-organizations-repositories/about-ssh-certificate-authorities) Docs: GitHub Docs: About SSH certificate authorities (https://docs.github.com/en/organizations/managing-git-access-to-your-organizations-repositories/about-ssh-certificate-authorities) ### GitHub App private key (.pem) [key, app, id app-private-key] Tier 0, context-dependent, control plane Mints installation tokens for every installation of the App, so it carries the strongest permission any installation was given. For an App with organization Administration or Members permissions that is Tier 0. Summary: The App's signing key, used to create JWTs and mint installation access tokens for every organization and repository where the App is installed. Attack path: A leaked private key lets an attacker mint installation tokens for every installation of the App, instantly inheriting whatever permissions each installation granted. Since one App is often installed organization-wide, this is frequently the highest-leverage credential in an organization's supply chain. Mitigations: Rotate keys regularly; Store it in a secrets manager, never in a repository or CI logs; Give the App the minimum permissions; Install on selected repositories, not all Related permissions: installation_repositories:write Available on: Every setup (https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps) Docs: GitHub Docs: Managing private keys for GitHub Apps (https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps) ### Webhook secret [key, app, id webhook-secret] Tier 2, context-dependent, data plane Grants nothing on GitHub. It lets someone forge events to the service that receives them, so its real tier is whatever that service does with an event, such as starting a deployment. Summary: The shared secret used to check that incoming webhook payloads really came from GitHub. Attack path: A leaked webhook secret lets an attacker forge signed payloads to the receiving service, which may trigger deployment or automation logic with fake event data the attacker chose. Mitigations: Rotate webhook secrets like any other credential; Have receivers re-check state through the API instead of trusting payload contents Related permissions: repository_hooks:write, organization_hooks:write Available on: Every setup (https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) Docs: GitHub Docs: Validating webhook deliveries (https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) ### GPG key for commit signing [key, user, id gpg-key] Tier 2, data plane The same tier as GPG keys: write. Grants no access on its own, but the private key makes commits and tags show as Verified for this user, which defeats a signed-commit rule as a control. Pushing still needs separate write access. Summary: Makes commits and tags signed with the matching private key show as Verified for the user. Attack path: With the private key, or the ability to add a key to the account, an attacker signs commits as that user. Where a ruleset requires signed commits, or reviewers trust the Verified badge, forged commits pass as the user's own. Pushing them still needs write access from another credential. Mitigations: Protect signing keys like other credentials when signed commits are required by a ruleset; Remove signing keys from accounts that leave, and alert on new ones Related permissions: gpg_keys:write Available on: Every setup (https://docs.github.com/en/authentication/managing-commit-signature-verification) Docs: GitHub Docs: Managing commit signature verification (https://docs.github.com/en/authentication/managing-commit-signature-verification) ### SSH key for commit signing [key, user, id ssh-signing-key] Tier 2, data plane The same tier as SSH signing keys: write. Like a GPG key, it makes signed commits and tags show as Verified for this user, which defeats a signed-commit rule as a control. Pushing still needs separate write access. Summary: An SSH key added to the account as a signing key rather than an authentication key. Commits and tags signed with it show as Verified. Attack path: An SSH key registered for signing does not authenticate Git, but it signs commits as the user. A stolen signing key, or one added to the account, makes forged commits pass a signed-commit rule. Mitigations: Protect signing keys like other credentials when signed commits are required by a ruleset; Remove signing keys from accounts that leave, and alert on new ones Related permissions: git_signing_ssh_public_keys:write Available on: Every setup (https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) Docs: GitHub Docs: About commit signature verification (https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) ### Web session cookie [key, user, id web-session] Tier 0, context-dependent, control plane Acts as the signed-in account in the browser, with everything that account can do, including its settings. For an organization or enterprise owner that is Tier 0. It was issued after the password and 2FA, so a stolen cookie skips both. Summary: The cookie that keeps a person signed in to GitHub in the browser. Attack path: Infostealer malware copies browser cookies. Replayed elsewhere, the session is already past the password and 2FA, and SSO for the organization may still be valid. The attacker can then create tokens, add SSH keys or change settings as that person. Mitigations: Review and revoke sessions in the account's security settings after a suspected infection; Keep owner accounts on managed devices, and use a separate account for day-to-day work; Alert on new personal access tokens, SSH keys and OAuth grants for owner accounts Related permissions: administration:write, organization_administration:write, keys:write Available on: Every setup (https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/viewing-and-managing-your-sessions) Docs: GitHub Docs: Viewing and managing your sessions (https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/viewing-and-managing-your-sessions) ### Runner registration token or JIT configuration [key, organization, id runner-registration] Tier 1, management plane Registers a machine as a self-hosted runner. That machine then receives the jobs routed to its labels and the secrets those jobs get, so it is the same reach as Self-hosted runners: write. Summary: A short-lived credential (a registration token expires after one hour) or a just-in-time runner configuration, used to add a self-hosted runner to a repository or organization. Attack path: A registration token or JIT configuration copied from a build log, a script or a provisioning pipeline lets an attacker register their own machine under the expected labels. Jobs then run on it, with their secrets and GITHUB_TOKEN, until someone notices the extra runner. Mitigations: Create registration tokens just before use and never log them; Use runner groups limited to the repositories that need them; Alert on new self-hosted runners Related permissions: organization_self_hosted_runners:write, administration:write Available on: Every setup (https://docs.github.com/en/rest/actions/self-hosted-runners) Docs: GitHub REST: Self-hosted runners (https://docs.github.com/en/rest/actions/self-hosted-runners) ### GitHub App or OAuth App client secret [key, app, id app-client-secret] Tier 2, context-dependent, data plane Not a credential on its own: it exchanges authorization codes and refresh tokens for user access tokens. Stolen together with stored refresh tokens, it turns them into live tokens for every user of the App, at those users' access. Summary: The secret an App uses with its client ID to get user access tokens and refresh them. Attack path: Apps often keep the client secret next to the refresh tokens of their users. Whoever gets both can mint user access tokens for each of those users until the secret is rotated and the tokens revoked. Mitigations: Keep client secrets and refresh tokens in a secrets manager, apart from each other; Rotate the client secret when the App's servers are compromised Available on: Every setup (https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/best-practices-for-creating-a-github-app) Docs: GitHub Docs: Best practices for creating a GitHub App (https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/best-practices-for-creating-a-github-app) ### Codespaces token [key, repository, id codespaces-token] Tier 2, context-dependent, data plane Read and write on the codespace's repository when its owner has write access, plus any other repositories the codespace was authorized for. Data plane, and it expires on its own. Summary: The GitHub token a codespace gets each time it is created or restarted. Its scope follows the owner's access to the repository. Attack path: Code running in a codespace, such as a malicious dependency or a dev container script, can read the token and push to the repository as the developer while it is valid. Mitigations: Only authorize a codespace for the other repositories it needs; Treat dev container configuration from untrusted branches as code that runs with this token Related permissions: contents:write, codespaces:write Available on: Free, Team, Enterprise Cloud, EMU (https://docs.github.com/en/codespaces/reference/security-in-github-codespaces) Docs: GitHub Docs: Security in GitHub Codespaces (https://docs.github.com/en/codespaces/reference/security-in-github-codespaces) ### SSO recovery codes [key, enterprise, id sso-recovery-codes] Tier 0, control plane They let an owner reach the organization or enterprise when the identity provider is unavailable, so they bypass the IdP and its MFA and conditional access. A break-glass credential at the top of the tree. Summary: One-time codes an organization or enterprise owner downloads for signing in when the SAML or OIDC identity provider is down. Attack path: Recovery codes are often saved once in a shared drive or a password manager entry nobody watches. With an owner's password and these codes, an attacker signs in without the IdP ever seeing it. Mitigations: Store recovery codes offline or in a restricted password manager vault; Alert on their use in the audit log, and regenerate them after use Related permissions: organization_administration:write Available on: Enterprise Cloud (organization or enterprise SSO), EMU (used by the setup user) (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/managing-recovery-codes-for-your-enterprise/downloading-your-enterprise-accounts-single-sign-on-recovery-codes) Docs: GitHub Docs: Downloading your enterprise account's single sign-on recovery codes (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/managing-recovery-codes-for-your-enterprise/downloading-your-enterprise-accounts-single-sign-on-recovery-codes) ### Enterprise Managed Users setup user [key, enterprise, id emu-setup-user] Tier 0, control plane The first enterprise owner and the only account in the enterprise that is not provisioned by SCIM. It signs in with a password, configures authentication and provisioning, and is the one that uses the recovery codes. Summary: The break-glass owner account of an Enterprise Managed Users enterprise, created with the enterprise. Attack path: The setup user sits outside the IdP. If its password and 2FA, or the recovery codes, leak, an attacker owns the enterprise and can change authentication itself. Mitigations: Keep the setup user's credentials in the company password manager with few people able to read them; Require 2FA on it and alert on every sign-in Related permissions: enterprise_custom_enterprise_roles:write, enterprise_scim:write Available on: EMU (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/understanding-iam-for-enterprises/getting-started-with-enterprise-managed-users) Docs: GitHub Docs: Getting started with Enterprise Managed Users (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/understanding-iam-for-enterprises/getting-started-with-enterprise-managed-users) ### Management Console access (Enterprise Server) [key, enterprise, id management-console] Tier 0, control plane Configures the whole GitHub Enterprise Server instance, including authentication. The root site administrator and Management Console users sit above every organization and enterprise role on the instance. Summary: Access to the Enterprise Server Management Console, held by the root site administrator and any Management Console users they create. Attack path: Whoever signs in to the Management Console can change authentication for the instance, so every other control on it depends on this credential. Mitigations: Limit Management Console users and keep the root site administrator's credentials offline; Restrict network access to the Management Console Available on: Server (https://docs.github.com/en/enterprise-server@latest/admin/administering-your-instance/administering-your-instance-from-the-web-ui/managing-access-to-the-management-console) Docs: GitHub Docs: Managing access to the Management Console (https://docs.github.com/en/enterprise-server@latest/admin/administering-your-instance/administering-your-instance-from-the-web-ui/managing-access-to-the-management-console) ### Copilot cloud agent and other AI agents [agent, repository, id ai-agent] Tier 2, context-dependent, data plane Opens pull requests and comments, which is data-plane write. Its real tier is the tokens, secrets and MCP tools it is given. Summary: An AI agent that takes an issue or prompt, works in its own environment and opens a pull request. It reads text that outsiders can write, and acts with the tools and MCP servers it is configured with. Attack path: Its inputs are issues, comments and files, so prompt injection is the entry point. An agent with broad MCP tools or secrets in its environment can be talked into reading private data or pushing changes it was never asked for. Mitigations: Keep its output behind normal review; Treat the tokens and MCP servers it is given as grants to review like any other Related permissions: pull_requests:write, issues:write, organization_copilot_agent_settings:write Available on: Free (with a Copilot plan), Team (with a Copilot plan), Enterprise Cloud, EMU (organization repositories only) (https://docs.github.com/en/copilot/get-started/plans) Docs: GitHub Docs: Copilot cloud agent concepts (https://docs.github.com/en/copilot/concepts/agents/cloud-agent) ### Ruleset bypass actor [grant, repository, id ruleset-bypass] Tier 1, management plane Skips every rule the ruleset enforces: required reviews, signed commits, protected tags. That is control over what reaches production. Summary: A role, team or App on a ruleset's bypass list. It can push or merge past the rules that bind everyone else. Attack path: Rulesets are where required reviews, signed commits and protected release tags live. Whoever is on the bypass list skips all of it, so a stolen bypass account or App token can force-push a default branch or move a release tag. Bypass entries are often added once for a release bot and never removed. Mitigations: Keep bypass lists empty or limited to a break-glass team; Prefer "pull requests only" bypass over "always" Related permissions: administration:write, organization_administration:write Available on: Free (public repositories only), Team, Enterprise Cloud, EMU, Server (https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/about-rulesets) Docs: GitHub Docs: Creating rulesets for a repository (https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/creating-rulesets-for-a-repository) ### Environment required reviewer [grant, repository, id environment-reviewer] Tier 2, context-dependent, data plane Approving a deployment releases the environment's secrets to the job. On a production environment that is a Tier 1 decision. Summary: A person or team who must approve a job before it can deploy to an environment such as production. Attack path: The approval is what releases the environment's secrets to the job. A compromised reviewer, or a reviewer list that includes the person who wrote the change, turns the gate into a formality. On Free, Pro and Team plans required reviewers only work on public repositories. Mitigations: Turn on "Prevent self-review"; Use a reviewer team separate from the authors Related permissions: deployments:write, environments:write Available on: Free (public repositories only), Team (public repositories only), Enterprise Cloud, EMU, Server (https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) Docs: GitHub Docs: Managing environments for deployment (https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) ### Code owner (CODEOWNERS entry) [grant, repository, id codeowners] Tier 2, context-dependent, data plane A code owner's approval satisfies review for their paths. Whoever can edit CODEOWNERS on the protected branch chooses who approves. Summary: A person or team named in CODEOWNERS whose review is required for the paths they own, when the branch requires code owner review. Attack path: Code owner approval is a grant written in a file. Anyone who can change CODEOWNERS on the protected branch decides who may approve, so the file needs its own owner. A wildcard entry that names a large team makes any member's approval enough. Mitigations: Make CODEOWNERS itself owned by a small security or platform team; Cover .github/workflows/ with a specific owner Related permissions: contents:write, pull_requests:write Available on: Free (public repositories only), Team, Enterprise Cloud, EMU, Server (https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners) Docs: GitHub Docs: About code owners (https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners) ### Outside collaborator [grant, repository, id outside-collaborator] Tier 1, context-dependent, management plane Takes the tier of the repository role it is given, up to Admin. It lives outside the identity provider: GitHub does not require SSO for outside collaborators, so removing someone in the IdP, or SCIM, never touches this access. Summary: A person who is not a member of the organization but has a role on one or more of its repositories. Attack path: An outside collaborator account is a personal account the organization does not manage. It keeps its repository roles through IdP offboarding and is not covered by SSO, so a contractor who left, or a compromised personal account, keeps Write or Admin until someone removes it by hand. Adding one is also a quiet way to keep access after an incident. Mitigations: Only let organization owners invite outside collaborators; Give each outside collaborator an internal sponsor and an end date, and review them with members; Alert on new outside collaborators Related permissions: administration:write, members:write Available on: Free, Team, Enterprise Cloud, Server (https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-outside-collaborators/adding-outside-collaborators-to-repositories-in-your-organization) Docs: GitHub Docs: About identity and access management with SAML single sign-on (https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-saml-single-sign-on-for-your-organization/about-identity-and-access-management-with-saml-single-sign-on) ### Enterprise SAML SSO enforcement [policy, enterprise, id enterprise-saml-sso] Tier 0, control plane Decides whether the identity provider is the gate for every organization. Switching it off is a change to identity for the whole enterprise. Summary: Enforces, configures or disables SAML single sign-on across every organization in the enterprise. Attack path: Disabling enterprise-wide SSO enforcement removes the IdP as the single control point for every organization at once, so stale or stolen GitHub credentials work without the IdP's MFA and conditional access. Mitigations: Alert on any change to SSO configuration; Keep the enterprise owners who can change it to a minimum Available on: Enterprise Cloud, EMU (SAML or OIDC, required), Server (SAML for the whole instance) (https://docs.github.com/en/enterprise-cloud@latest/admin/identity-and-access-management/using-saml-for-enterprise-iam/about-identity-and-access-management-for-your-enterprise) Docs: GitHub Docs: About identity and access management for your enterprise (https://docs.github.com/en/enterprise-cloud@latest/admin/identity-and-access-management/using-saml-for-enterprise-iam/about-identity-and-access-management-for-your-enterprise) ### Enterprise Managed Users provisioning (SCIM) [policy, enterprise, id enterprise-emu-provisioning] Tier 0, control plane Creates and removes the identities themselves, with their team memberships. That is who exists in the enterprise. Summary: Controls creation, deprovisioning and attribute mapping of Enterprise Managed User identities through SCIM. Attack path: An attacker with this access can provision a new managed identity with chosen attributes and team memberships, minting a trusted user from nothing inside the enterprise's identity system. Mitigations: Protect the SCIM token like an owner credential; Review provisioning events in the audit log Related permissions: enterprise_scim:write Available on: EMU, Server (SCIM provisioning for an instance that uses SAML; managed users are Enterprise Cloud only) (https://docs.github.com/en/enterprise-server@latest/admin/managing-iam/provisioning-user-accounts-with-scim/configuring-scim-provisioning-for-users) Docs: GitHub Docs: Configuring SCIM provisioning for Enterprise Managed Users (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/provisioning-user-accounts-with-scim/configuring-scim-provisioning-for-users) ### Organization SAML SSO enforcement [policy, organization, id organization-saml-sso] Tier 0, control plane Decides whether the identity provider is the gate for the organization. Switching it off is a change to identity. Summary: Enables, disables or reconfigures SAML single sign-on enforcement for the organization. Attack path: Disabling SSO enforcement removes the organization's central identity control point, letting compromised or stale credentials authenticate without the IdP's own MFA and conditional access policies. Mitigations: Alert on any change to SSO configuration Available on: Enterprise Cloud (https://docs.github.com/en/enterprise-cloud@latest/admin/concepts/identity-and-access-management/identity-and-access-management-fundamentals) Docs: GitHub Docs: Managing SAML SSO for your organization (https://docs.github.com/en/organizations/managing-saml-single-sign-on-for-your-organization) ### Audit log streaming [policy, enterprise, id enterprise-audit-log-streaming] Tier 1, management plane Grants no access, but switching it off or redirecting it hides everything that follows from the security team. Summary: Configures where enterprise audit log events are streamed, such as a SIEM, and can turn streaming off. Attack path: An attacker who disables or redirects audit log streaming can operate afterwards without their actions reaching the security team's monitoring pipeline, a classic anti-forensics move. Mitigations: Alert separately, for example through a redundant export, whenever streaming settings change Available on: Enterprise Cloud, EMU, Server (https://docs.github.com/en/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise) Docs: GitHub Docs: Streaming the audit log for your enterprise (https://docs.github.com/en/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise) ### Enterprise 2FA requirement [policy, enterprise, id enterprise-2fa-enforcement] Tier 1, management plane Grants nothing directly, but switching it off reopens every organization to password-only account takeover. Summary: Controls whether two-factor authentication is required for every member across every organization in the enterprise. Attack path: Disabling this requirement reopens every organization in the enterprise to password-only account takeover, undoing a control that otherwise covers the whole enterprise. Mitigations: Require 2FA, and prefer passkeys or security keys Available on: Enterprise Cloud, Server (with built-in or LDAP authentication) (https://docs.github.com/en/enterprise-cloud@latest/admin/enforcing-policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise) Docs: GitHub Docs: Enforcing policies for security settings in your enterprise (https://docs.github.com/en/enterprise-cloud@latest/admin/enforcing-policies/enforcing-policies-for-your-enterprise/enforcing-policies-for-security-settings-in-your-enterprise) ### IP allow list [policy, enterprise, id enterprise-ip-allow-list] Tier 1, management plane Grants nothing directly, but adding a range removes the network barrier for stolen credentials used from it. Summary: Manages the IP ranges allowed to reach the enterprise's organizations. Attack path: Adding an attacker-controlled IP range to the allow list removes a network barrier that would otherwise block credential-based access from outside the corporate network. Mitigations: Alert on changes to the allow list Related permissions: organization_administration:write Available on: Enterprise Cloud, EMU (https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-allowed-ip-addresses-for-your-organization) Docs: GitHub Docs: Restricting network traffic to your enterprise with an IP allow list (https://docs.github.com/en/enterprise-cloud@latest/admin/configuring-settings/hardening-security-for-your-enterprise/restricting-network-traffic-to-your-enterprise-with-an-ip-allow-list) ### Organization creation and deletion policy [policy, enterprise, id enterprise-organization-creation] Tier 1, management plane Decides who can create or delete organizations inside the enterprise, including unmonitored ones. Summary: Controls whether, and by whom, organizations can be created or deleted within the enterprise. Attack path: An attacker can create a new, unmonitored organization inside the trusted enterprise to stage malicious repositories or Apps, or delete a legitimate organization outright. Mitigations: Restrict organization creation to enterprise owners Available on: Server (https://docs.github.com/en/enterprise-server@latest/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/preventing-users-from-creating-organizations) Docs: GitHub Docs: Preventing users from creating organizations (https://docs.github.com/en/enterprise-server@latest/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/preventing-users-from-creating-organizations) ### Base permission: Write or Admin on every repository [policy, organization, id organization-base-permission] Tier 0, context-dependent, control plane Set to Admin, every member becomes admin on every repository, the same as giving everyone All-repository admin. Set to Write, everyone can change workflows and read secrets everywhere, the same as All-repository write. Summary: The organization's base permission: the repository role every member gets on every repository by default. Attack path: A single setting quietly gives the whole membership write or admin on every repository, including production. It is a common, easy-to-miss misconfiguration. Mitigations: Set the base permission to Read or None; Grant more per repository or per team instead Related permissions: organization_administration:write Available on: Every setup (https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/setting-base-permissions-for-an-organization) Docs: GitHub Docs: Setting base permissions for an organization (https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/setting-base-permissions-for-an-organization) ### Organization Actions policy [policy, organization, id organization-actions-policy] Tier 1, management plane Decides which actions may run, what GITHUB_TOKEN gets by default and which repositories can use runner groups, for every repository at once. Summary: Org-wide Actions settings: allowed actions and workflows, default GITHUB_TOKEN permissions, SHA pinning, fork pull request rules and runner group access. Attack path: Loosening this policy, for example allowing every Marketplace action or raising the default GITHUB_TOKEN permission to read and write, widens the CI/CD attack surface for every repository in the organization at once. Mitigations: Require SHA pinning and allow only selected actions; Keep the default GITHUB_TOKEN permission read-only Related permissions: organization_administration:write Available on: Every setup (https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization) Docs: GitHub Docs: Disabling or limiting GitHub Actions for your organization (https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization) ### Environment protection rules [policy, repository, id environment-protection-rules] Tier 1, management plane Decides who must approve a deployment and from which branches, which is the gate in front of production secrets. Summary: Required reviewers, wait timers and deployment branch policies for a repository's deployment environments. Attack path: Removing required reviewers or loosening the deployment branch policy on a production environment lets an attacker's branch deploy straight to production without a second set of eyes. Mitigations: Restrict deployments to protected branches; Alert on changes to production environments Related permissions: administration:write, environments:write Available on: Free (public repositories only), Team (reviewers and wait timers on public repositories only; branch rules on private ones too), Enterprise Cloud, EMU, Server (https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) Docs: GitHub Docs: Managing environments for deployment (https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) ### Account without 2FA [policy, user, id account-2fa-disabled] Tier 1, context-dependent, control plane Everything the account holds is one phished or reused password away. Its real tier is the highest role or token the account has. Summary: The account can be accessed with a password alone, with no second factor. Attack path: A password-only account is one credential-stuffing or phishing hit away from full compromise of everything that identity holds. Every role and token it owns inherits this weakness. Mitigations: Require 2FA at the organization or enterprise level; Prefer passkeys or security keys over SMS and app codes Available on: Free, Team, Enterprise Cloud, Server (with built-in or LDAP authentication) (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/understanding-iam-for-enterprises/abilities-and-restrictions-of-managed-user-accounts) Docs: GitHub Docs: Securing your account with 2FA (https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa) ### Personal access token policy [policy, organization, id organization-pat-policy] Tier 1, management plane Decides whether members' classic and fine-grained tokens can reach the organization at all, and whether fine-grained tokens need an owner's approval. Opening it lets any member's leaked token act on the organization's repositories. Summary: The organization setting that allows or restricts classic personal access tokens and fine-grained tokens, and whether fine-grained ones need approval. Attack path: With classic tokens allowed and no approval for fine-grained ones, every token a member ever created works against the organization. A token leaked from a laptop or CI log then needs nothing else. Mitigations: Restrict classic tokens and require approval for fine-grained ones Related permissions: organization_administration:write, organization_personal_access_token_requests:write Available on: Every setup (https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization) Docs: GitHub Docs: Setting a personal access token policy for your organization (https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization) ### OAuth app access restrictions [policy, organization, id organization-oauth-restrictions] Tier 1, management plane Decides which OAuth apps can use members' access to the organization. Turned off, any OAuth app a member authorizes reaches the organization's repositories with that member's access. Summary: The organization setting that limits organization access to OAuth apps an owner has approved. Attack path: A consent-phishing OAuth app, or a compromised third-party app, gets a member's token. Without restrictions that token works on the organization straight away; with them, it does nothing until an owner approves the app. Mitigations: Turn on OAuth app access restrictions; Review approved OAuth apps and remove unused ones Related permissions: organization_administration:write Available on: Free, Team, Enterprise Cloud, EMU (https://docs.github.com/en/organizations/managing-oauth-access-to-your-organizations-data/about-oauth-app-access-restrictions) Docs: GitHub Docs: About OAuth app access restrictions (https://docs.github.com/en/organizations/managing-oauth-access-to-your-organizations-data/about-oauth-app-access-restrictions) ### App access requests and installations [policy, organization, id organization-app-requests] Tier 2, data plane Decides who can ask for an OAuth app or GitHub App to be given access. A request still needs an owner, so this shapes the review queue rather than granting access. Summary: The organization setting that controls which users can request organization access for apps. Attack path: Open requests let outside collaborators and members flood owners with app requests, which makes a malicious one easier to approve by mistake. Mitigations: Limit app requests to members; Review each request against a named owner and need Related permissions: organization_administration:write Available on: Every setup (https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/limiting-oauth-app-and-github-app-access-requests) Docs: GitHub Docs: Limiting OAuth app and GitHub App access requests and installations (https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/limiting-oauth-app-and-github-app-access-requests) ### Allow GitHub Actions to create and approve pull requests [policy, organization, id actions-create-approve-prs] Tier 1, management plane Turned on, a workflow's GITHUB_TOKEN can approve pull requests, and that approval counts towards required reviews. Anyone who can change a workflow can then get code merged without a person reviewing it. Summary: The workflow permissions setting that lets GITHUB_TOKEN create and approve pull requests. New organizations start with it off. Attack path: An attacker with write access opens a pull request and a workflow that approves it. With the setting on, the approval satisfies the review rule and the change merges. Mitigations: Turn the setting off at the enterprise or organization level; Require reviews from code owners, which a workflow cannot be Related permissions: pull_requests:write, organization_administration:write Available on: Every setup (https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization) Docs: GitHub Docs: Disabling or limiting GitHub Actions for your organization (https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization) ### Member privileges: visibility changes, deletion and private forks [policy, organization, id member-repository-privileges] Tier 1, management plane Decides whether repository admins, not only owners, can make repositories public, delete or transfer them, and whether private repositories can be forked. Each one moves code out of the organization's control. Summary: The organization settings for changing repository visibility, deleting or transferring repositories, and forking private repositories. Attack path: With these left open, a stolen repository admin account can flip a private repository to public, transfer it away or delete it, and a member can fork private code to an account the organization does not control. Mitigations: Limit visibility changes, deletion and transfer to organization owners; Turn off forking of private repositories unless a team needs it Related permissions: organization_administration:write, administration:write Available on: Every setup (https://docs.github.com/en/organizations/managing-organization-settings/restricting-repository-visibility-changes-in-your-organization) Docs: GitHub Docs: Restricting repository visibility changes in your organization (https://docs.github.com/en/organizations/managing-organization-settings/restricting-repository-visibility-changes-in-your-organization) ### Who can invite outside collaborators [policy, organization, id outside-collaborator-invites] Tier 1, management plane Decides whether repository admins can add outside collaborators, who keep their access outside the identity provider (see Outside collaborator). Summary: The organization setting that limits inviting outside collaborators to organization owners. Attack path: With invites open to repository admins, one stolen admin account can add an outside account that survives offboarding and SSO. Mitigations: Limit outside collaborator invites to organization owners Related permissions: organization_administration:write, administration:write Available on: Enterprise Cloud, Server (https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-organization-settings/setting-permissions-for-adding-outside-collaborators) Docs: GitHub Docs: Setting permissions for adding outside collaborators (https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-organization-settings/setting-permissions-for-adding-outside-collaborators) ### Identity provider admin for the GitHub app [external, enterprise, id idp-app-admin] Tier 0, control plane Whoever administers the SAML or OIDC app, or SCIM provisioning, in the IdP decides who can sign in. With Enterprise Managed Users the IdP creates every account, so this is control over the enterprise from outside GitHub. Summary: Administrators of the GitHub application in Entra ID, Okta or another identity provider. Attack path: An attacker with admin rights in the IdP assigns themselves to the GitHub app, or provisions a new account, and signs in. GitHub sees an ordinary SSO sign-in. Mitigations: Treat IdP admins of the GitHub app as Tier 0 in the IdP's own model; Alert on assignment changes to the GitHub app in the IdP Related permissions: enterprise_scim:write Available on: Enterprise Cloud (with SAML SSO), EMU, Server (with SAML or another external authentication) (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/configuring-authentication-for-enterprise-managed-users/configuring-saml-single-sign-on-for-enterprise-managed-users) Docs: GitHub Docs: Configuring SAML single sign-on for Enterprise Managed Users (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/configuring-authentication-for-enterprise-managed-users/configuring-saml-single-sign-on-for-enterprise-managed-users) ### Cloud OIDC trust policy for GitHub Actions [external, repository, id cloud-oidc-trust] Tier 1, context-dependent, management plane Takes the tier of the cloud role it lets workflows assume. The subject (sub) condition decides which workflows can: a wildcard on the repository, branch or environment lets workflows nobody reviewed assume the role. Summary: The trust policy in AWS, Azure or Google Cloud that accepts GitHub's OIDC token for a cloud role. Attack path: A trust policy that matches repo:org/* or any branch lets a workflow from any repository or pull request branch in the organization get cloud credentials, skipping environment reviewers. Mitigations: Match the full subject, including the environment, in every trust policy; Use environments with required reviewers for roles that can deploy Related permissions: id_token:write Available on: Every setup (https://docs.github.com/en/actions/reference/security/oidc) Docs: GitHub Docs: OpenID Connect reference (https://docs.github.com/en/actions/reference/security/oidc) --- ## Incidents (31) ### User (6) #### Dependabot impersonation — July 2023 id: user-dependabot-impersonation | url: /scenarios/dependabot-impersonation Attackers used stolen personal access tokens to push commits faked to look like automated Dependabot contributions across hundreds of public and private repositories. They added a hook.yml workflow file that shipped GitHub secrets to an external server on every push, and appended obfuscated JS to every .js file in the project to harvest form passwords. Permissions abused: contents:write, workflow scope (classic PAT) Attribution: Checkmarx noted that PAT activity logging was only visible to enterprise accounts at the time Sources: Surprise: When Dependabot Contributes Malicious Code (Research: explains the primitive, generalizes beyond one victim) — https://checkmarx.com/blog/surprise-when-dependabot-contributes-malicious-code/ #### Mercedes-Benz source code exposure — January 2024 id: user-mercedes-benz-token-leak | url: /scenarios/mercedes-benz-token-leak An employee committed a token to a public repo. It gave unrestricted and unmonitored access to the entire source code on the internal GitHub Enterprise Server, exposing database connection strings, cloud access keys, blueprints, design documents, SSO passwords, and API keys. The first token granted full ownership of the account; inside one of its private repos sat a second token to the GHE instance, a classic chained-credential blast radius that ran from the user plane straight into the enterprise plane. Permissions abused: Classic PAT (full account access), Chained token → GitHub Enterprise Server access Sources: Mercedes-Benz source code at risk: GitHub token mishap (Research: explains the primitive, generalizes beyond one victim) — https://redhuntlabs.com/blog/mercedes-benz-source-code-at-risk-github-token-mishap-sparks-major-security-concerns/ | How a mistakenly published password exposed Mercedes-Benz source code (Tracker: live campaign feed, recheck before publishing) — https://techcrunch.com/2024/01/26/mercedez-benz-token-exposed-source-code-github | A mishandled GitHub token exposed Mercedes-Benz source code (Tracker: live campaign feed, recheck before publishing) — https://www.bleepingcomputer.com/news/security/a-mishandled-github-token-exposed-mercedes-benz-source-code/ | Leaked GitHub token exposed Mercedes source code (Tracker: live campaign feed, recheck before publishing) — https://www.securityweek.com/leaked-github-token-exposed-mercedes-source-code/ #### OAuth consent phishing at scale — 2025–2026 id: user-oauth-consent-phishing | url: /scenarios/oauth-consent-phishing A campaign hit roughly 12,000 repositories with fake "Security Alert" issues, tricking developers into authorizing a malicious OAuth app that then held full control of their accounts and code. Because the link lands on the real GitHub authorization screen, it passes SPF/DKIM, bypasses MFA, and grants a persistent token without ever touching a password. Device-code phishing against GitHub follows the same shape: research reports success rates above 90% in authorized engagements, and a broadly scoped phished token yields private repos, Actions secrets, and more. Permissions abused: OAuth App authorization (broad scope), Device-code phishing Sources: Fake 'Security Alert' issues use OAuth app to hijack accounts (Tracker: live campaign feed, recheck before publishing) — https://www.bleepingcomputer.com/news/security/fake-security-alert-issues-on-github-use-oauth-app-to-hijack-accounts/ | Device code phishing explained: how attackers abuse OAuth across SaaS (Research: explains the primitive, generalizes beyond one victim) — https://appomni.com/blog/device-code-phishing-saas/ How it unfolded: 1. A fake "Security Alert" issue is filed — The campaign opened issues on roughly 12,000 repositories, styled to look like an automated GitHub security notification about the repository itself. 2. The link leads to a genuine GitHub authorization screen — The issue's call-to-action pointed to GitHub's own, real OAuth authorization page for a malicious OAuth App — not a spoofed login page — so it passed SPF/DKIM checks and looked legitimate under every check a developer could reasonably make. 3. One click grants a persistent token — Approving the prompt handed the attacker's app a persistent OAuth token. Because the developer's own already-authenticated session did the authorizing, MFA was never re-challenged and never came into play. 4. The token grants full account and code control — With that token, the attacker held exactly the access the developer had just authorized: full control over the developer's repositories and account activity, with no password ever touched. 5. Device-code phishing achieves the same outcome without an app — A related technique skips installing an OAuth App entirely: research reports success rates above 90% against users in authorized testing engagements using device-code phishing alone. Mitigations: Require security/IT approval before any new GitHub App or OAuth App installation; Review every currently installed GitHub App / OAuth App and its granted scope on a recurring basis; Train developers to treat any "authorize this app" prompt reached via an issue, email, or chat link as suspicious — it can resolve to a genuine github.com domain and still be malicious; Restrict which OAuth Apps can access organization resources — via OAuth App access restrictions; Monitor and alert on new OAuth App authorizations in the audit log — rather than only reviewing them periodically #### CircleCI session-cookie theft — December 2022 – January 2023 id: user-circleci-session-theft | url: /scenarios/circleci-session-theft Malware on an engineer's laptop stole a 2FA-backed session cookie, letting the attacker impersonate an employee who had rights to generate production tokens. Customer environment variables, tokens, and SSH keys were exfiltrated, and encryption keys made at-rest encryption moot. CircleCI rotated every customer GitHub OAuth token on their behalf. Worth noting for tiering purposes: a session cookie sat above MFA in the trust chain. Permissions abused: 2FA-backed session cookie (bypasses PAT/SSH controls) Attribution: CircleCI incident report Sources: CircleCI: rotating secrets for the January 4th incident (Postmortem: the victim's own account of the incident) — https://support.circleci.com/hc/en-us/articles/11816211460891-Rotating-Secrets-for-January-4th-Incident #### GitHub's internal repositories taken through a VS Code extension — May 2026 id: user-nx-console-github-breach | url: /scenarios/nx-console-github-breach On 18 May 2026 GitHub detected and contained the compromise of an employee's device through a poisoned VS Code extension from a third party. Aikido reports that GitHub confirmed the extension as Nx Console, which has more than 2.2 million installs and a verified publisher; the bad release was pulled from the VS Code Marketplace within about 18 minutes. That was enough. The attacker claimed about 3,800 repositories, and GitHub said the claim was directionally consistent with its own investigation. Only GitHub's internal repositories were affected, but GitHub rotated the GitHub Enterprise Server signing key as a precaution, and every GHES customer had to rotate the GPG public key used to verify upgrades. Nobody needed a GitHub permission to start this: the extension ran with the developer's own access. Permissions abused: Everything the developer's machine could reach, Developer tokens and cloned code Attribution: GitHub Sources: Investigating unauthorized access to GitHub's internal repositories (Postmortem: the victim's own account of the incident) — https://github.blog/security/investigating-unauthorized-access-to-githubs-internal-repositories/ | GitHub breached through a VS Code extension (Research: explains the primitive, generalizes beyond one victim) — https://www.aikido.dev/blog/github-breached-vs-code-extension | 5 supply chain attacks in 48 hours (Tracker: live campaign feed, recheck before publishing) — https://www.stepsecurity.io/blog/5-supply-chain-attacks-in-48-hours-why-securing-one-layer-is-not-enough How it unfolded: 1. A trusted extension ships a bad release — A malicious version of a widely installed VS Code extension was published under its real, verified publisher. Extensions update automatically, so it reached installed copies before anyone reviewed it. 2. It runs on an employee's machine — The extension executed with the developer's own rights: their tokens, their SSH keys and the repositories they had cloned or could clone. 3. Internal repositories are copied out — The attacker took roughly 3,800 of GitHub's internal repositories before GitHub contained the device on the same day. 4. Downstream keys are rotated — GitHub rotated the GHES signing key, so every Enterprise Server customer had to update the key their upgrades are verified against. Mitigations: Allow-list IDE extensions centrally — a verified publisher was not enough to keep a bad release out; Delay extension updates by a few days — the malicious release was live for minutes; a cooldown would have skipped it; Keep long-lived tokens off developer laptops — whatever the machine holds, the extension holds; Enable an IP allow list on the organization — tokens taken off the machine stop working elsewhere; Develop sensitive repositories in Codespaces or dev containers — keeps the code and credentials off the endpoint #### GlassWorm: editor extensions that steal GitHub tokens and force-push — October 2025 – March 2026 id: user-glassworm | url: /scenarios/glassworm GlassWorm started on 17 October 2025 as malicious VS Code extensions on Open VSX and the VS Code Marketplace. The loader is written in invisible Unicode characters, so it shows as blank space in editors, terminals and GitHub's diff view. Once installed, it takes GitHub, Git, npm and Open VSX credentials from the developer's machine, uses them to infect more extensions, and turns the machine into a proxy with remote access. From late 2025 the stolen GitHub tokens were also used against repositories. In March 2026 the same actor took the latest real commit in hundreds of Python repositories, added an obfuscated payload to files such as setup.py, and force-pushed it to the default branch. The author and commit message were kept, so only the committer date gives it away. Aikido found at least 151 affected repositories through code search in 3 to 9 March, and StepSecurity found hundreds more across hundreds of accounts, which it tracks as ForceMemo and ties to GlassWorm through the same command wallet. A force-push like this only works where the branch allows it. Permissions abused: Tokens stored on the developer's machine, contents:write (force-push to the default branch) Attribution: Aikido, StepSecurity Sources: Glassworm returns: invisible Unicode malware found in 150+ GitHub repositories (Research: explains the primitive, generalizes beyond one victim) — https://www.aikido.dev/blog/glassworm-returns-unicode-attack-github-npm-vscode | ForceMemo: hundreds of GitHub Python repos compromised via account takeover and force-push (Research: explains the primitive, generalizes beyond one victim) — https://www.stepsecurity.io/blog/forcememo-hundreds-of-github-python-repos-compromised-via-account-takeover-and-force-push | GlassWorm attack uses stolen GitHub tokens to force-push malware into Python repos (Tracker: live campaign feed, recheck before publishing) — https://thehackernews.com/2026/03/glassworm-attack-uses-stolen-github.html How it unfolded: 1. A malicious extension is installed — Extensions on Open VSX and the VS Code Marketplace carried a loader hidden in invisible Unicode characters. Reviewing the source shows nothing. 2. Credentials are taken from the machine — The payload reads GitHub tokens from git credential fill, VS Code's extension storage, ~/.git-credentials and environment variables, along with npm and Open VSX tokens. 3. The worm publishes itself — Stolen registry tokens are used to push infected versions of other extensions the victim maintains. 4. Repositories are rewritten with a force-push — The attacker rebases the latest commit with the payload added and force-pushes it to the default branch. The original author and message stay, so the history looks unchanged at a glance. Mitigations: Block force pushes on default branches with a ruleset — the GitHub side of the campaign was nothing but force-pushes; Require signed commits — a rewritten commit loses the original signature; Allow-list IDE extensions centrally — the way in was an extension the developer chose to install; Keep long-lived tokens off developer laptops and set a maximum token lifetime — every token on the machine was taken; Alert on force pushes in the audit log — the committer date is the only visible trace ### Organization (14) #### "Pwn requests": pull_request_target & workflow_run — Ongoing id: org-pwn-requests | url: /scenarios/pwn-requests The structural flaw behind most of the incidents on this page. pull_request deliberately withholds write permissions and secrets; pull_request_target was created to restore them, and becomes hazardous the moment maintainers treat it as a normal CI trigger. workflow_run carries the same risk and gets far less attention: the triggered workflow can access secrets and write tokens even when the workflow that triggered it could not. One industry scan found 38% of organizations have a workflow vulnerable to script injection or dangerous triggers. Since 8 December 2025, pull_request_target always runs the workflow file from the default branch and checks out the default branch, whatever branch the pull request targets. That closes attacks through an old, vulnerable workflow left on another branch. It does not help when the default branch workflow itself checks out and runs the pull request's code. Permissions abused: pull_request_target write + secrets access, workflow_run inherited privilege Attribution: Datadog Sources: The case for GitHub Actions security after recent supply chain attacks (Research: explains the primitive, generalizes beyond one victim) — https://securitylabs.datadoghq.com/articles/case-for-github-actions-security/ | Vulnerable GitHub Actions workflows: privilege escalation (Research: explains the primitive, generalizes beyond one victim) — https://www.legitsecurity.com/blog/github-privilege-escalation-vulnerability | Actions pull_request_target and environment branch protections changes (Advisory: vendor/CVE record with affected version ranges) — https://github.blog/changelog/2025-11-07-actions-pull_request_target-and-environment-branch-protections-changes/ | Keeping your GitHub Actions and workflows secure: preventing pwn requests (Research: explains the primitive, generalizes beyond one victim) — https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ #### Ultralytics build cache poisoning — December 2024 id: org-ultralytics | url: /scenarios/ultralytics Template injection via a crafted branch name against a format.yml workflow using pull_request_target. Secrets including secrets._GITHUB_TOKEN were exfiltrated and the build cache poisoned to inject a cryptominer into v8.3.41 and v8.3.42. The read from researchers was that the attackers had write access but preferred extracting CI/CD secrets, then used them to poison the cache and ship malicious releases. Permissions abused: pull_request_target template injection, secrets._GITHUB_TOKEN Attribution: GitGuardian Sources: zizmor would have caught the Ultralytics workflow vulnerability (Research: explains the primitive, generalizes beyond one victim) — https://blog.yossarian.net/2024/12/06/zizmor-ultralytics-injection | Ultralytics AI pwn request supply chain attack (Research: explains the primitive, generalizes beyond one victim) — https://snyk.io/blog/ultralytics-ai-pwn-request-supply-chain-attack/ | The Ultralytics supply chain attack: how it happened, how to prevent (Research: explains the primitive, generalizes beyond one victim) — https://www.legitsecurity.com/blog/the-ultralytics-supply-chain-attack-how-it-happened-how-to-prevent | Ultralytics AI library hacked via GitHub for cryptomining (Tracker: live campaign feed, recheck before publishing) — https://www.wiz.io/blog/ultralytics-ai-library-hacked-via-github-for-cryptomining | The Ultralytics supply chain attack: connecting the dots (Research: explains the primitive, generalizes beyond one victim) — https://blog.gitguardian.com/the-ultralytics-supply-chain-attack-connecting-the-dots-with-gitguardians-public-monitoring-data/ #### Nx "s1ngularity" — August 2025 id: org-nx-s1ngularity | url: /scenarios/nx-s1ngularity The injected script extracted a read/write GitHub token, a consequence of the workflow permissions setting, then created a branch replacing the CI script, triggered the publish workflow against that branch via the API using workflow_dispatch, and deleted branches and workflow runs to clean up. When publish ran, it had NPM_TOKEN in scope and exfiltrated it. The PR targeted an outdated branch that still carried the vulnerable workflow after it had been reverted on master. In phase two, the stolen GitHub tokens were used to flip thousands of private repositories to public. Downstream, one threat-intel group rode the consequences to full AWS administrator access at a victim org in under 72 hours, via OIDC trust chains and a CloudFormation IAM capability chain. No zero-days were involved. Permissions abused: Read/write GITHUB_TOKEN (permissive workflow default), actions:write (workflow_dispatch), NPM_TOKEN Attribution: Mandiant (UNC6426, downstream follow-on) Sources: s1ngularity: what happened, how we responded, what we learned (Postmortem: the victim's own account of the incident) — https://nx.dev/blog/s1ngularity-postmortem | Learnings from recent npm compromises (Research: explains the primitive, generalizes beyond one victim) — https://securitylabs.datadoghq.com/articles/learnings-from-recent-npm-compromises/ | OIDC trust chain abuse: cloud takeover (Research: explains the primitive, generalizes beyond one victim) — https://labs.cloudsecurityalliance.org/research/csa-research-note-oidc-trust-chain-abuse-cloud-takeover-2026/ How it unfolded: 1. An injected script extracts a read/write token — A malicious script running in CI took advantage of a permissive workflow-level default and pulled out a GITHUB_TOKEN scoped for both read and write. 2. The CI script itself is replaced on a new branch — Using that token, the attacker created a new branch that swapped in a tampered version of the legitimate CI script. 3. The publish workflow is triggered directly via the API — Rather than waiting for a normal push, the attacker invoked workflow_dispatch through the GitHub API to run the publish workflow against their tampered branch — one that still carried a vulnerable workflow definition already reverted on master. 4. NPM_TOKEN is exfiltrated during the tampered publish — Because the publish workflow had NPM_TOKEN in scope, running it against the attacker's branch exfiltrated that token directly. The attacker then deleted the branch and workflow run history to reduce the trail. 5. Phase two: stolen tokens flip repos public — With the stolen GitHub tokens, the attacker flipped thousands of private repositories to public within a short window. 6. Downstream: an OIDC trust chain reaches full AWS admin — A separate threat-intel group rode the exposure to full AWS administrator access at a victim org in under 72 hours, chaining OIDC trust relationships with a CloudFormation IAM capability — no zero-day involved. Mitigations: Set the default GITHUB_TOKEN permission to read-only at the org level — a compromised workflow can't write branches or trigger further jobs on its own; Require CODEOWNERS review on CI/CD script changes — a replaced build script can't merge unseen; Restrict who can trigger workflow_dispatch on publish/release workflows — limit it to a minimal, protected set of actors; Prevent branch and workflow-run deletion on protected branches — cleanup steps can't erase the trail; Migrate cloud credentials from static keys to OIDC federation scoped tightly to repo + branch — closes off the trust-chain pivot into cloud infrastructure #### Self-hosted runner takeovers (PyTorch and others) — 2023–2024 id: org-self-hosted-runner-attacks | url: /scenarios/self-hosted-runner-attacks A fork PR makes you a "previous contributor," after which workflows run on the self-hosted runner without approval; default runner setup is non-ephemeral, so access persists. On PyTorch, researchers confirmed root on the runners and pulled GITHUB_TOKENs with extensive write access out of .git/config during active workflows. Against GitHub's own actions/runner-images, the access reached internal macOS private cloud vCenter and Azure credentials, and could have poisoned every hosted runner base image. TensorFlow's fix was requiring approval for all fork PRs and setting GITHUB_TOKEN to read-only on self-hosted runners. Permissions abused: Fork PR access to a non-ephemeral self-hosted runner, GITHUB_TOKEN with write scope Attribution: Adnan Khan / John Stawinski Sources: One supply chain attack to rule them all (Research: explains the primitive, generalizes beyond one victim) — https://adnanthekhan.com/2023/12/20/one-supply-chain-attack-to-rule-them-all/ | TensorFlow supply chain compromise via self-hosted runner attack (Research: explains the primitive, generalizes beyond one victim) — https://www.praetorian.com/blog/tensorflow-supply-chain-compromise-via-self-hosted-runner-attack/ | PyTorch supply chain compromise (Research: explains the primitive, generalizes beyond one victim) — https://www.stepsecurity.io/blog/pytorch-supply-chain-compromise | GitHub, PyTorch and more orgs vulnerable to self-hosted runner attacks (Research: explains the primitive, generalizes beyond one victim) — https://www.legitsecurity.com/blog/github-pytorch-and-more-organizations-found-vulnerable-to-self-hosted-runner-attacks #### GhostAction — September 2025, returned 2026 id: org-ghostaction | url: /scenarios/ghostaction 327 compromised GitHub user accounts were used to inject workflows disguised as "Add GitHub Actions Security workflow" across 817 repositories, stealing 3,325 secrets. The actors enumerated secret names from legitimate workflow files first, then hardcoded those names into their own workflow, harvesting PyPI, npm, DockerHub, GitHub, Cloudflare, and AWS credentials. It came back in 2026. GitGuardian found the malicious workflow in 772 public repositories belonging to 373 users and organizations between 31 August and 30 September 2026, aimed at 2,577 secrets; GitHub held most of the runs for approval, and by 5 October only 16% of the repositories had removed the workflow. From 7 October Socket saw more than 500 accounts committing it to tens of thousands of repositories. Permissions abused: Injected Actions workflow, Harvested repo/CI secrets by name Sources: The GhostAction campaign: 3,325 secrets stolen (Research: explains the primitive, generalizes beyond one victim) — https://blog.gitguardian.com/ghostaction-campaign-3-325-secrets-stolen/ | GhostAction returns: GitHub Actions supply chain attack (Research: explains the primitive, generalizes beyond one victim) — https://blog.gitguardian.com/ghostaction-github-actions-supply-chain-attack-returns/ | GhostAction: cloud credentials targeted across tens of thousands of repositories (Tracker: live campaign feed, recheck before publishing) — https://socket.dev/blog/ghostaction-cloud-credentials | GhostAction returns (Tracker: live campaign feed, recheck before publishing) — https://www.stepsecurity.io/blog/ghostaction-returns How it unfolded: 1. 327 GitHub accounts are compromised — The campaign started from 327 already-compromised GitHub accounts, most likely via credential stuffing or reused leaked passwords rather than any GitHub platform vulnerability. 2. Existing workflows are read to learn real secret names — Before writing anything malicious, the actors read each target repository's own workflow files to learn exactly which secret names it referenced — PyPI, npm, DockerHub, GitHub, Cloudflare, AWS — instead of guessing. 3. A convincingly-named workflow is injected — Using the compromised accounts' write access, they committed a new workflow disguised as "Add GitHub Actions Security workflow" — a name designed to look like a routine hardening change — into 817 repositories. 4. Secrets are harvested by their real names — Because the injected workflow referenced the exact secret names it had just enumerated, every one of them resolved and was exfiltrated: 3,325 secrets in total, across six credential types. Mitigations: Enforce MFA on every account, including bots/service accounts — compromised accounts, not a code vulnerability, were the entry point; Require CODEOWNERS review on any change under .github/workflows/ — a new workflow file can't land unreviewed; Enable secret scanning and push protection — flags exfiltration attempts in an injected workflow; Inventory every Actions secret and its scope on a recurring basis — the campaign relied on being able to correctly guess names from existing workflows; Scope secrets to the narrowest repo/environment that actually needs them — instead of one org-wide secret reused everywhere #### Toptal repo-visibility flip and wiper — July 2025 id: org-toptal | url: /scenarios/toptal 73 repositories in the org went public within a five-minute window; at least 10 contained code to exfiltrate GitHub auth tokens and destroy victim systems. The payloads sat in preinstall/postinstall hooks, sending the token to a webhook endpoint then recursively deleting files on Windows and Linux. About 5,000 downloads occurred before removal. This was an org-owner compromise, and the very first action taken was flipping repo visibility. Permissions abused: Org owner (bulk repo visibility change), npm pre/postinstall token exfiltration Sources: Toptal's GitHub organization hijacked (Research: explains the primitive, generalizes beyond one victim) — https://socket.dev/blog/toptal-s-github-organization-hijacked-10-malicious-packages-published | Hackers breach Toptal GitHub account, publish malicious npm packages (Tracker: live campaign feed, recheck before publishing) — https://www.bleepingcomputer.com/news/security/hackers-breach-toptal-github-account-publish-malicious-npm-packages/ #### Salesloft Drift GitHub → AWS pivot — March – June 2025 id: org-salesloft-drift | url: /scenarios/salesloft-drift Investigators found the threat actor UNC6395 in the Salesloft GitHub account from March through June 2025, downloading content from multiple repositories, adding a guest user, and establishing workflows. They then moved to Drift's AWS environment, took OAuth tokens for customer integrations, and used those to reach customer data. Downstream victims included Zscaler, Palo Alto Networks, Cloudflare, PagerDuty, and others. The interesting detail for the tier model: "add a guest user" and "establish workflows" are both persistence primitives that org admin grants. Permissions abused: Org admin: add guest collaborator, Org admin: establish workflows, OAuth tokens for downstream integrations Attribution: Mandiant (UNC6395) Sources: Anatomy of the Salesloft breach (Research: explains the primitive, generalizes beyond one victim) — https://permiso.io/blog/anatomy-of-the-salesloft-breach | GitHub account compromise led to Salesloft Drift breach (Tracker: live campaign feed, recheck before publishing) — https://thehackernews.com/2025/09/github-account-compromise-led-to.html #### Shai-Hulud self-propagating worm (1.0 & 2.0) — September 2025 / November 2025 / May 2026 id: org-shai-hulud | url: /scenarios/shai-hulud The worm made GitHub permissions the propagation mechanism. It validated available GitHub tokens and their permissions, then queried /user/repos?affiliation=owner,collaborator,organization_member to find repos where the compromised account had enough privilege. It planted shai-hulud-workflow.yml, exfiltrated ${{ toJSON(secrets) }} to a webhook, and made repos public, including any the victim merely held tokens for. The 2.0 variant registered the infected host as a self-hosted Actions runner named SHA1HULUD and planted an injection-prone workflow at .github/workflows/discussion.yaml, so opening a GitHub Discussion gave arbitrary command execution on the host. 2.0 compromised 796 npm packages, with credential exfiltration confirmed across more than 500 GitHub users spanning 150+ organizations. A smaller descendant, known as Mini Shai-Hulud, hit in May 2026: on 19 May one developer account takeover led to more than 630 malicious versions across 317 npm packages in about 20 minutes, including AntV packages. The TanStack compromise a week earlier came through a different door, Actions cache poisoning (see the cache poisoning incident). Permissions abused: Enumerated token permissions via /user/repos, contents:write (public flip, workflow injection), Self-hosted runner registration (2.0) Sources: Inside Shai-Hulud's maw: how the npm worm exploits and propagates (Research: explains the primitive, generalizes beyond one victim) — https://checkmarx.com/zero-post/inside-shai-huluds-maw-how-the-npm-worm-exploits-and-propagates/ | Shai-Hulud: self-replicating worm compromises 500+ npm packages (Research: explains the primitive, generalizes beyond one victim) — https://www.stepsecurity.io/blog/ctrl-tinycolor-and-40-npm-packages-compromised | Shai-Hulud 2.0: host registered as SHA1HULUD runner (Tracker: live campaign feed, recheck before publishing) — https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/ | Shai-Hulud npm supply chain attack (Tracker: live campaign feed, recheck before publishing) — https://unit42.paloaltonetworks.com/npm-supply-chain-attack/ | npm supply chain attack: GitHub API exploitation (Tracker: live campaign feed, recheck before publishing) — https://www.trendmicro.com/en_us/research/25/i/npm-supply-chain-attack.html | Hackers have compromised dozens of popular open source packages in an ongoing supply chain attack (Tracker: live campaign feed, recheck before publishing) — https://techcrunch.com/2026/05/19/hackers-have-compromised-dozens-of-popular-open-source-packages-in-an-ongoing-supply-chain-attack/ How it unfolded: 1. Available GitHub tokens are validated and scoped — After the worm reached a host — typically via a compromised npm package — its payload validated whatever GitHub tokens were reachable there and checked exactly what each one could do. 2. Repos are enumerated by privilege, not just ownership — It queried /user/repos?affiliation=owner,collaborator,organization_member to find every repository where the compromised account held enough privilege to act — far beyond repos it directly owned. 3. A malicious workflow is planted and secrets exfiltrated — It planted shai-hulud-workflow.yml in each reachable repo and exfiltrated the full secrets context (${{ toJSON(secrets) }}) to an external webhook. 4. Repositories are flipped public to maximize spread — It made repositories public, including ones the compromised account merely held a token for rather than owned outright, maximizing both exposure and further propagation surface. 5. 2.0: the infected host registers itself as a runner — The follow-up variant registered the infected machine as a self-hosted Actions runner named SHA1HULUD, turning a single compromised developer machine into infrastructure the attacker could keep using. 6. 2.0: opening a GitHub Discussion becomes remote code execution — It planted an injection-prone workflow at .github/workflows/discussion.yaml, so simply opening a GitHub Discussion on an affected repo triggered arbitrary command execution on that self-hosted runner — ultimately compromising 796 npm packages, with credential exfiltration confirmed across 500+ GitHub users spanning 150+ organizations. Mitigations: Disallow self-hosted runners on public repositories — the 2.0 variant's persistence relied entirely on registering one; Prevent branch and workflow-run deletion — a propagating worm's cleanup steps can't erase the evidence trail; Set the default GITHUB_TOKEN permission to read-only at the org level — limits what any single compromised token can enumerate and write; Enable secret scanning and push protection — catches exfiltration attempts in an injected workflow; Audit and revoke stale PATs/SSH keys on a recurring basis — compromise of one maintainer's npm-side token was enough to start the chain #### hackerbot-claw autonomous agent RCE — February 2026 id: org-hackerbot-claw | url: /scenarios/hackerbot-claw An autonomous agent doing this at scale. It abused unsafe pull_request_target, unsanitized inputs, dynamic shell execution, and overprivileged GITHUB_TOKEN to get RCE on GitHub-hosted runners across at least six repositories, exfiltrating write-capable tokens in some cases. Targets included Trivy, avelino/awesome-go, microsoft/ai-discovery-agent, DataDog/datadog-iac-scanner, project-akri/akri, RustPython, and ambient-code/platform, where the vector was prompt injection via CLAUDE.md, which was blocked. On Trivy it stole a PAT through a pull_request_target workflow and used it to take over the repository. Permissions abused: Unsafe pull_request_target, Overprivileged GITHUB_TOKEN, PAT exfiltration Sources: hackerbot-claw: AI-powered bot exploiting GitHub Actions (Research: explains the primitive, generalizes beyond one victim) — https://www.stepsecurity.io/blog/hackerbot-claw-github-actions-exploitation | HackerBot-Claw campaign timeline (Tracker: live campaign feed, recheck before publishing) — https://orca.security/resources/blog/hackerbot-claw-github-actions-attack/ How it unfolded: 1. Public repositories are scanned at scale — Over a single week (February 21–28, 2026), an autonomous, AI-driven bot systematically scanned public GitHub repositories for exploitable CI/CD workflow patterns, rather than hand-picking one victim. 2. Vulnerable patterns are matched automatically — The bot classified targets by which of five exploitation techniques applied — unsafe pull_request_target, unsanitized inputs, dynamic shell execution, or an overprivileged GITHUB_TOKEN among them — and applied the matching attack per repository. 3. A crafted pull request gets code execution on a hosted runner — Where pull_request_target combined with unsanitized input reaching a shell, a single PR was enough to achieve remote code execution on a GitHub-hosted runner in that repository's privileged context. 4. Write-capable tokens are exfiltrated — Running with an overprivileged GITHUB_TOKEN, the bot exfiltrated write-capable tokens from at least six repositories, including Trivy, awesome-go, and projects at Microsoft, DataDog, and the CNCF. 5. A stolen token is reused for outright takeover — On Trivy specifically, the exfiltrated PAT was reused to take over the repository. On another target, a prompt-injection attempt via CLAUDE.md was blocked before it could do damage. Mitigations: Require approval for first-time/external contributor workflow runs on forks — a single PR can't auto-execute; Audit every use of pull_request_target — remove or restructure any that also check out and run the PR's own code; Set the default GITHUB_TOKEN permission to read-only at the org level; Sanitize any PR-controlled input (titles, branch names, file contents) — before it can reach a shell command; Treat repository files an autonomous coding agent might read (CLAUDE.md, AGENTS.md, etc.) as untrusted input — if such an agent runs against the repo #### TeamPCP tag force-overwrite campaign — March – April 2026 id: org-teampcp | url: /scenarios/teampcp The most complete demonstration of tag-write as a permission tier. On 19 March 2026 (CVE-2026-33634), continuing an attack that began in late February, the actors used credentials with tag-write privileges to publish Trivy v0.69.4 and force-overwrite 76 of 77 tags in aquasecurity/trivy-action and all seven in aquasecurity/setup-trivy, redirecting trusted versions to malicious commits with no visible change in release metadata. Checkmarx's KICS GitHub Action followed on 23 March, when all 35 of its tags were moved through a compromised service account, and on 24 March LiteLLM versions 1.82.7 and 1.82.8 were uploaded straight to PyPI with credentials taken from LiteLLM's own CI, where Trivy ran. Researchers traced at least seven waves through to @bitwarden/cli hijacked with stolen KICS npm tokens, and elementary-data via Actions script injection. Each compromised environment yielded the credentials for the next. Permissions abused: Tag-write privilege (mass tag rewrite), Stolen PATs/npm tokens chained across projects Attribution: Trend Micro Sources: CVE-2026-33634 (Trivy, trivy-action and setup-trivy) (Advisory: vendor/CVE record with affected version ranges) — https://nvd.nist.gov/vuln/detail/cve-2026-33634 | LiteLLM security update, March 2026 (Postmortem: the victim's own account of the incident) — https://docs.litellm.ai/blog/security-update-march-2026 | Detecting, investigating, defending against Trivy supply chain compromise (Tracker: live campaign feed, recheck before publishing) — https://www.microsoft.com/en-us/security/blog/2026/03/24/detecting-investigating-defending-against-trivy-supply-chain-compromise/ | TeamPCP: seven waves of credential chaining (Tracker: live campaign feed, recheck before publishing) — https://www.trendmicro.com/en_us/research/26/e/analyzing-teampcp-supply-chain-attacks.html | TeamPCP isn't done (Tracker: live campaign feed, recheck before publishing) — https://www.endorlabs.com/learn/teampcp-isnt-done | KICS GitHub Action compromised (Tracker: live campaign feed, recheck before publishing) — https://www.wiz.io/blog/teampcp-attack-kics-github-action | TeamPCP campaign overview (Tracker: live campaign feed, recheck before publishing) — https://arcticwolf.com/resources/blog/teampcp-supply-chain-attack-campaign-targets-trivy-checkmarx-kics-and-litellm-potential-downstream-impact-to-additional-projects/ How it unfolded: 1. Tag-write privilege force-overwrites 76 of 77 trivy-action tags — Using credentials with tag-write access, the actors force-overwrote almost every release tag in aquasecurity/trivy-action (76 of 77) and all seven tags in aquasecurity/setup-trivy, redirecting every consumer pinning by tag to malicious commits — with no visible change in release metadata. 2. Checkmarx's KICS Action is compromised via stolen PATs — Days later, on March 23, the actors used stolen personal access tokens to compromise Checkmarx's KICS GitHub Action the same way, alongside OpenVSX extensions and LiteLLM on PyPI. 3. Stolen npm tokens chain into the next project — Credentials harvested from the KICS compromise — specifically npm tokens — were reused to hijack @bitwarden/cli. 4. A separate wave uses plain script injection — elementary-data was compromised through ordinary Actions script injection, a different technique from the tag-overwrite chain used elsewhere in the campaign. 5. Each compromised project funds the next wave — Researchers traced at least seven distinct waves, with each compromise's stolen credentials chaining directly into the next target rather than the campaign relying on a single initial foothold. Mitigations: Apply tag-protection rulesets — release tags can't be force-moved after publication; Pin third-party Actions to a full commit SHA rather than a tag — a retargeted upstream tag can't silently change what your workflow runs; Set an organization-wide maximum PAT lifetime and rotate on schedule — stolen long-lived PATs were the mechanism chaining each wave to the next; Audit and revoke stale PATs and npm tokens on a recurring basis; Write and rehearse a credential-rotation runbook — this class of campaign rewards whoever rotates slowest #### Actions cache poisoning (Angular, TanStack) — Ongoing id: org-cache-poisoning | url: /scenarios/cache-poisoning Actions caches are shared between PR workflows and release workflows. One researcher's angular/dev-infra chain was script injection via ${{ github.head_ref }} in a pull_request_target workflow, then cache filling with a purpose-built tool. TanStack saw 84 malicious versions across 42 @tanstack/* packages from the same class of chain on 11 May 2026: a pull_request_target workflow ran a fork's code, which poisoned the pnpm store in the shared cache, and the release workflow restored it and leaked its npm OIDC token. There is no CVE for any of this; the cache is behaving exactly as designed. Permissions abused: Fork PR write into a cache shared with release workflows Attribution: Adnan Khan Sources: TanStack npm supply chain compromise postmortem (Postmortem: the victim's own account of the incident) — https://tanstack.com/blog/npm-supply-chain-compromise-postmortem | Cache poisoning through pull_request_target: the TanStack incident (Research: explains the primitive, generalizes beyond one victim) — https://safedep.io/tanstack-github-actions-cache-poisoning/ | The cache that bites back: GitHub Actions cache poisoning attacks (Research: explains the primitive, generalizes beyond one victim) — https://hivesecurity.gitlab.io/blog/github-actions-cache-poisoning-supply-chain/ How it unfolded: 1. A pull request opens the door — An attacker opens an ordinary-looking PR against a workflow triggered on pull_request_target that checks out the PR's own head commit. That trigger runs with the base repository's secrets and cache access, even though the code being executed is the attacker's. 2. The shared build cache gets poisoned — During that run, the attacker's code writes a tampered dependency store into GitHub Actions' cache under the same key a legitimate release build would use — cache keys are derived from the lockfile hash and scoped to the whole repository, not to the branch or workflow that wrote them. 3. A real release restores the poisoned entry — Days or weeks later, a maintainer pushes to main and the publish workflow runs. It restores the cache entry with the matching key — the poisoned one — and installs from it, with no indication anything is wrong. 4. The publishing token is extracted from memory — The tampered install code executes inside the trusted release job and dumps runtime memory, recovering the OIDC token the workflow had just obtained to authenticate to the npm registry. 5. 84 malicious versions ship before detection — Using the stolen token, the attacker publishes 84 tampered versions across 42 @tanstack/* packages — including Query, Router, and Form, each with millions of weekly downloads — before the versions are caught and yanked. Mitigations: Audit every pull_request_target workflow — remove or isolate any that also check out and run the PR's own code; Pin actions/cache, actions/checkout, and every third-party action to a full commit SHA — not a mutable tag; Separate cache scopes for PR workflows and release workflows — distinct key prefixes (e.g. pr- vs release-) so a fork PR can never write into a key a release job will later restore; Restrict which workflows can request an OIDC publishing token — using environment protection rules; Disable caching entirely in release/publish workflows and install fresh every time — the cold-install cost is negligible next to the exposure #### prt-scan: automated pull_request_target attacks — March – April 2026 id: org-prt-scan | url: /scenarios/prt-scan The second AI-assisted campaign of 2026 against pull_request_target, after hackerbot-claw. Wiz traced more than 500 malicious pull requests in six waves from six accounts, starting on 11 March 2026. The largest burst came from one account that opened more than 475 pull requests in 26 hours from 2 April. Early waves used crude shell scripts; later ones used AI-generated payloads written for each repository's language, including a stage that tried to get past label-gated workflows. Fewer than one in ten attempts worked, but at least two npm packages were published with the stolen credentials. The attacker never needed access to a target repository, only a fork and a workflow that ran its code with secrets. Permissions abused: pull_request_target write token + secrets, Fork pull request (no access to the target needed) Attribution: Wiz Research Sources: Six accounts, one actor: inside the prt-scan supply chain campaign (Research: explains the primitive, generalizes beyond one victim) — https://www.wiz.io/blog/six-accounts-one-actor-inside-the-prt-scan-supply-chain-campaign | prt-scan: GitHub Actions exfiltration campaign (Research: explains the primitive, generalizes beyond one victim) — https://safedep.io/prt-scan-github-actions-exfiltration-campaign/ | CSA research note: GitHub Actions prt-scan supply chain campaign (Tracker: live campaign feed, recheck before publishing) — https://labs.cloudsecurityalliance.org/research/csa-research-note-github-actions-prt-scan-supply-chain-2026/ | AI-assisted supply chain attack targets GitHub (Tracker: live campaign feed, recheck before publishing) — https://darkreading.com/application-security/ai-assisted-supply-chain-attack-targets-github How it unfolded: 1. Vulnerable workflows are found by scanning — The attacker searched public GitHub for workflows triggered on pull_request_target that check out or run code from the pull request. 2. Pull requests are opened in bulk — Each pull request came from a branch named prt-scan- plus 12 hex characters, titled "ci: update build configuration". One account opened more than 475 of them in about a day. 3. The payload adapts to the target — Later waves generated payloads in the repository's own language and tried to get around label-gated workflows, so a common piece of hardening was no longer enough. 4. Secrets go out, packages get published — Where the workflow ran the code, the payload took the workflow's secrets. At least two npm packages were then published with them. Mitigations: Audit every pull_request_target and workflow_run workflow — the campaign only worked where these ran code from the pull request; Set the default GITHUB_TOKEN to read-only — limits what a successful run can change; Require approval for workflow runs from forks and new contributors — the accounts were new and had never contributed; Search your repositories for prt-scan- branches and the PR title — the indicators are specific and easy to find #### Megalodon: mass backdooring of CI workflows — May 2026 id: org-megalodon | url: /scenarios/megalodon On 18 May 2026, in about six hours, throwaway accounts pushed 5,718 commits into 5,561 public repositories, straight to the default branch and without pull requests. The commits claimed to come from bots such as build-bot and ci-bot, but no real account was linked to those names. A mass variant added a workflow that ran on push and pull_request_target and sent secrets out. A targeted variant replaced existing workflows with one that only runs on workflow_dispatch, a dormant backdoor the attacker can fire later through the API. At least one project, Tiledesk, then published npm versions built from the poisoned source; the npm account itself was never touched. How the attacker got push access to so many repositories is not established in the primary write-ups. Permissions abused: Push to the default branch without review, workflows: write (new and replaced workflows) Attribution: SafeDep, StepSecurity Sources: Megalodon: mass GitHub repo backdooring via CI workflows (Research: explains the primitive, generalizes beyond one victim) — https://safedep.io/megalodon-mass-github-repo-backdooring-ci-workflows/ | Megalodon: mass GitHub Actions secret exfiltration across 5,500 public repositories (Research: explains the primitive, generalizes beyond one victim) — https://stepsecurity.io/blog/megalodon-mass-github-actions-secret-exfiltration-across-5-500-public-repositories How it unfolded: 1. Push access is obtained (how is unknown) — The commits were pushed directly to default branches. A stolen token or deploy key is the likely explanation, but neither write-up confirms it. 2. Commits pose as CI bots — The author name was set to things like build-bot, auto-ci or pipeline-bot. Git lets anyone write any author name; without signed commits nothing tells them apart from a real bot. 3. A workflow steals secrets on the next run — The mass variant added a workflow triggered on push and pull_request_target that exfiltrated the repository's secrets. 4. A dormant workflow is left for later — The targeted variant replaced existing workflows with one that only runs on workflow_dispatch, so it does nothing until the attacker triggers it. 5. A package ships from the poisoned source — Tiledesk's maintainer published several npm versions from the backdoored repository before it was noticed. Mitigations: Protect the default branch with a ruleset that requires a pull request — every commit went straight to the default branch; Require code owner review for .github/workflows — a new or replaced workflow can't land on its own; Require signed commits — forged bot author names stand out; Alert on new workflows and unexpected workflow_dispatch runs — the dormant variant only shows itself when fired; Restrict classic PATs and set a maximum token lifetime — limits what one stolen token can push to #### ChainDrop: a hijacked maintainer account publishes through trusted publishing — August 2026 id: org-chaindrop | url: /scenarios/chaindrop On 4 August 2026 an attacker used the GitHub account of the maintainer of keyv and cacheable, packages with well over 100 million weekly npm downloads together. The commits went straight to main, unsigned, which StepSecurity reads as a stolen token or session used through the API; how the account was taken is not known. The attacker then cut releases, and the projects' own release workflows published the malware through npm trusted publishing, with valid provenance. No npm token was stolen for this part. The payload, a descendant of Shai-Hulud 2.0, steals npm and GitHub tokens, cloud keys and SSH keys from developer machines and CI runners, publishes itself to every package the victim can publish, and in victims' repositories adds a workflow that dumps the secrets context, then deletes the branch. It also writes .vscode/tasks.json and .claude/settings.json into repositories to run again later. StepSecurity counted 2,212 versions of 444 packages within about four hours. Provenance showed which commit was built, not whether anyone should have pushed it. Permissions abused: contents:write (push to main, create release tags), workflows:write (secrets-dumping workflow in victims' repositories), The project's own trusted publishing workflow Attribution: StepSecurity Sources: ChainDrop npm worm: Bun-loaded CI/CD credential harvester with Ethereum dead-drop C2 (Research: explains the primitive, generalizes beyond one victim) — https://www.stepsecurity.io/blog/chaindrop-npm-worm | ChainDrop: the Keyv and Cacheable npm supply chain attack (Tracker: live campaign feed, recheck before publishing) — https://insights.integrity360.com/threat-advisories/chaindrop-the-keyv-and-cacheable-npm-supply-chain-attack How it unfolded: 1. The maintainer's GitHub account is used — Unsigned commits appear on main of keyv, cacheable and related repositories under the maintainer's name. How the attacker got the account or token is not established. 2. A release runs the real publish workflow — The attacker creates releases, and each project's release workflow publishes to npm through trusted publishing. The packages carry valid provenance because the workflow really did build them. 3. Installs steal credentials — A preinstall script downloads the Bun runtime and runs the payload, which collects npm and GitHub tokens, cloud credentials and SSH keys from the machine or runner. 4. The worm spreads and digs in — With the stolen tokens it publishes infected versions of the victim's own packages, adds a workflow that writes the secrets context to an artifact and then removes the branch, and plants editor and agent config files that run it again. Mitigations: Require a pull request on the default branch, with no bypass for maintainers — every malicious commit went straight to main; Restrict who can create release tags with a ruleset — a release was all it took to start the publish workflow; Put the publish job behind an environment with required reviewers — trusted publishing ran for whoever triggered it; Require signed commits — the attacker's commits were unsigned; Use security keys or passkeys for maintainer accounts — the maintainer's account was the single point of failure ### Enterprise (6) #### CVE-2024-4985: SAML auth bypass — Fixed 2024 id: ent-cve-2024-4985 | url: /scenarios/cve-2024-4985 Rated CVSS 10.0. With SAML SSO and encrypted assertions enabled, an attacker could forge a SAML response to provision or access a site administrator account, with no prior authentication required. All GHES versions before 3.13.0 were affected; fixed in 3.9.15, 3.10.12, 3.11.10, and 3.12.4. Site admin means every repository, secret, and CI/CD pipeline on the instance. A single forged assertion reaches Tier 0 with zero credentials. Permissions abused: Forged SAML assertion (pre-authentication) Sources: GHSA-5pw9-f9r4-mv2r: CVE-2024-4985, SAML forge to site admin (Advisory: vendor/CVE record with affected version ranges) — https://github.com/advisories/GHSA-5pw9-f9r4-mv2r | GitHub fixes maximum severity GHES auth bypass (Tracker: live campaign feed, recheck before publishing) — https://www.helpnetsecurity.com/2024/05/23/cve-2024-4985/ #### CVE-2025-11578: pre-receive hook symlink escape — Fixed 2025 id: ent-cve-2025-11578 | url: /scenarios/cve-2025-11578 An authenticated enterprise admin could escape via symlink in pre-receive hook environments, replace system binaries during hook cleanup, and add an SSH key to root's authorized_keys, turning enterprise-admin access into root on the appliance itself. Permissions abused: Enterprise admin → root via symlink escape Sources: CVE-2025-11578: enterprise admin to root via pre-receive symlink escape (Advisory: vendor/CVE record with affected version ranges) — https://github.com/advisories?query=CVE-2025-11578 #### CVE-2025-3509: pre-receive hook RCE via port reuse — Fixed 2025 id: ent-cve-2025-3509 | url: /scenarios/cve-2025-3509 Pre-receive hook RCE via dynamically allocated ports available during operations such as hot-patch upgrades, requiring either site admin or a user able to modify repos with hooks already enabled. Permissions abused: Site admin, or repo-hook-modify access, during a hot-patch window Sources: GHSA-g3x6-r9w9-mcxx: CVE-2025-3509, pre-receive hook RCE (Advisory: vendor/CVE record with affected version ranges) — https://github.com/advisories/GHSA-g3x6-r9w9-mcxx #### CVE-2026-3854: push-option metadata RCE — Fixed 2026 id: ent-cve-2026-3854 | url: /scenarios/cve-2026-3854 Rated CVSS 8.7. A low-privileged user with push access to any repository, including one they created themselves, could inject metadata fields through crafted git push option values and achieve RCE on the instance. Notable because the starting privilege required is minimal: ordinary repo push, not admin. Permissions abused: Repo push access (any repo, including self-created) Sources: GHSA-64fw-jx9p-5j24: CVE-2026-3854, push option injection to RCE (Advisory: vendor/CVE record with affected version ranges) — https://github.com/advisories/GHSA-64fw-jx9p-5j24 | GHES vulnerability: find impacted assets (Tracker: live campaign feed, recheck before publishing) — https://www.runzero.com/blog/github-enterprise-server/ #### CVE-2024-1374: Management Console template injection — Fixed 2024 id: ent-cve-2024-1374 | url: /scenarios/cve-2024-1374 A Management Console editor reached root SSH on the appliance via Nomad template injection in the audit log forwarding configuration, a mid-tier admin console role escalating straight to host-level control. Permissions abused: Management Console editor role → root Sources: HackerOne #2332623: editor to root SSH via nomad template injection (CVE-2024-1374) (Advisory: vendor/CVE record with affected version ranges) — https://hackerone.com/reports/2332623 #### CVE-2022-23732: Management Console path traversal / CSRF bypass — Fixed 2022 id: ent-cve-2022-23732 | url: /scenarios/cve-2022-23732 A Management Console path traversal vulnerability bypassed CSRF protections, leading to privilege escalation on the appliance. Permissions abused: Management Console path traversal bypassing CSRF Sources: CVE-2022-23732: Management Console path traversal, CSRF bypass (Advisory: vendor/CVE record with affected version ranges) — https://github.com/advisories?query=CVE-2022-23732 ### Application (5) #### Heroku / Travis CI OAuth token theft — April 2022 id: app-heroku-travisci-oauth-theft | url: /scenarios/heroku-travisci-oauth-theft The canonical application-scope incident. Stolen OAuth user tokens issued to Heroku and Travis CI were used to authenticate to the GitHub API and download private repositories from dozens of organizations, including npm; GitHub assessed the actor was likely mining those repos for secrets to pivot into other infrastructure. Detection started when GitHub found unauthorized access to npm production infrastructure using an AWS key that had been lifted from a private npm repo downloaded with one of those tokens. Five specific app IDs were implicated (three Heroku Dashboard variants, Heroku Dashboard Classic, and Travis CI). The tokens were never compromised at GitHub. The app vendor was the weak point. Permissions abused: OAuth App token (private repo read) Sources: GitHub security alert: stolen OAuth user tokens (Postmortem: the victim's own account of the incident) — https://github.blog/news-insights/company-news/security-alert-stolen-oauth-user-tokens/ #### tj-actions/changed-files supply-chain compromise (CVE-2025-30066) — March 2025 id: app-tj-actions-changed-files | url: /scenarios/tj-actions-changed-files The attacker used a previously obtained GitHub token with write permissions to push a commit disguised as renovate[bot], slipped into a legitimate auto-merged PR, then retroactively repointed multiple version tags at the malicious commit, affecting over 23,000 repositories. The payload dumped runner memory to logs, exposing access keys, PATs, npm tokens, and private RSA keys. Upstream, reviewdog/action-setup (CVE-2025-30154) was backdoored first, and researchers traced the original target to Coinbase's agentkit repo. The permission abused, contents:write on an Action repo, implies tag-write, which implies control of every consumer pinning by tag. Permissions abused: contents:write on an Action repo (tag rewrite) Attribution: Unit42 Sources: GitHub Actions supply chain attack: Coinbase to tj-actions (Research: explains the primitive, generalizes beyond one victim) — https://unit42.paloaltonetworks.com/github-actions-supply-chain-attack/ | CISA alert: tj-actions/changed-files and reviewdog/action-setup (Advisory: vendor/CVE record with affected version ranges) — https://www.cisa.gov/news-events/alerts/2025/03/18/supply-chain-compromise-third-party-tj-actionschanged-files-cve-2025-30066-and-reviewdogaction | GHSA-mrrh-fwg8-r2c3 (CVE-2025-30066) (Advisory: vendor/CVE record with affected version ranges) — https://github.com/advisories/ghsa-mrrh-fwg8-r2c3 | tj-actions/changed-files supply chain attack (Wiz) (Tracker: live campaign feed, recheck before publishing) — https://www.wiz.io/blog/github-action-tj-actions-changed-files-supply-chain-attack-cve-2025-30066 How it unfolded: 1. An upstream dependency is backdoored first — Attackers compromised reviewdog/action-setup (CVE-2025-30154), a dependency of the tj-actions release process, obtaining a GitHub token with write permissions in the process. 2. A commit is disguised as a routine bot contribution — Using that write token, the attacker pushed a commit to tj-actions/changed-files crafted to look like an automated renovate[bot] update, and it was folded into a legitimate pull request that auto-merged. 3. Release tags are retroactively repointed — The attacker then force-moved multiple existing version tags to point at the malicious commit. Every consuming workflow pinning by tag — not by commit SHA — silently ran the tampered action on its very next run. 4. The payload dumps runner memory into public logs — The injected code printed the CI runner's process memory straight into the workflow's own log output, a public artifact on public repositories, exposing access keys, PATs, npm tokens, and private RSA keys. 5. 23,000+ repositories are exposed in one sweep — Because tj-actions/changed-files was so widely consumed, the tag retarget alone reached every downstream repository's next workflow run; researchers traced the original intended target to Coinbase's agentkit repository. Mitigations: Pin third-party Actions to a full commit SHA, never a mutable tag or branch — a tag can be retargeted with no warning; Restrict which Actions can run to GitHub-verified creators or an internal allow-list; Enable dependency review on pull requests — surfaces unexpected Action version bumps before merge; Apply tag-protection rulesets on your own releases — tags can't be silently force-moved after publication; Treat workflow log output as a secret-exposure surface — avoid any step that could dump environment variables or process memory into it #### ArtiPACKED artifact token leakage — August 2024 id: app-artipacked | url: /scenarios/artipacked actions/checkout persists the GitHub token into .git/config by default; uploading the whole checkout directory as an artifact exposes it. Researchers observed leakage of both GITHUB_TOKEN and the undocumented ACTIONS_RUNTIME_TOKEN, which lives about six hours and can be used to swap an artifact for a malicious version. The token-theft path is a race condition: download the artifact mid-run and create a branch before expiry. It requires the workflow to hold contents: write. Permissions abused: GITHUB_TOKEN persisted in .git/config, ACTIONS_RUNTIME_TOKEN (~6h lifetime) Attribution: Unit42 Sources: ArtiPACKED: hacking giants through a race condition in Actions artifacts (Research: explains the primitive, generalizes beyond one victim) — https://unit42.paloaltonetworks.com/github-repo-artifacts-leak-tokens/ | GitHub Actions artifacts found leaking auth tokens in popular projects (Tracker: live campaign feed, recheck before publishing) — https://www.bleepingcomputer.com/news/security/github-actions-artifacts-found-leaking-auth-tokens-in-popular-projects/ | GitHub Actions security: a guide to common risks (Research: explains the primitive, generalizes beyond one victim) — https://orca.security/resources/blog/github-actions-security-risks/ | GitHub Actions hardening (Research: explains the primitive, generalizes beyond one victim) — https://orca.security/resources/blog/github-actions-hardening/ #### GitHub MCP / agentic token breadth — May 2025 – ongoing id: app-agentic-mcp-tokens | url: /scenarios/agentic-mcp-tokens Invariant Labs showed a malicious public issue can coerce an agent into pulling private repo data into context and leaking it via an autonomously created public PR, arguing this can't be fixed server-side and needs architectural controls. The pattern kept spreading: Orca's RoguePilot used a hidden prompt in an issue to make Copilot leak a repository's privileged token, and Noma's GitLost targeted agentic workflows handed a token with read access across an org's repos, private ones included. This is the clearest case where the token's scope breadth is the entire vulnerability. Nothing else needs to go wrong. Permissions abused: Broad read-across-org token handed to an autonomous agent Attribution: Invariant Labs, Orca Security, Noma Security Sources: GitHub MCP exploited: accessing private repositories via MCP (Research: explains the primitive, generalizes beyond one victim) — https://invariantlabs.ai/blog/mcp-github-vulnerability | github-mcp-server issue #844: token scope reproduction (Research: explains the primitive, generalizes beyond one victim) — https://github.com/github/github-mcp-server/issues/844 | Public GitHub issue could trick agentic workflows into leaking private repo data (Tracker: live campaign feed, recheck before publishing) — https://thehackernews.com/2026/07/public-github-issue-could-trick-github.html | MCP horror stories: the GitHub prompt injection data heist (Research: explains the primitive, generalizes beyond one victim) — https://www.docker.com/blog/mcp-horror-stories-github-prompt-injection/ | AI GitHub agents: how one issue leaked private repos (Research: explains the primitive, generalizes beyond one victim) — https://www.upguard.com/blog/ai-github-agents-issue-leaked-private-repos #### GitHub App installation token abuse — Ongoing id: app-installation-token-abuse | url: /scenarios/installation-token-abuse An attacker holding the App private key, or landing on a runner that carries the installation, mints fresh short-lived tokens with scopes like contents:write and pull_requests:write, then reads private repos, pushes code, or opens malicious PRs. GHES also had an incorrect-authorization bug, fixed in 2022 (versions 3.3.17/3.4.12/3.5.9/3.6.5), where a scoped user-to-server token could escalate to full admin/owner. Permissions abused: App private key theft, contents:write, pull_requests:write Sources: What is GitHub App token abuse? (Research: explains the primitive, generalizes beyond one victim) — https://securelayer7.net/learn/credential-access/github-app-token-abuse | HackerOne #1732595: scoped user-to-server token escalates to org admin/owner (Advisory: vendor/CVE record with affected version ranges) — https://hackerone.com/reports/1732595 --- ## Escalation primitives - Force-repoint version tags — via contents:write on an Action repo — seen in: tj-actions, TeamPCP - Inject a workflow file — via workflow scope / contents:write — seen in: Dependabot spoof, GhostAction, Shai-Hulud, Salesloft - Flip repo visibility to public — via org owner / repo admin — seen in: Toptal, Nx phase 2, Shai-Hulud - Steal GITHUB_TOKEN from runner memory or .git/config — via any RCE on a runner — seen in: tj-actions, PyTorch, ArtiPACKED - workflow_dispatch a privileged publish job — via actions:write — seen in: Nx - Register a self-hosted runner — via admin:org / repo admin — seen in: Shai-Hulud 2.0 - Non-ephemeral runner persistence — via runner config default — seen in: PyTorch, TensorFlow - Poison the shared Actions cache — via fork PR write into shared cache — seen in: Ultralytics, Angular, TanStack - Add a guest/outside collaborator — via org owner — seen in: Salesloft - Forge a SAML assertion — via none (pre-auth) — seen in: CVE-2024-4985 --- ## Access Tiers 2.0: permissions (190) One global tier scale, comparable across every permission: - Tier 0 (Critical): Control-plane equivalent. Full compromise of an org, enterprise, or app identity, or of every repository in it. - Tier 1 (High): Management/pipeline plane. Control over deployments, CI/CD, or secrets. - Tier 2 (Medium): User/workload plane. Real but bounded risk: standard collaboration, or stopping work by cutting spend or licenses. - Tier 3 (Low): Low impact. Reading, or changes that touch no access, code, credentials or availability. Rules for tiering a single permission: - Start from the plane: control plane Tier 0, management plane Tier 1, data plane Tier 2 for write. - Go up a tier when one documented step reaches a higher one, and name that step in the reason. Workflows: write is on the data plane, but a workflow can ask for its repository's secrets, so it is Tier 1. - Reading is Tier 3, unless what is read is itself a credential, such as a leaked secret in a secret scanning alert. - Go down when the permission cannot change anything that matters for security, such as stars, follows or a profile picture, and say so. - Tiers are about impact, not about read or write. A write that can stop work, such as cutting spend or removing licenses, is at least Tier 2. - Breadth counts for roles: admin on every repository in an organization is Tier 0, because it reaches every repository's visibility, secrets and protections at once. Planes: - Control plane (default Tier 0): Identity and administration: who is a member, who is an owner, and which roles exist. - Management plane (default Tier 1): Settings and pipelines: policies, protections, tokens, App installs, runners and webhooks. - Data plane (default Tier 2): The workload: code, workflow runs, secrets, alerts, releases and day-to-day collaboration. - Custom enterprise roles: Write (enterprise) [id enterprise_custom_enterprise_roles:write] Tier 0; control plane, area admins; available in github-app; on Enterprise Cloud, EMU. Creates enterprise roles and assigns them to teams, so it can hand out enterprise-level admin powers. - Enterprise SCIM: Write (enterprise) [id enterprise_scim:write] Tier 0; control plane, area identity; available in github-app; on Enterprise Cloud, EMU, Server. Creates, changes and removes enterprise identities through SCIM, which decides who exists in the enterprise at all. - Enterprise teams: Write (enterprise) [id enterprise_teams:write] Tier 0, context-dependent; control plane, area identity; available in github-app; on Enterprise Cloud, EMU, Server. Adds people to enterprise teams and teams to organizations. Teams carry enterprise and organization roles, so membership is role assignment. - Administration: Write (organization) [id organization_administration:write] Tier 0; management plane, area org-policy; available in fine-grained-pat, github-app; on Every setup. Changes org settings including the base repository permission, org rulesets, Actions policy, the OIDC subject format and which team is security manager. Enough to switch off every org-wide guard at once. - Members: Write (organization) [id members:write] Tier 0; control plane, area identity; available in fine-grained-pat, github-app; on Every setup. Sets organization membership, including the owner role, invites people, manages teams and assigns organization roles. This is who controls the organization. - Custom properties: Write (enterprise) [id enterprise_custom_properties:write] Tier 1, context-dependent; management plane, area org-policy; available in github-app; on Enterprise Cloud, EMU, Server. Defines custom properties. Rulesets and OIDC claims can be targeted by property, so changing them changes which rules and cloud trusts apply. - Enterprise credentials: Write (enterprise) [id enterprise_credentials:write] Tier 1; management plane, area tokens; available in github-app; on Enterprise Cloud, EMU. Authorizes and revokes credentials for the enterprise. Revoking all of them locks every member's tokens and keys out at once. - Enterprise custom properties for organizations: Admin (enterprise) [id enterprise_custom_properties_for_organizations:admin] Tier 1, context-dependent; management plane, area org-policy; available in github-app; on Enterprise Cloud, EMU, Server. Defines organization custom properties, which policies can target. - Enterprise custom properties for organizations: Write (enterprise) [id enterprise_custom_properties_for_organizations:write] Tier 1, context-dependent; management plane, area org-policy; available in github-app; on Enterprise Cloud, EMU, Server. Sets property values on organizations, which policies can target. - Enterprise organization installation repositories: Write (enterprise) [id enterprise_organization_installation_repositories:write] Tier 1; management plane, area apps; available in github-app; on Enterprise Cloud, EMU, Server. Changes which repositories an enterprise-owned App installation can reach. - Enterprise organization installations: Write (enterprise) [id enterprise_organization_installations:write] Tier 1; management plane, area apps; available in github-app; on Enterprise Cloud, EMU, Server. Installs enterprise-owned Apps into organizations, giving them whatever permissions the App asks for there. - Custom organization roles: Write (organization) [id organization_custom_org_roles:write] Tier 1, context-dependent; control plane, area admins; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Creates and changes custom organization roles. Editing a role that is already assigned changes what every holder can do; assigning roles needs Members: write. - Custom properties: Admin (organization) [id organization_custom_properties:admin] Tier 1, context-dependent; management plane, area org-policy; available in fine-grained-pat, github-app; on Every setup. Defines custom properties that rulesets and OIDC claims can use. - Custom properties: Write (organization) [id organization_custom_properties:write] Tier 1, context-dependent; management plane, area org-policy; available in fine-grained-pat, github-app; on Every setup. Sets property values on repositories. Rulesets and OIDC claims can be targeted by property, so values decide which rules and cloud trusts apply. - Custom repository roles: Write (organization) [id organization_custom_roles:write] Tier 1; control plane, area admins; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Creates and edits custom repository roles. Editing an assigned role widens access for everyone who has it. - Network configurations: Write (organization) [id organization_network_configurations:write] Tier 1; management plane, area runners; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Connects GitHub-hosted runners to private networks, putting workflow code inside internal networks. - Organization private registries: Write (organization) [id organization_private_registries:write] Tier 1; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Changes private registry configurations and their credentials, which decides where dependencies are resolved from. Pointing a registry elsewhere redirects dependency resolution for every repository that uses it. - Personal access token requests: Write (organization) [id organization_personal_access_token_requests:write] Tier 1; management plane, area tokens; available in github-app; on Every setup. Approves or denies fine-grained PAT requests, deciding whose tokens reach the organization's resources. - Secrets: Write (organization) [id organization_secrets:write] Tier 1; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Creates and overwrites organization Actions secrets and changes which repositories can use them. Swapping a deploy credential redirects every pipeline that reads it. - Self-hosted runners: Write (organization) [id organization_self_hosted_runners:write] Tier 1; management plane, area runners; available in fine-grained-pat, github-app; on Every setup. Registers runners and changes runner groups. A runner the attacker controls receives the jobs, and the secrets, of every repository allowed to use it. - Webhooks: Write (organization) [id organization_hooks:write] Tier 1; management plane, area webhooks; available in fine-grained-pat, github-app; on Every setup. Creates organization webhooks, which send events from every repository to any URL. A quiet, lasting way to watch everything that happens. - Actions: Write (repository) [id actions:write] Tier 1; data plane, area workflows; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key actions); on Every setup. Starts, re-runs and cancels workflows and deletes caches and artifacts. Starting a privileged publish or deploy job by hand was the step that mattered in the Nx incident. - Administration: Write (repository) [id administration:write] Tier 1; management plane, area repo-settings; available in fine-grained-pat, github-app; on Every setup. Changes branch protection and rulesets, adds collaborators and deploy keys, registers runners, and can rename, transfer, make public or delete the repository. - Custom properties: Write (repository) [id repository_custom_properties:write] Tier 1, context-dependent; management plane, area repo-settings; available in fine-grained-pat, github-app; on Every setup. Sets the repository's custom property values. Rulesets and OIDC claims can be targeted by property. - Environments: Write (repository) [id environments:write] Tier 1; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Creates and overwrites environment secrets and variables, which production deploy jobs read. - GitHub App installation repository access: Write (repository) [id installation_repositories:write] Tier 1; management plane, area apps; available in fine-grained-pat, github-app; on Every setup. Adds repositories to a GitHub App installation the user can manage, giving the App its permissions on them. - OIDC ID token: Write (repository) [id id_token:write] Tier 1, context-dependent; data plane, area secrets; available in github-token (GITHUB_TOKEN key id-token); on Every setup. Lets the job request an OIDC token and trade it for cloud credentials. The cloud trust policy is the only check left; a wildcard subject turns this into cloud admin. - Packages: Write (repository) [id packages:write] Tier 1, context-dependent; data plane, area releases; available in github-token (GITHUB_TOKEN key packages); on Every setup. Publishes packages. Whatever is published is pulled by everyone who depends on it. - Secret scanning alerts: Read (repository) [id secret_scanning_alerts:read] Tier 1; data plane, area code-security; available in fine-grained-pat, github-app; on Every setup. Reads secret scanning alerts, which include the leaked secret values themselves. Reading this is reading credentials. - Secrets: Write (repository) [id secrets:write] Tier 1; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Creates and overwrites repository Actions secrets. Swapping a deploy credential redirects the pipeline that reads it. - Webhooks: Write (repository) [id repository_hooks:write] Tier 1; management plane, area webhooks; available in fine-grained-pat, github-app; on Every setup. Creates repository webhooks that send every push, pull request and issue event to any URL. - Workflows: Write (repository) [id workflows:write] Tier 1; data plane, area workflows; available in fine-grained-pat, github-app; on Every setup. Changes files under .github/workflows. A changed workflow runs with the repository's secrets and can ask for any token permission, so this is a direct path to secrets and cloud access. - Git SSH keys: Write (user) [id keys:write] Tier 1; control plane, area identity; available in fine-grained-pat, github-app; on Every setup. Adds SSH keys to the user's account. A key added here gives lasting Git access as that user in every organization they belong to. - Enterprise AI controls: Write (enterprise) [id enterprise_ai_controls:write] Tier 2; management plane, area org-policy; available in github-app; on Enterprise Cloud, EMU. Changes Copilot content exclusions and agent controls, deciding which files and agents Copilot may use. - Enterprise billing: Write (enterprise) [id enterprise_billing:write] Tier 2; data plane, area insights; available in github-app; on Enterprise Cloud, EMU. Changes budgets. Grants no access, but cutting spend can stop Actions, Codespaces and other paid features for the whole enterprise, which is an availability risk. - Enterprise Licensing: Write (enterprise) [id enterprise_licensing:write] Tier 2; data plane, area insights; available in github-app; on Enterprise Cloud, EMU. Assigns and removes licenses. Grants no access to code or settings, but removing a license locks that person out, which is an availability risk. - Agent secrets: Write (organization) [id organization_agent_secrets:write] Tier 2; data plane, area secrets; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Creates and overwrites secrets given to the Copilot agent's environment. The agent's changes still go through a pull request, so this does not reach production on its own. - Agent variables: Write (organization) [id organization_agent_variables:write] Tier 2; data plane, area agents; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Changes variables in the Copilot agent's environment. - Copilot agent settings: Write (organization) [id organization_copilot_agent_settings:write] Tier 2; data plane, area agents; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Turns the Copilot coding agent on for repositories, which lets it open pull requests there. - Copilot content exclusion: Write (organization) [id org_copilot_content_exclusion:write] Tier 2; management plane, area org-policy; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Changes which files Copilot must ignore, so sensitive files can be put back in reach of Copilot. - Copilot Spaces: Write (organization) [id organization_copilot_spaces:write] Tier 2; data plane, area agents; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Creates and edits Copilot Spaces, which decide what context Copilot answers from. - Custom properties for organizations: Write (organization) [id custom_properties_for_organizations:write] Tier 2, context-dependent; management plane, area org-policy; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Sets the organization's own property values, which enterprise policies can target. - External custom properties for repositories: Admin (organization) [id organization_external_properties_for_repos:admin] Tier 2, context-dependent; management plane, area org-policy; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Lets Apps provide repository properties. - External custom properties for repositories: Write (organization) [id organization_external_properties_for_repos:write] Tier 2, context-dependent; management plane, area org-policy; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Sets App-provided property values on repositories. - Hosted runner custom images: Write (organization) [id organization_runner_custom_images:write] Tier 2; management plane, area runners; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Deletes custom hosted runner images and their versions. - Organization bypass requests for secret scanning: Write (organization) [id organization_secret_scanning_bypass_requests:write] Tier 2; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Approves pushes that contain a detected secret. - Organization codespaces: Write (organization) [id organization_codespaces:write] Tier 2; data plane, area codespaces; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Stops and deletes members' codespaces. - Organization codespaces secrets: Write (organization) [id organization_codespaces_secrets:write] Tier 2; data plane, area secrets; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Creates and overwrites secrets injected into members' codespaces. Unlike Actions and Dependabot secrets, nothing deploys or resolves dependencies with them on its own; they reach a developer's environment. - Organization codespaces settings: Write (organization) [id organization_codespaces_settings:write] Tier 2; management plane, area org-policy; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Decides which members may use Codespaces paid for by the organization. - Organization dependabot secrets: Write (organization) [id organization_dependabot_secrets:write] Tier 2; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Creates and overwrites the secrets Dependabot uses for private registries. It only replaces a credential: the registry URL lives in dependabot.yml or the organization's private registry settings, so a swap breaks updates rather than redirecting them. - Organization dismissal requests for code scanning: Write (organization) [id organization_code_scanning_dismissal_requests:write] Tier 2; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Approves requests to dismiss code scanning alerts, so findings can be closed without a fix. - Organization dismissal requests for Dependabot: Write (organization) [id organization_dependabot_dismissal_requests:write] Tier 2; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Approves requests to dismiss Dependabot alerts. - Personal access tokens: Write (organization) [id organization_personal_access_tokens:write] Tier 2; management plane, area tokens; available in github-app; on Every setup. Revokes fine-grained PAT access to the organization. Can cut off automation, but cannot grant access. - Secret scanning alert dismissal requests: Write (organization) [id secret_scanning_dismissal_requests:write] Tier 2; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Approves requests to dismiss secret scanning alerts, so a leaked credential can be marked as handled. - Variables: Write (organization) [id organization_actions_variables:write] Tier 2, context-dependent; data plane, area workflows; available in fine-grained-pat, github-app; on Every setup. Changes organization Actions variables. Workflows that put a variable into a shell command can be steered by it. - Agent secrets: Write (repository) [id agent_secrets:write] Tier 2; data plane, area secrets; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Creates and overwrites secrets given to the Copilot agent's environment for this repository. The agent's changes still go through a pull request. - Agent variables: Write (repository) [id agent_variables:write] Tier 2; data plane, area agents; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Changes variables in the Copilot agent's environment. - Artifact metadata: Write (repository) [id artifact_metadata:write] Tier 2; data plane, area releases; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key artifact-metadata); on Free, Team, Enterprise Cloud, EMU. Writes artifact deployment records. - Attestations: Write (repository) [id attestations:write] Tier 2; data plane, area releases; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key attestations); on Free, Team, Enterprise Cloud, EMU. Uploads and deletes attestations. Deleting one makes a genuine artifact fail verification. - Checks: Write (repository) [id checks:write] Tier 2; data plane, area code; available in github-app, github-token (GITHUB_TOKEN key checks); on Every setup. Creates and completes check runs, which can satisfy required checks named in branch rules. - Code scanning alerts: Write (repository) [id security_events:write] Tier 2; data plane, area code-security; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key security-events); on Every setup. Dismisses code scanning alerts and uploads analyses, so findings can disappear without a fix. - Codespaces: Write (repository) [id codespaces:write] Tier 2; data plane, area codespaces; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Creates codespaces, which run the repository's dev container with the user's access. - Codespaces lifecycle admin: Write (repository) [id codespaces_lifecycle_admin:write] Tier 2; data plane, area codespaces; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Starts, stops and exports codespaces. - Codespaces secrets: Write (repository) [id codespaces_secrets:write] Tier 2; data plane, area secrets; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Creates and overwrites secrets injected into codespaces for this repository. They reach a developer's environment, not a pipeline that deploys. - Commit statuses: Write (repository) [id statuses:write] Tier 2; data plane, area code; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key statuses); on Every setup. Sets commit statuses. A required status check that does not pin its source can be marked green by anyone with this. - Contents: Write (repository) [id contents:write] Tier 2, context-dependent; data plane, area code; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key contents); on Every setup. Pushes commits, creates and moves branches and tags, and edits releases. Cannot change workflow files without Workflows: write. On a repository other projects consume, moving a tag changes what they run. - Dependabot alerts: Write (repository) [id vulnerability_alerts:write] Tier 2; data plane, area code-security; available in fine-grained-pat, github-app; on Every setup. Dismisses Dependabot alerts. - Dependabot secrets: Write (repository) [id dependabot_secrets:write] Tier 2; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Creates and overwrites the secrets Dependabot uses for private registries. It only replaces a credential: the registry URL lives in dependabot.yml or the organization's private registry settings, so a swap breaks updates rather than redirecting them. - Deployments: Write (repository) [id deployments:write] Tier 2, context-dependent; data plane, area workflows; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key deployments); on Every setup. Creates deployments, and approves or rejects pending ones where the caller is a required reviewer or the custom protection rule. - Discussions: Write (repository) [id discussions:write] Tier 2; data plane, area collaboration; available in github-token (GITHUB_TOKEN key discussions); on Every setup. Creates and edits discussions. GitHub Discussions has been used as a command channel for malware. - Issues: Write (repository) [id issues:write] Tier 2; data plane, area collaboration; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key issues); on Every setup. Creates and edits issues and comments. Text in issues is what prompt injection against AI agents uses. - Pages: Write (repository) [id pages:write] Tier 2, context-dependent; data plane, area workflows; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key pages); on Every setup. Builds and deploys GitHub Pages. Can publish content on the repository's Pages site. - Pull requests: Write (repository) [id pull_requests:write] Tier 2; data plane, area code; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key pull-requests); on Every setup. Opens, reviews, approves and merges pull requests. An approval can satisfy a review rule when the approver counts as a reviewer. - Repository creation: Write (repository) [id repository_creation:write] Tier 2; data plane, area code; available in fine-grained-pat, github-app; on Every setup. Creates repositories in the organization. - Repository security advisories: Write (repository) [id repository_advisories:write] Tier 2; data plane, area code-security; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Creates and edits security advisories. - Secret scanning alert dismissal requests: Write (repository) [id repo_secret_scanning_dismissal_requests:write] Tier 2; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Approves requests to dismiss secret scanning alerts. - Secret scanning alerts: Write (repository) [id secret_scanning_alerts:write] Tier 2; data plane, area code-security; available in fine-grained-pat, github-app; on Every setup. Closes secret scanning alerts, so a leaked credential can be marked as handled. - Secret scanning push protection bypass requests: Write (repository) [id secret_scanning_bypass_requests:write] Tier 2; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Approves pushes that contain a detected secret. - Variables: Write (repository) [id actions_variables:write] Tier 2, context-dependent; data plane, area workflows; available in fine-grained-pat, github-app; on Every setup. Changes repository Actions variables. Workflows that put a variable into a shell command can be steered by it. - Codespaces user secrets: Write (user) [id codespaces_user_secrets:write] Tier 2; data plane, area secrets; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Creates and overwrites secrets injected into the user's codespaces. - Email addresses: Write (user) [id emails:write] Tier 2; control plane, area identity; available in fine-grained-pat, github-app; on Every setup. Adds and removes email addresses and changes their visibility. - GPG keys: Write (user) [id gpg_keys:write] Tier 2; data plane, area code; available in fine-grained-pat, github-app; on Every setup. Adds GPG keys, so commits signed with them show as Verified for this user. - SSH signing keys: Write (user) [id git_signing_ssh_public_keys:write] Tier 2; data plane, area code; available in fine-grained-pat, github-app; on Every setup. Adds SSH signing keys, so commits signed with them show as Verified for this user. - Copilot usage records: Read (enterprise) [id enterprise_copilot_usage:read] Tier 3; data plane, area insights; available in github-app; on Enterprise Cloud, EMU. Reads Copilot usage records. - Custom enterprise roles: Read (enterprise) [id enterprise_custom_enterprise_roles:read] Tier 3; control plane, area admins; available in github-app; on Enterprise Cloud, EMU. Lists custom enterprise roles and who holds them. - Custom properties: Read (enterprise) [id enterprise_custom_properties:read] Tier 3; management plane, area org-policy; available in github-app; on Enterprise Cloud, EMU, Server. Reads the enterprise's custom property definitions. - Enterprise AI controls: Read (enterprise) [id enterprise_ai_controls:read] Tier 3; management plane, area org-policy; available in github-app; on Enterprise Cloud, EMU. Reads Copilot content exclusions and agent controls. - Enterprise billing: Read (enterprise) [id enterprise_billing:read] Tier 3; data plane, area insights; available in github-app; on Enterprise Cloud, EMU. Reads billing usage and budgets. - Enterprise Copilot metrics: Read (enterprise) [id enterprise_copilot_metrics:read] Tier 3; data plane, area insights; available in github-app; on Free, Team, Enterprise Cloud, EMU. Reads Copilot usage metrics. - Enterprise credentials: Read (enterprise) [id enterprise_credentials:read] Tier 3; management plane, area tokens; available in github-app; on Enterprise Cloud, EMU. Lists the credentials members have authorized for SAML SSO and can export that list. - Enterprise custom properties for organizations: Read (enterprise) [id enterprise_custom_properties_for_organizations:read] Tier 3; management plane, area org-policy; available in github-app; on Enterprise Cloud, EMU, Server. Reads organization custom property definitions and values. - Enterprise innersource vulnerabilities: Write (enterprise) [id enterprise_innersource_vulnerabilities:write] Tier 3; data plane, area code-security; available in github-app; on Enterprise Cloud, EMU. Syncs innersource vulnerability data. Changes reporting, not access, code, credentials or availability. - Enterprise Licensing: Read (enterprise) [id enterprise_licensing:read] Tier 3; data plane, area insights; available in github-app; on Enterprise Cloud, EMU. Reads Visual Studio subscription license assignments. - Enterprise organization installation repositories: Read (enterprise) [id enterprise_organization_installation_repositories:read] Tier 3; management plane, area apps; available in github-app; on Enterprise Cloud, EMU, Server. Lists which repositories an enterprise-owned App installation can reach. - Enterprise organization installations: Read (enterprise) [id enterprise_organization_installations:read] Tier 3; management plane, area apps; available in github-app; on Enterprise Cloud, EMU, Server. Lists which organizations an enterprise-owned App is installed in. - Enterprise SCIM: Read (enterprise) [id enterprise_scim:read] Tier 3; control plane, area identity; available in github-app; on Enterprise Cloud, EMU, Server. Reads provisioned enterprise users and groups. - Enterprise teams: Read (enterprise) [id enterprise_teams:read] Tier 3; control plane, area identity; available in github-app; on Enterprise Cloud, EMU, Server. Lists enterprise teams, their members and organizations. - Administration: Read (organization) [id organization_administration:read] Tier 3; management plane, area org-policy; available in fine-grained-pat, github-app; on Every setup. Reads organization settings, Actions policy, rulesets and security configurations. - Agent secrets: Read (organization) [id organization_agent_secrets:read] Tier 3; data plane, area secrets; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Lists organization agent secret names. Values are never returned. - Agent variables: Read (organization) [id organization_agent_variables:read] Tier 3; data plane, area agents; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads organization agent variables. - API Insights: Read (organization) [id organization_api_insights:read] Tier 3; data plane, area insights; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads API usage statistics per actor, which shows which tokens and Apps are active. - Blocking users: Read (organization) [id organization_user_blocking:read] Tier 3; management plane, area org-policy; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Lists users blocked from the organization. - Blocking users: Write (organization) [id organization_user_blocking:write] Tier 3; management plane, area org-policy; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Blocks and unblocks users. Moderation only. - Campaigns: Read (organization) [id organization_campaigns:read] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads security campaigns. - Campaigns: Write (organization) [id organization_campaigns:write] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Creates security campaigns. Organizes remediation work, changes no alerts. - Copilot agent settings: Read (organization) [id organization_copilot_agent_settings:read] Tier 3; data plane, area agents; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads where the Copilot coding agent is allowed. - Copilot content exclusion: Read (organization) [id org_copilot_content_exclusion:read] Tier 3; management plane, area org-policy; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads which files Copilot must ignore. - Copilot Spaces: Read (organization) [id organization_copilot_spaces:read] Tier 3; data plane, area agents; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads Copilot Spaces and the context they hold. - Custom organization roles: Read (organization) [id organization_custom_org_roles:read] Tier 3; control plane, area admins; available in fine-grained-pat, github-app; on Every setup. Lists custom organization roles and their permissions. - Custom properties: Read (organization) [id organization_custom_properties:read] Tier 3; management plane, area org-policy; available in fine-grained-pat, github-app; on Every setup. Reads custom property definitions. - Custom properties for organizations: Read (organization) [id custom_properties_for_organizations:read] Tier 3; management plane, area org-policy; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Reads the organization's own property values. - Custom repository roles: Read (organization) [id organization_custom_roles:read] Tier 3; control plane, area admins; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Lists custom repository roles. - Events: Read (organization) [id organization_events:read] Tier 3; data plane, area insights; available in fine-grained-pat, github-app; on Every setup. Reads organization activity events. - External custom properties for repositories: Read (organization) [id organization_external_properties_for_repos:read] Tier 3; management plane, area org-policy; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads property definitions provided by installed Apps. - GitHub Copilot Business: Read (organization) [id organization_copilot_seat_management:read] Tier 3; data plane, area insights; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads Copilot seat assignments. - GitHub Copilot Business: Write (organization) [id organization_copilot_seat_management:write] Tier 3; data plane, area insights; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Assigns Copilot seats. Billing only. - Hosted runner custom images: Read (organization) [id organization_runner_custom_images:read] Tier 3; management plane, area runners; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Lists custom images for hosted runners. - Issue Fields: Read (organization) [id issue_fields:read] Tier 3; data plane, area collaboration; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads issue field definitions. - Issue Fields: Write (organization) [id issue_fields:write] Tier 3; data plane, area collaboration; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Defines issue fields. Configuration of issue forms only. - Issue Types: Read (organization) [id issue_types:read] Tier 3; data plane, area collaboration; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads issue type definitions. - Issue Types: Write (organization) [id issue_types:write] Tier 3; data plane, area collaboration; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Defines issue types. Configuration of issues only. - Members: Read (organization) [id members:read] Tier 3; control plane, area identity; available in fine-grained-pat, github-app; on Every setup. Lists members, teams, invitations and role assignments. Useful recon for picking targets. - Network configurations: Read (organization) [id organization_network_configurations:read] Tier 3; management plane, area runners; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads private network settings for hosted runners. - Organization announcement banners: Read (organization) [id organization_announcement_banners:read] Tier 3; management plane, area org-policy; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Reads the organization banner. - Organization announcement banners: Write (organization) [id organization_announcement_banners:write] Tier 3; management plane, area org-policy; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Sets the banner shown to members. Could carry a phishing link, but grants no access. - Organization bypass requests for secret scanning: Read (organization) [id organization_secret_scanning_bypass_requests:read] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Reads requests to push past secret scanning push protection. - Organization codespaces: Read (organization) [id organization_codespaces:read] Tier 3; data plane, area codespaces; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Lists codespaces in the organization. - Organization codespaces secrets: Read (organization) [id organization_codespaces_secrets:read] Tier 3; data plane, area secrets; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Lists organization Codespaces secret names. Values are never returned. - Organization Copilot metrics: Read (organization) [id organization_copilot_metrics:read] Tier 3; data plane, area insights; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads Copilot usage metrics. - Organization dependabot secrets: Read (organization) [id organization_dependabot_secrets:read] Tier 3; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Lists organization Dependabot secret names. Values are never returned. - Organization dismissal requests for code scanning: Read (organization) [id organization_code_scanning_dismissal_requests:read] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Reads requests to dismiss code scanning alerts. - Organization dismissal requests for Dependabot: Read (organization) [id organization_dependabot_dismissal_requests:read] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Reads requests to dismiss Dependabot alerts. - Organization private registries: Read (organization) [id organization_private_registries:read] Tier 3; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Lists private registry configurations. Credentials are not returned. - Personal access token requests: Read (organization) [id organization_personal_access_token_requests:read] Tier 3; management plane, area tokens; available in github-app; on Every setup. Lists pending fine-grained PAT requests. - Personal access tokens: Read (organization) [id organization_personal_access_tokens:read] Tier 3; management plane, area tokens; available in github-app; on Every setup. Lists fine-grained PATs with access to the organization and what they reach. - Projects: Read (organization) [id organization_projects:read] Tier 3; data plane, area collaboration; available in fine-grained-pat, github-app; on Every setup. Reads organization projects. - Projects: Write (organization) [id organization_projects:write] Tier 3; data plane, area collaboration; available in fine-grained-pat, github-app; on Every setup. Edits organization projects. Planning data only. - Secret scanning alert dismissal requests: Read (organization) [id secret_scanning_dismissal_requests:read] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Reads requests to dismiss secret scanning alerts. - Secrets: Read (organization) [id organization_secrets:read] Tier 3; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Lists organization Actions secret names and which repositories can use them. Values are never returned. - Self-hosted runners: Read (organization) [id organization_self_hosted_runners:read] Tier 3; management plane, area runners; available in fine-grained-pat, github-app; on Every setup. Lists self-hosted runners and runner groups. - Variables: Read (organization) [id organization_actions_variables:read] Tier 3; data plane, area workflows; available in fine-grained-pat, github-app; on Every setup. Reads organization Actions variables. - Webhooks: Read (organization) [id organization_hooks:read] Tier 3; management plane, area webhooks; available in fine-grained-pat, github-app; on Every setup. Lists organization webhooks and their delivery settings. - Actions: Read (repository) [id actions:read] Tier 3; data plane, area workflows; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key actions); on Every setup. Reads workflow runs, logs, artifacts and caches. Logs and artifacts sometimes contain tokens that were not masked. - Administration: Read (repository) [id administration:read] Tier 3; management plane, area repo-settings; available in fine-grained-pat, github-app; on Every setup. Reads repository settings, protection rules, collaborators and Actions settings. - Agent secrets: Read (repository) [id agent_secrets:read] Tier 3; data plane, area secrets; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Lists agent secret names. Values are never returned. - Agent variables: Read (repository) [id agent_variables:read] Tier 3; data plane, area agents; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads agent variables for this repository. - Artifact metadata: Read (repository) [id artifact_metadata:read] Tier 3; data plane, area releases; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key artifact-metadata); on Free, Team, Enterprise Cloud, EMU. Reads artifact deployment records. - Attestations: Read (repository) [id attestations:read] Tier 3; data plane, area releases; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key attestations); on Free, Team, Enterprise Cloud, EMU. Reads build provenance attestations. - Checks: Read (repository) [id checks:read] Tier 3; data plane, area code; available in github-app, github-token (GITHUB_TOKEN key checks); on Every setup. Reads check runs and suites. - Code quality: Read (repository) [id code_quality:read] Tier 3; data plane, area code; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads code quality findings. - Code scanning alerts: Read (repository) [id security_events:read] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key security-events); on Every setup. Reads code scanning alerts and analyses. Shows where the code is weak. - Codespaces: Read (repository) [id codespaces:read] Tier 3; data plane, area codespaces; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Lists codespaces for the repository. - Codespaces lifecycle admin: Read (repository) [id codespaces_lifecycle_admin:read] Tier 3; data plane, area codespaces; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads codespace export status. - Codespaces metadata: Read (repository) [id codespaces_metadata:read] Tier 3; data plane, area codespaces; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads dev container and machine type options. - Commit statuses: Read (repository) [id statuses:read] Tier 3; data plane, area code; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key statuses); on Every setup. Reads commit statuses. - Contents: Read (repository) [id contents:read] Tier 3; data plane, area code; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key contents); on Every setup. Reads code, commits, branches, tags and releases. On a private repository this is the source code itself. - Copilot agent settings: Read (repository) [id copilot_agent_settings:read] Tier 3; data plane, area agents; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads the Copilot cloud agent configuration. - Dependabot alerts: Read (repository) [id vulnerability_alerts:read] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app; on Every setup. Reads Dependabot alerts. - Dependabot secrets: Read (repository) [id dependabot_secrets:read] Tier 3; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Lists repository Dependabot secret names. Values are never returned. - Deployments: Read (repository) [id deployments:read] Tier 3; data plane, area workflows; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key deployments); on Every setup. Reads deployments and their statuses. - Discussions: Read (repository) [id discussions:read] Tier 3; data plane, area collaboration; available in github-token (GITHUB_TOKEN key discussions); on Every setup. Reads discussions. - Environments: Read (repository) [id environments:read] Tier 3; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Lists environment secret names and environment variables. Secret values are never returned. - Issues: Read (repository) [id issues:read] Tier 3; data plane, area collaboration; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key issues); on Every setup. Reads issues and comments. - Metadata: Read (repository) [id metadata:read] Tier 3; data plane, area code; available in fine-grained-pat, github-app; on Every setup. Reads basic repository information. Every App and fine-grained PAT gets this. - Packages: Read (repository) [id packages:read] Tier 3; data plane, area releases; available in github-token (GITHUB_TOKEN key packages); on Every setup. Downloads packages from GitHub Packages. - Pages: Read (repository) [id pages:read] Tier 3; data plane, area workflows; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key pages); on Every setup. Reads the GitHub Pages configuration. - Pull requests: Read (repository) [id pull_requests:read] Tier 3; data plane, area code; available in fine-grained-pat, github-app, github-token (GITHUB_TOKEN key pull-requests); on Every setup. Reads pull requests and reviews. - Repository security advisories: Read (repository) [id repository_advisories:read] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads security advisories, including drafts about vulnerabilities that are not fixed yet. - Secret scanning alert dismissal requests: Read (repository) [id repo_secret_scanning_dismissal_requests:read] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Reads requests to dismiss secret scanning alerts. - Secret scanning push protection bypass requests: Read (repository) [id secret_scanning_bypass_requests:read] Tier 3; data plane, area code-security; available in fine-grained-pat, github-app; on Enterprise Cloud, EMU, Server. Reads requests to push past push protection. - Secrets: Read (repository) [id secrets:read] Tier 3; data plane, area secrets; available in fine-grained-pat, github-app; on Every setup. Lists repository Actions secret names. Values are never returned. - Variables: Read (repository) [id actions_variables:read] Tier 3; data plane, area workflows; available in fine-grained-pat, github-app; on Every setup. Reads repository Actions variables. - Webhooks: Read (repository) [id repository_hooks:read] Tier 3; management plane, area webhooks; available in fine-grained-pat, github-app; on Every setup. Lists repository webhooks. - Block another user: Read (user) [id blocking:read] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Lists users the user has blocked. - Block another user: Write (user) [id blocking:write] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Blocks and unblocks users. - Codespaces user secrets: Read (user) [id codespaces_user_secrets:read] Tier 3; data plane, area secrets; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Lists the user's Codespaces secret names. Values are never returned. - Email addresses: Read (user) [id emails:read] Tier 3; control plane, area identity; available in fine-grained-pat, github-app; on Every setup. Reads the user's email addresses. - Followers: Read (user) [id followers:read] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Every setup. Lists who the user follows and who follows them. - Followers: Write (user) [id followers:write] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Every setup. Follows and unfollows users. - Gists: Write (user) [id gists:write] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Every setup. Creates and edits the user's gists. Content outside the organization's repositories. - Git SSH keys: Read (user) [id keys:read] Tier 3; control plane, area identity; available in fine-grained-pat, github-app; on Every setup. Lists the user's SSH keys. - GPG keys: Read (user) [id gpg_keys:read] Tier 3; data plane, area code; available in fine-grained-pat, github-app; on Every setup. Lists the user's GPG keys. - Interaction limits: Read (user) [id interaction_limits:read] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Reads interaction limits on the user's repositories. - Interaction limits: Write (user) [id interaction_limits:write] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Free, Team, Enterprise Cloud, EMU. Limits who can comment on the user's repositories. - Private repository invitations: Read (user) [id private_repository_invitations:read] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Every setup. Reads invitations to private repositories. - Profile: Write (user) [id profile:write] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Every setup. Edits the user's profile. - SSH signing keys: Read (user) [id git_signing_ssh_public_keys:read] Tier 3; data plane, area code; available in fine-grained-pat, github-app; on Every setup. Lists the user's SSH signing keys. - Starring: Read (user) [id starring:read] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Every setup. Lists starred repositories. - Starring: Write (user) [id starring:write] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Every setup. Stars and unstars repositories. - Watching: Read (user) [id watching:read] Tier 3; data plane, area account; available in fine-grained-pat, github-app; on Every setup. Lists watched repositories. ## Access Tiers 2.0: roles (26) A role's tier is the highest tier among the permissions it includes. "in part" means the role only uses part of that permission and counts with the tier given. ### Read [Repository, id repo-read] Tier 3, data plane Scope: One repository. View and discuss the project. Tier 3 because nothing it includes goes beyond Tier 3. Available on: Every setup (https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization) - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository] - codespaces:write (Tier 2), in part as Tier 3: A codespace with the person's own access and no repository secrets. [inferred; from: Create codespaces for private repositories; Create codespaces for public repositories] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases] - discussions:write (Tier 2), in part as Tier 3: Posting, not moderating. [inferred; from: Create new discussions and comment on existing discussions] - issues:write (Tier 2), in part as Tier 3: Opening and commenting on issues, not editing anyone else's. [inferred; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves] - packages:read (Tier 3) [documented; from: View and install packages] - pull_requests:write (Tier 2), in part as Tier 3: Proposes changes; nothing lands without someone with write access. [inferred; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Report abusive or spammy content Docs: https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization ### Triage [Repository, id repo-triage] Tier 2, data plane Scope: One repository. Manage issues, pull requests and discussions without write access to code. Tier 2 because it includes Discussions: Write (repository), Issues: Write (repository), Pull requests: Write (repository). Available on: Every setup (https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization) - discussions:write (Tier 2) [inferred; from: Hide anyone's comments on discussions; Move a discussion to a different category; Lock and unlock discussions; Individually convert issues to discussions; Create new discussions and comment on existing discussions; Delete a discussion] - issues:write (Tier 2) [documented; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Apply milestones; Mark duplicate issues and pull requests] - pull_requests:write (Tier 2) [documented; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Request pull request reviews] - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository] - codespaces:write (Tier 2), in part as Tier 3: A codespace with the person's own access and no repository secrets. [inferred; from: Create codespaces for private repositories; Create codespaces for public repositories] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases] - packages:read (Tier 3) [documented; from: View and install packages] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Report abusive or spammy content Docs: https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization ### Write [Repository, id repo-write] Tier 1, data plane Scope: One repository. Push code, run workflows and manage releases. Tier 1 because it includes Actions: Write (repository), Packages: Write (repository), Secrets: Write (repository) and 1 more. Available on: Every setup (https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization) - actions:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - packages:write (Tier 1) [documented; from: Publish packages] - secrets:write (Tier 1) [documented; from: Create, update, and delete GitHub Actions secrets on GitHub.com; Create, update, and delete GitHub Actions secrets using the REST API] - workflows:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - actions_variables:write (Tier 2) [documented; from: Create, update, and delete GitHub Actions variables on GitHub.com; Create, update, and delete GitHub Actions variables using the REST API] - checks:write (Tier 2) [documented; from: Create status checks] - codespaces:write (Tier 2) [inferred; from: Create codespaces for private repositories; Create codespaces for private repositories with Codespaces secrets access; Create codespaces for public repositories] - contents:write (Tier 2) [documented; from: Apply suggested changes to pull requests; Merge a pull request; Push to (write) the person or team's assigned repositories; Create and edit releases; Define code owners for a repository; Rename a branch other than the repository's default branch] - discussions:write (Tier 2) [inferred; from: Hide anyone's comments on discussions; Create and edit categories for GitHub Discussions; Move a discussion to a different category; Transfer a discussion to a new repository; Manage pinned discussions; Lock and unlock discussions; Individually convert issues to discussions; Create new discussions and comment on existing discussions; Delete a discussion] - issues:write (Tier 2) [documented; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves; Apply/dismiss labels; Create, edit, delete labels; Close, reopen, and assign all issues and pull requests; Create, edit, delete milestones; Apply milestones; Mark duplicate issues and pull requests; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Lock conversations; Transfer issues] - pull_requests:write (Tier 2) [documented; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests; Approve or request changes to a pull request with required reviews; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Enable and disable auto-merge on a pull request; Request pull request reviews; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Mark a draft pull request as ready for review; Convert a pull request to a draft] - secret_scanning_alerts:write (Tier 2) [inferred; from: View and dismiss secret scanning alerts in a repository] - security_events:write (Tier 2) [documented; from: List, dismiss, and delete code scanning alerts] - statuses:write (Tier 2) [documented; from: Create status checks] - vulnerability_alerts:write (Tier 2) [documented; from: Dismiss Dependabot alerts] - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases; View draft releases] - packages:read (Tier 3) [documented; from: View and install packages] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests] - vulnerability_alerts:read (Tier 3) [documented; from: Receive Dependabot alerts for insecure dependencies in a repository] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Edit wikis in private repositories; Report abusive or spammy content; Act as a designated code owner for a repository Docs: https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization ### Maintain [Repository, id repo-maintain] Tier 1, management plane Scope: One repository. Manage the repository without sensitive or destructive settings. Tier 1 because it includes Actions: Write (repository), Administration: Write (repository), Packages: Write (repository) and 2 more. Available on: Every setup (https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization) - actions:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - administration:write (Tier 1) [documented; from: Edit a repository's description; Manage topics; Enable wikis and restrict wiki editors; Configure pull request merges; Create and edit repository social cards; Limit interactions in a repository; Enable GitHub Discussions in a repository] - packages:write (Tier 1) [documented; from: Publish packages] - secrets:write (Tier 1) [documented; from: Create, update, and delete GitHub Actions secrets on GitHub.com; Create, update, and delete GitHub Actions secrets using the REST API] - workflows:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - actions_variables:write (Tier 2) [documented; from: Create, update, and delete GitHub Actions variables on GitHub.com; Create, update, and delete GitHub Actions variables using the REST API] - checks:write (Tier 2) [documented; from: Create status checks] - codespaces:write (Tier 2) [inferred; from: Create codespaces for private repositories; Create codespaces for private repositories with Codespaces secrets access; Create codespaces for public repositories] - contents:write (Tier 2) [documented; from: Apply suggested changes to pull requests; Merge a pull request; Push to (write) the person or team's assigned repositories; Create and edit releases; Push to protected branches; Define code owners for a repository; Rename a branch other than the repository's default branch] - discussions:write (Tier 2) [inferred; from: Hide anyone's comments on discussions; Create and edit categories for GitHub Discussions; Move a discussion to a different category; Transfer a discussion to a new repository; Manage pinned discussions; Lock and unlock discussions; Individually convert issues to discussions; Create new discussions and comment on existing discussions; Delete a discussion] - issues:write (Tier 2) [documented; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves; Apply/dismiss labels; Create, edit, delete labels; Close, reopen, and assign all issues and pull requests; Create, edit, delete milestones; Apply milestones; Mark duplicate issues and pull requests; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Lock conversations; Transfer issues] - pages:write (Tier 2) [documented; from: Configure a publishing source for GitHub Pages] - pull_requests:write (Tier 2) [documented; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests; Approve or request changes to a pull request with required reviews; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Enable and disable auto-merge on a pull request; Request pull request reviews; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Mark a draft pull request as ready for review; Convert a pull request to a draft] - secret_scanning_alerts:write (Tier 2) [inferred; from: View and dismiss secret scanning alerts in a repository] - security_events:write (Tier 2) [documented; from: List, dismiss, and delete code scanning alerts] - statuses:write (Tier 2) [documented; from: Create status checks] - vulnerability_alerts:write (Tier 2) [documented; from: Dismiss Dependabot alerts] - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases; View draft releases] - packages:read (Tier 3) [documented; from: View and install packages] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests] - vulnerability_alerts:read (Tier 3) [documented; from: Receive Dependabot alerts for insecure dependencies in a repository] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Edit wikis in private repositories; Report abusive or spammy content; Act as a designated code owner for a repository; View content exclusion settings for GitHub Copilot Docs: https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization ### Admin [Repository, id repo-admin] Tier 1, context-dependent, management plane Scope: One repository. Full access to the repository, including security, access and destructive settings. Tier 1 because it includes Actions: Write (repository), Administration: Write (repository), Packages: Write (repository) and 5 more. Available on: Every setup (https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization) - actions:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - administration:write (Tier 1) [documented; from: Manage individual, team, and outside collaborator access to the repository; Edit a repository's description; Manage topics; Enable wikis and restrict wiki editors; Configure pull request merges; Manage branch protection rules and repository rulesets; Merge pull requests on protected branches, even if there are no approving reviews; Create and edit repository social cards; Limit interactions in a repository; Add a repository to a team; Manage outside collaborator access to a repository; Change a repository's visibility; Make a repository a template; Change a repository's settings; Manage team and collaborator access to the repository; Edit the repository's default branch; Rename the repository's default branch; Manage webhooks and deploy keys; Manage the forking policy for a repository; Transfer repositories into the organization; Delete or transfer repositories out of the organization; Archive repositories; Display a sponsor button; Create autolink references to external resources, like Jira or Zendesk; Enable GitHub Discussions in a repository; Manage access to GitHub Advanced Security features; Enable the dependency graph for a private repository] - packages:write (Tier 1) [documented; from: Publish packages; Delete and restore packages] - repository_custom_properties:write (Tier 1) [documented; from: Edit the custom property values for the repository] - repository_hooks:write (Tier 1) [documented; from: Manage webhooks and deploy keys] - secret_scanning_alerts:read (Tier 1) [documented; from: Unlike writers and maintainers, admins see every secret scanning alert in the repository, including the leaked values.] - secrets:write (Tier 1) [documented; from: Create, update, and delete GitHub Actions secrets on GitHub.com; Create, update, and delete GitHub Actions secrets using the REST API] - workflows:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - actions_variables:write (Tier 2) [documented; from: Create, update, and delete GitHub Actions variables on GitHub.com; Create, update, and delete GitHub Actions variables using the REST API] - checks:write (Tier 2) [documented; from: Create status checks] - codespaces:write (Tier 2) [inferred; from: Create codespaces for private repositories; Create codespaces for private repositories with Codespaces secrets access; Create codespaces for public repositories] - contents:write (Tier 2) [documented; from: Apply suggested changes to pull requests; Merge a pull request; Push to (write) the person or team's assigned repositories; Create and edit releases; Push to protected branches; Define code owners for a repository; Rename a branch other than the repository's default branch] - discussions:write (Tier 2) [inferred; from: Hide anyone's comments on discussions; Create and edit categories for GitHub Discussions; Move a discussion to a different category; Transfer a discussion to a new repository; Manage pinned discussions; Lock and unlock discussions; Individually convert issues to discussions; Create new discussions and comment on existing discussions; Delete a discussion] - issues:write (Tier 2) [documented; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves; Apply/dismiss labels; Create, edit, delete labels; Close, reopen, and assign all issues and pull requests; Create, edit, delete milestones; Apply milestones; Mark duplicate issues and pull requests; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Lock conversations; Transfer issues; Delete an issue] - pages:write (Tier 2) [documented; from: Configure a publishing source for GitHub Pages] - pull_requests:write (Tier 2) [documented; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests; Approve or request changes to a pull request with required reviews; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Enable and disable auto-merge on a pull request; Request pull request reviews; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Mark a draft pull request as ready for review; Convert a pull request to a draft] - repository_advisories:write (Tier 2) [documented; from: Create security advisories] - secret_scanning_alerts:write (Tier 2) [inferred; from: View and dismiss secret scanning alerts in a repository] - security_events:write (Tier 2) [documented; from: List, dismiss, and delete code scanning alerts] - statuses:write (Tier 2) [documented; from: Create status checks] - vulnerability_alerts:write (Tier 2) [documented; from: Dismiss Dependabot alerts] - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases; View draft releases] - packages:read (Tier 3) [documented; from: View and install packages] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests] - vulnerability_alerts:read (Tier 3) [documented; from: Receive Dependabot alerts for insecure dependencies in a repository] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Edit wikis in private repositories; Report abusive or spammy content; Act as a designated code owner for a repository; View content exclusion settings for GitHub Copilot Docs: https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/repository-roles-for-an-organization ### Collaborator on a personal repository [Repository, id personal-repo-collaborator] Tier 1, data plane Scope: One repository owned by a personal account. Someone the owner of a personal repository has invited. There is only one level: collaborators can pull and push, manage issues and pull requests, and publish packages. Tier 1 because it includes Packages: Write (repository), Workflows: Write (repository). Available on: Free, Team, Enterprise Cloud, Server (https://docs.github.com/en/account-and-profile/reference/permission-levels-for-a-personal-account-repository) - packages:write (Tier 1) [documented; from: Collaborators on a personal repository can pull (read) the contents of the repository and push (write) changes to the repository, manage issues, labels and milestones, create, merge and close pull requests, and publish packages.] - workflows:write (Tier 1) [inferred; from: Pushing includes files under .github/workflows; the docs give collaborators no lower level, and private repositories cannot grant read only.] - contents:write (Tier 2) [documented; from: Collaborators on a personal repository can pull (read) the contents of the repository and push (write) changes to the repository, manage issues, labels and milestones, create, merge and close pull requests, and publish packages.] - issues:write (Tier 2) [documented; from: Collaborators on a personal repository can pull (read) the contents of the repository and push (write) changes to the repository, manage issues, labels and milestones, create, merge and close pull requests, and publish packages.] - pull_requests:write (Tier 2) [documented; from: Collaborators on a personal repository can pull (read) the contents of the repository and push (write) changes to the repository, manage issues, labels and milestones, create, merge and close pull requests, and publish packages.] Docs: https://docs.github.com/en/account-and-profile/reference/permission-levels-for-a-personal-account-repository ### Owner [Organization, id org-owner] Tier 0, control plane Scope: The organization and every repository in it. Full administrative control of the organization. Tier 0 because it includes Members: Write (organization), Administration: Write (organization). Available on: Every setup (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - members:write (Tier 0) [documented; from: Invite people to join the organization; Edit and cancel invitations to join the organization; Remove members from the organization; Reinstate former members to the organization; Add and remove people from all teams; Promote organization members to team maintainer; Configure code review assignments; Delete all teams; Create teams; Move teams in an organization's hierarchy; Convert organization members to outside collaborators; Owners have full administrative access to the organization: every setting, role, webhook, secret and runner.] - organization_administration:write (Tier 0) [documented; from: Edit the organization's profile page; Delete the organization account, including all repositories; Limit interactions for certain users in public repositories; Manage the publication of GitHub Pages sites from repositories in the organization; Manage security and analysis settings; Manage the forking policy; Limit activity in public repositories in an organization; Manage the default branch name; Manage default labels; Manage pull request reviews in the organization; Owners have full administrative access to the organization: every setting, role, webhook, secret and runner.] - actions:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - administration:write (Tier 1) [documented; from: Manage individual, team, and outside collaborator access to the repository; Edit a repository's description; Manage topics; Enable wikis and restrict wiki editors; Configure pull request merges; Manage branch protection rules and repository rulesets; Merge pull requests on protected branches, even if there are no approving reviews; Create and edit repository social cards; Limit interactions in a repository; Add a repository to a team; Manage outside collaborator access to a repository; Change a repository's visibility; Make a repository a template; Change a repository's settings; Manage team and collaborator access to the repository; Edit the repository's default branch; Rename the repository's default branch; Manage webhooks and deploy keys; Manage the forking policy for a repository; Transfer repositories into the organization; Delete or transfer repositories out of the organization; Archive repositories; Display a sponsor button; Create autolink references to external resources, like Jira or Zendesk; Enable GitHub Discussions in a repository; Manage access to GitHub Advanced Security features; Enable the dependency graph for a private repository; Add collaborators to all repositories; Transfer repositories; Manage Dependabot security updates] - installation_repositories:write (Tier 1) [inferred; from: Purchase, install, manage billing for, and cancel GitHub Marketplace apps] - organization_custom_org_roles:write (Tier 1) [documented; from: Owners have full administrative access to the organization: every setting, role, webhook, secret and runner.] - organization_custom_properties:admin (Tier 1) [documented; from: Owners have full administrative access to the organization: every setting, role, webhook, secret and runner.] - organization_custom_roles:write (Tier 1) [documented; from: Owners have full administrative access to the organization: every setting, role, webhook, secret and runner.] - organization_hooks:write (Tier 1) [documented; from: Owners have full administrative access to the organization: every setting, role, webhook, secret and runner.] - organization_personal_access_token_requests:write (Tier 1) [documented; from: Owners have full administrative access to the organization: every setting, role, webhook, secret and runner.] - organization_secrets:write (Tier 1) [documented; from: Owners have full administrative access to the organization: every setting, role, webhook, secret and runner.] - organization_self_hosted_runners:write (Tier 1) [documented; from: Owners have full administrative access to the organization: every setting, role, webhook, secret and runner.] - packages:write (Tier 1) [documented; from: Publish packages; Delete and restore packages] - repository_custom_properties:write (Tier 1) [documented; from: Edit the custom property values for the repository] - repository_hooks:write (Tier 1) [documented; from: Manage webhooks and deploy keys] - secret_scanning_alerts:read (Tier 1) [documented; from: Unlike writers and maintainers, admins see every secret scanning alert in the repository, including the leaked values.] - secrets:write (Tier 1) [documented; from: Create, update, and delete GitHub Actions secrets on GitHub.com; Create, update, and delete GitHub Actions secrets using the REST API] - workflows:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - actions_variables:write (Tier 2) [documented; from: Create, update, and delete GitHub Actions variables on GitHub.com; Create, update, and delete GitHub Actions variables using the REST API] - checks:write (Tier 2) [documented; from: Create status checks] - codespaces:write (Tier 2) [inferred; from: Create codespaces for private repositories; Create codespaces for private repositories with Codespaces secrets access; Create codespaces for public repositories] - contents:write (Tier 2) [documented; from: Apply suggested changes to pull requests; Merge a pull request; Push to (write) the person or team's assigned repositories; Create and edit releases; Push to protected branches; Define code owners for a repository; Rename a branch other than the repository's default branch; Push (write) and clone (copy) all repositories in the organization] - discussions:write (Tier 2) [inferred; from: Hide anyone's comments on discussions; Create and edit categories for GitHub Discussions; Move a discussion to a different category; Transfer a discussion to a new repository; Manage pinned discussions; Lock and unlock discussions; Individually convert issues to discussions; Create new discussions and comment on existing discussions; Delete a discussion] - issues:write (Tier 2) [documented; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves; Apply/dismiss labels; Create, edit, delete labels; Close, reopen, and assign all issues and pull requests; Create, edit, delete milestones; Apply milestones; Mark duplicate issues and pull requests; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Lock conversations; Transfer issues; Delete an issue; Hide comments on writable commits, pull requests, and issues; Hide comments on all commits, pull requests, and issues] - organization_code_scanning_dismissal_requests:write (Tier 2) [documented; from: Review and manage code scanning dismissal requests] - organization_dependabot_dismissal_requests:write (Tier 2) [documented; from: Review Dependabot alert dismissal requests; Bypass Dependabot alert dismissal requests] - organization_secret_scanning_bypass_requests:write (Tier 2) [documented; from: Review and manage secret scanning bypass requests] - pages:write (Tier 2) [documented; from: Configure a publishing source for GitHub Pages] - pull_requests:write (Tier 2) [documented; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests; Approve or request changes to a pull request with required reviews; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Enable and disable auto-merge on a pull request; Request pull request reviews; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Mark a draft pull request as ready for review; Convert a pull request to a draft] - repository_advisories:write (Tier 2) [documented; from: Create security advisories] - repository_creation:write (Tier 2) [documented; from: Create repositories] - secret_scanning_alerts:write (Tier 2) [inferred; from: View and dismiss secret scanning alerts in a repository] - secret_scanning_dismissal_requests:write (Tier 2) [documented; from: Review and manage secret scanning dismissal requests] - security_events:write (Tier 2) [documented; from: List, dismiss, and delete code scanning alerts] - statuses:write (Tier 2) [documented; from: Create status checks] - vulnerability_alerts:write (Tier 2) [documented; from: Dismiss Dependabot alerts] - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository; View people with access to an organization repository] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases; View draft releases; Pull (read) all repositories in the organization] - members:read (Tier 3) [documented; from: See all organization members and teams] - organization_administration:read (Tier 3) [documented; from: Access the organization audit log] - organization_user_blocking:write (Tier 3) [documented; from: Block and unblock non-member contributors] - packages:read (Tier 3) [documented; from: View and install packages] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests; View security overview for the organization] - vulnerability_alerts:read (Tier 3) [documented; from: Receive Dependabot alerts for insecure dependencies in a repository; View security overview for the organization; Receive Dependabot alerts about insecure dependencies for all of an organization's repositories] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Edit wikis in private repositories; Report abusive or spammy content; Act as a designated code owner for a repository; View content exclusion settings for GitHub Copilot; View and edit billing information; Set scheduled reminders; @mention any visible team; Can be made a team maintainer; Set a team profile picture in all teams; Sponsor accounts and manage the organization's sponsorships; Manage email updates from sponsored accounts; Attribute your sponsorships to another organization; List apps in GitHub Marketplace Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### Member [Organization, id org-member] Tier 2, context-dependent, data plane Scope: The organization. Standard access to organization features. Repository access comes from the base permission, teams and direct grants. Tier 2 because it includes Repository creation: Write (repository). Available on: Every setup (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - repository_creation:write (Tier 2) [documented; from: Create repositories] - issues:write (Tier 2), in part as Tier 3: Moderation on repositories they can already write to. [inferred; from: Hide comments on writable commits, pull requests, and issues] - members:read (Tier 3) [documented; from: See all organization members and teams] - members:write (Tier 0), in part as Tier 3: Creates a team. Giving it repository access needs a repository admin. [inferred; from: Create teams] No matching permission: @mention any visible team; Can be made a team maintainer Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### Moderator [Organization, id org-moderator] Tier 2, context-dependent, data plane Scope: The organization's public spaces. Moderates discussions and community content. Tier 2 because it includes Repository creation: Write (repository). Available on: Free, Team, Enterprise Cloud, EMU (https://docs.github.com/en/enterprise-server@latest/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - repository_creation:write (Tier 2) [documented; from: Create repositories] - issues:write (Tier 2), in part as Tier 3: Moderation on repositories they can already write to. [inferred; from: Hide comments on writable commits, pull requests, and issues; Hide comments on all commits, pull requests, and issues] - members:read (Tier 3) [documented; from: See all organization members and teams] - members:write (Tier 0), in part as Tier 3: Creates a team. Giving it repository access needs a repository admin. [inferred; from: Create teams] - organization_administration:write (Tier 0), in part as Tier 3: Moderation setting. [inferred; from: Limit interactions for certain users in public repositories] - organization_user_blocking:write (Tier 3) [documented; from: Block and unblock non-member contributors] No matching permission: @mention any visible team; Can be made a team maintainer Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### Billing manager [Organization, id org-billing-manager] Tier 3, data plane Scope: Organization billing. Views and manages billing settings. None of its abilities map to a permission on the reference, so it gets the lowest tier. Available on: Free, Team, Enterprise Cloud (only in organizations not owned by an enterprise account) (https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-peoples-access-to-your-organization-with-roles/adding-a-billing-manager-to-your-organization) No matching permission: View and edit billing information; Sponsor accounts and manage the organization's sponsorships Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### Security manager [Organization, id org-security-manager] Tier 1, management plane Scope: The organization and every repository in it. Manages security policies, alerts and configurations across the organization. Tier 1 because it includes Secret scanning alerts: Read (repository), Administration: Write (organization) (in part). Available on: Every setup (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - organization_administration:write (Tier 0), in part as Tier 1: Security configurations for every repository. Can switch secret scanning, push protection and code scanning off org-wide. [inferred; from: Manage security and analysis settings] - secret_scanning_alerts:read (Tier 1) [documented; from: Security managers can view and manage security alerts in every repository in the organization.] - administration:write (Tier 1), in part as Tier 2: Can turn automated security fixes off. [inferred; from: Manage Dependabot security updates] - organization_code_scanning_dismissal_requests:write (Tier 2) [documented; from: Review and manage code scanning dismissal requests] - organization_dependabot_dismissal_requests:write (Tier 2) [documented; from: Review Dependabot alert dismissal requests; Bypass Dependabot alert dismissal requests] - organization_secret_scanning_bypass_requests:write (Tier 2) [documented; from: Review and manage secret scanning bypass requests] - repository_creation:write (Tier 2) [documented; from: Create repositories] - secret_scanning_alerts:write (Tier 2) [documented; from: Security managers can view and manage security alerts in every repository in the organization.] - secret_scanning_dismissal_requests:write (Tier 2) [documented; from: Review and manage secret scanning dismissal requests] - security_events:write (Tier 2) [documented; from: Security managers can view and manage security alerts in every repository in the organization.] - vulnerability_alerts:write (Tier 2) [documented; from: Security managers can view and manage security alerts in every repository in the organization.] - contents:read (Tier 3) [documented; from: Pull (read) all repositories in the organization] - issues:write (Tier 2), in part as Tier 3: Moderation on repositories they can already write to. [inferred; from: Hide comments on writable commits, pull requests, and issues; Hide comments on all commits, pull requests, and issues] - members:read (Tier 3) [documented; from: See all organization members and teams] - members:write (Tier 0), in part as Tier 3: Creates a team. Giving it repository access needs a repository admin. [inferred; from: Create teams] - security_events:read (Tier 3) [documented; from: View security overview for the organization] - vulnerability_alerts:read (Tier 3) [documented; from: View security overview for the organization; Receive Dependabot alerts about insecure dependencies for all of an organization's repositories] No matching permission: @mention any visible team; Can be made a team maintainer; Sponsor accounts and manage the organization's sponsorships Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### App manager [Organization, id org-app-manager] Tier 1, management plane Scope: GitHub Apps owned by the organization. Creates, edits and deletes GitHub App registrations owned by the organization. Managing an App registration is not a permission on the reference. It decides what an organization-owned App asks for and where its webhooks go, which reaches every repository that App is installed on. Available on: Every setup (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - repository_creation:write (Tier 2) [documented; from: Create repositories] - issues:write (Tier 2), in part as Tier 3: Moderation on repositories they can already write to. [inferred; from: Hide comments on writable commits, pull requests, and issues] - members:read (Tier 3) [documented; from: See all organization members and teams] - members:write (Tier 0), in part as Tier 3: Creates a team. Giving it repository access needs a repository admin. [inferred; from: Create teams] No matching permission: @mention any visible team; Can be made a team maintainer Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### CI/CD admin [Organization, id org-cicd-admin] Tier 1, management plane Scope: The organization's Actions setup. Manages Actions policies, runners, runner groups, hosted compute networks, secrets, variables and usage metrics. Tier 1 because it includes Network configurations: Write (organization), Secrets: Write (organization), Self-hosted runners: Write (organization) and 1 more. Available on: Every setup (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - organization_administration:write (Tier 0), in part as Tier 1: Only the Actions policy part of organization Administration: which actions may run and what workflow tokens get. [inferred; from: Actions policies are part of organization Administration.] - organization_network_configurations:write (Tier 1) [documented; from: Grants admin access to manage Actions policies, runners, runner groups, hosted compute network configurations, secrets, variables, and usage metrics.] - organization_secrets:write (Tier 1) [documented; from: Grants admin access to manage Actions policies, runners, runner groups, hosted compute network configurations, secrets, variables, and usage metrics.] - organization_self_hosted_runners:write (Tier 1) [documented; from: Grants admin access to manage Actions policies, runners, runner groups, hosted compute network configurations, secrets, variables, and usage metrics.] - organization_actions_variables:write (Tier 2) [documented; from: Grants admin access to manage Actions policies, runners, runner groups, hosted compute network configurations, secrets, variables, and usage metrics.] Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### All-repository read [Organization, id org-all-repo-read] Tier 3, data plane Scope: Every repository in the organization. Read on every repository. Tier 3 because nothing it includes goes beyond Tier 3. Available on: Every setup (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository] - codespaces:write (Tier 2), in part as Tier 3: A codespace with the person's own access and no repository secrets. [inferred; from: Create codespaces for private repositories; Create codespaces for public repositories] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases] - discussions:write (Tier 2), in part as Tier 3: Posting, not moderating. [inferred; from: Create new discussions and comment on existing discussions] - issues:write (Tier 2), in part as Tier 3: Opening and commenting on issues, not editing anyone else's. [inferred; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves] - packages:read (Tier 3) [documented; from: View and install packages] - pull_requests:write (Tier 2), in part as Tier 3: Proposes changes; nothing lands without someone with write access. [inferred; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Report abusive or spammy content Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### All-repository triage [Organization, id org-all-repo-triage] Tier 2, data plane Scope: Every repository in the organization. Triage on every repository. Tier 2 because it includes Discussions: Write (repository), Issues: Write (repository), Pull requests: Write (repository). Available on: Every setup (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - discussions:write (Tier 2) [inferred; from: Hide anyone's comments on discussions; Move a discussion to a different category; Lock and unlock discussions; Individually convert issues to discussions; Create new discussions and comment on existing discussions; Delete a discussion] - issues:write (Tier 2) [documented; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Apply milestones; Mark duplicate issues and pull requests] - pull_requests:write (Tier 2) [documented; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Request pull request reviews] - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository] - codespaces:write (Tier 2), in part as Tier 3: A codespace with the person's own access and no repository secrets. [inferred; from: Create codespaces for private repositories; Create codespaces for public repositories] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases] - packages:read (Tier 3) [documented; from: View and install packages] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Report abusive or spammy content Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### All-repository write [Organization, id org-all-repo-write] Tier 1, data plane Scope: Every repository in the organization. Write on every repository. Tier 1 because it includes Actions: Write (repository), Packages: Write (repository), Secrets: Write (repository) and 1 more. Available on: Every setup (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - actions:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - packages:write (Tier 1) [documented; from: Publish packages] - secrets:write (Tier 1) [documented; from: Create, update, and delete GitHub Actions secrets on GitHub.com; Create, update, and delete GitHub Actions secrets using the REST API] - workflows:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - actions_variables:write (Tier 2) [documented; from: Create, update, and delete GitHub Actions variables on GitHub.com; Create, update, and delete GitHub Actions variables using the REST API] - checks:write (Tier 2) [documented; from: Create status checks] - codespaces:write (Tier 2) [inferred; from: Create codespaces for private repositories; Create codespaces for private repositories with Codespaces secrets access; Create codespaces for public repositories] - contents:write (Tier 2) [documented; from: Apply suggested changes to pull requests; Merge a pull request; Push to (write) the person or team's assigned repositories; Create and edit releases; Define code owners for a repository; Rename a branch other than the repository's default branch] - discussions:write (Tier 2) [inferred; from: Hide anyone's comments on discussions; Create and edit categories for GitHub Discussions; Move a discussion to a different category; Transfer a discussion to a new repository; Manage pinned discussions; Lock and unlock discussions; Individually convert issues to discussions; Create new discussions and comment on existing discussions; Delete a discussion] - issues:write (Tier 2) [documented; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves; Apply/dismiss labels; Create, edit, delete labels; Close, reopen, and assign all issues and pull requests; Create, edit, delete milestones; Apply milestones; Mark duplicate issues and pull requests; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Lock conversations; Transfer issues] - pull_requests:write (Tier 2) [documented; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests; Approve or request changes to a pull request with required reviews; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Enable and disable auto-merge on a pull request; Request pull request reviews; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Mark a draft pull request as ready for review; Convert a pull request to a draft] - secret_scanning_alerts:write (Tier 2) [inferred; from: View and dismiss secret scanning alerts in a repository] - security_events:write (Tier 2) [documented; from: List, dismiss, and delete code scanning alerts] - statuses:write (Tier 2) [documented; from: Create status checks] - vulnerability_alerts:write (Tier 2) [documented; from: Dismiss Dependabot alerts] - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases; View draft releases] - packages:read (Tier 3) [documented; from: View and install packages] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests] - vulnerability_alerts:read (Tier 3) [documented; from: Receive Dependabot alerts for insecure dependencies in a repository] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Edit wikis in private repositories; Report abusive or spammy content; Act as a designated code owner for a repository Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### All-repository maintain [Organization, id org-all-repo-maintain] Tier 1, management plane Scope: Every repository in the organization. Maintain on every repository. Tier 1 because it includes Actions: Write (repository), Administration: Write (repository), Packages: Write (repository) and 2 more. Available on: Every setup (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - actions:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - administration:write (Tier 1) [documented; from: Edit a repository's description; Manage topics; Enable wikis and restrict wiki editors; Configure pull request merges; Create and edit repository social cards; Limit interactions in a repository; Enable GitHub Discussions in a repository] - packages:write (Tier 1) [documented; from: Publish packages] - secrets:write (Tier 1) [documented; from: Create, update, and delete GitHub Actions secrets on GitHub.com; Create, update, and delete GitHub Actions secrets using the REST API] - workflows:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - actions_variables:write (Tier 2) [documented; from: Create, update, and delete GitHub Actions variables on GitHub.com; Create, update, and delete GitHub Actions variables using the REST API] - checks:write (Tier 2) [documented; from: Create status checks] - codespaces:write (Tier 2) [inferred; from: Create codespaces for private repositories; Create codespaces for private repositories with Codespaces secrets access; Create codespaces for public repositories] - contents:write (Tier 2) [documented; from: Apply suggested changes to pull requests; Merge a pull request; Push to (write) the person or team's assigned repositories; Create and edit releases; Push to protected branches; Define code owners for a repository; Rename a branch other than the repository's default branch] - discussions:write (Tier 2) [inferred; from: Hide anyone's comments on discussions; Create and edit categories for GitHub Discussions; Move a discussion to a different category; Transfer a discussion to a new repository; Manage pinned discussions; Lock and unlock discussions; Individually convert issues to discussions; Create new discussions and comment on existing discussions; Delete a discussion] - issues:write (Tier 2) [documented; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves; Apply/dismiss labels; Create, edit, delete labels; Close, reopen, and assign all issues and pull requests; Create, edit, delete milestones; Apply milestones; Mark duplicate issues and pull requests; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Lock conversations; Transfer issues] - pages:write (Tier 2) [documented; from: Configure a publishing source for GitHub Pages] - pull_requests:write (Tier 2) [documented; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests; Approve or request changes to a pull request with required reviews; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Enable and disable auto-merge on a pull request; Request pull request reviews; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Mark a draft pull request as ready for review; Convert a pull request to a draft] - secret_scanning_alerts:write (Tier 2) [inferred; from: View and dismiss secret scanning alerts in a repository] - security_events:write (Tier 2) [documented; from: List, dismiss, and delete code scanning alerts] - statuses:write (Tier 2) [documented; from: Create status checks] - vulnerability_alerts:write (Tier 2) [documented; from: Dismiss Dependabot alerts] - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases; View draft releases] - packages:read (Tier 3) [documented; from: View and install packages] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests] - vulnerability_alerts:read (Tier 3) [documented; from: Receive Dependabot alerts for insecure dependencies in a repository] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Edit wikis in private repositories; Report abusive or spammy content; Act as a designated code owner for a repository; View content exclusion settings for GitHub Copilot Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### All-repository admin [Organization, id org-all-repo-admin] Tier 0, control plane Scope: Every repository in the organization. Admin on every repository. Admin on every repository at once: it can make every repository public, read and replace every repository secret, remove every branch protection and add collaborators anywhere. Each permission is Tier 1 on one repository; across the whole organization it is organization scale. Available on: Every setup (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles) - actions:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - administration:write (Tier 1) [documented; from: Manage individual, team, and outside collaborator access to the repository; Edit a repository's description; Manage topics; Enable wikis and restrict wiki editors; Configure pull request merges; Manage branch protection rules and repository rulesets; Merge pull requests on protected branches, even if there are no approving reviews; Create and edit repository social cards; Limit interactions in a repository; Add a repository to a team; Manage outside collaborator access to a repository; Change a repository's visibility; Make a repository a template; Change a repository's settings; Manage team and collaborator access to the repository; Edit the repository's default branch; Rename the repository's default branch; Manage webhooks and deploy keys; Manage the forking policy for a repository; Transfer repositories into the organization; Delete or transfer repositories out of the organization; Archive repositories; Display a sponsor button; Create autolink references to external resources, like Jira or Zendesk; Enable GitHub Discussions in a repository; Manage access to GitHub Advanced Security features; Enable the dependency graph for a private repository] - packages:write (Tier 1) [documented; from: Publish packages; Delete and restore packages] - repository_custom_properties:write (Tier 1) [documented; from: Edit the custom property values for the repository] - repository_hooks:write (Tier 1) [documented; from: Manage webhooks and deploy keys] - secret_scanning_alerts:read (Tier 1) [documented; from: Unlike writers and maintainers, admins see every secret scanning alert in the repository, including the leaked values.] - secrets:write (Tier 1) [documented; from: Create, update, and delete GitHub Actions secrets on GitHub.com; Create, update, and delete GitHub Actions secrets using the REST API] - workflows:write (Tier 1) [inferred; from: Create, edit, run, re-run, and cancel GitHub Actions workflows] - actions_variables:write (Tier 2) [documented; from: Create, update, and delete GitHub Actions variables on GitHub.com; Create, update, and delete GitHub Actions variables using the REST API] - checks:write (Tier 2) [documented; from: Create status checks] - codespaces:write (Tier 2) [inferred; from: Create codespaces for private repositories; Create codespaces for private repositories with Codespaces secrets access; Create codespaces for public repositories] - contents:write (Tier 2) [documented; from: Apply suggested changes to pull requests; Merge a pull request; Push to (write) the person or team's assigned repositories; Create and edit releases; Push to protected branches; Define code owners for a repository; Rename a branch other than the repository's default branch] - discussions:write (Tier 2) [inferred; from: Hide anyone's comments on discussions; Create and edit categories for GitHub Discussions; Move a discussion to a different category; Transfer a discussion to a new repository; Manage pinned discussions; Lock and unlock discussions; Individually convert issues to discussions; Create new discussions and comment on existing discussions; Delete a discussion] - issues:write (Tier 2) [documented; from: Open issues; Close issues they opened themselves; Reopen issues they closed themselves; Apply/dismiss labels; Create, edit, delete labels; Close, reopen, and assign all issues and pull requests; Create, edit, delete milestones; Apply milestones; Mark duplicate issues and pull requests; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Lock conversations; Transfer issues; Delete an issue] - pages:write (Tier 2) [documented; from: Configure a publishing source for GitHub Pages] - pull_requests:write (Tier 2) [documented; from: Send pull requests from forks of the team's assigned repositories; Submit reviews on pull requests; Approve or request changes to a pull request with required reviews; Apply/dismiss labels; Close, reopen, and assign all issues and pull requests; Enable and disable auto-merge on a pull request; Request pull request reviews; Edit and delete anyone's comments on commits, pull requests, and issues; Hide anyone's comments on issues, pull requests, and commits; Mark a draft pull request as ready for review; Convert a pull request to a draft] - repository_advisories:write (Tier 2) [documented; from: Create security advisories] - secret_scanning_alerts:write (Tier 2) [inferred; from: View and dismiss secret scanning alerts in a repository] - security_events:write (Tier 2) [documented; from: List, dismiss, and delete code scanning alerts] - statuses:write (Tier 2) [documented; from: Create status checks] - vulnerability_alerts:write (Tier 2) [documented; from: Dismiss Dependabot alerts] - actions:read (Tier 3) [documented; from: View GitHub Actions workflow runs] - administration:read (Tier 3) [documented; from: View rulesets for a repository] - contents:read (Tier 3) [documented; from: Pull from the person or team's assigned repositories; Fork the person or team's assigned repositories; View published releases; View draft releases] - packages:read (Tier 3) [documented; from: View and install packages] - security_events:read (Tier 3) [documented; from: View code scanning alerts on pull requests] - vulnerability_alerts:read (Tier 3) [documented; from: Receive Dependabot alerts for insecure dependencies in a repository] No matching permission: Edit and delete their own comments; Have an issue assigned to them; Edit wikis in public repositories; Edit wikis in private repositories; Report abusive or spammy content; Act as a designated code owner for a repository; View content exclusion settings for GitHub Copilot Docs: https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/permissions-of-predefined-organization-roles ### Team maintainer [Team, id team-maintainer] Tier 1, context-dependent, control plane Scope: One team and the repositories it has. Manages a team's members and settings. Tier 1 because it includes Members: Write (organization) (in part). Available on: Every setup (https://docs.github.com/en/organizations/organizing-members-into-teams/assigning-the-team-maintainer-role-to-a-team-member) - members:write (Tier 0), in part as Tier 1: Only for their own team, but anyone they add gets every repository role the team holds. On a team with Admin on production repositories, that is a path to those repositories. [documented; from: Team maintainers can add and remove team members and change the team's settings.] Docs: https://docs.github.com/en/organizations/organizing-members-into-teams/assigning-the-team-maintainer-role-to-a-team-member ### Enterprise owner [Enterprise, id enterprise-owner] Tier 0, control plane Scope: Every organization in the enterprise. Manages all enterprise settings, members and policies. Tier 0 because it includes Custom enterprise roles: Write (enterprise), Enterprise SCIM: Write (enterprise), Enterprise teams: Write (enterprise). Available on: Enterprise Cloud, EMU, Server (https://docs.github.com/en/enterprise-server@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles) - enterprise_custom_enterprise_roles:write (Tier 0) [documented; from: Enterprise owners have complete control over the enterprise and can take every action, including managing administrators, members, settings, policy and billing.] - enterprise_scim:write (Tier 0) [documented; from: Enterprise owners have complete control over the enterprise and can take every action, including managing administrators, members, settings, policy and billing.] - enterprise_teams:write (Tier 0) [documented; from: Enterprise owners have complete control over the enterprise and can take every action, including managing administrators, members, settings, policy and billing.] - enterprise_credentials:write (Tier 1) [documented; from: Enterprise owners have complete control over the enterprise and can take every action, including managing administrators, members, settings, policy and billing.] - enterprise_custom_properties:write (Tier 1) [documented; from: Enterprise owners have complete control over the enterprise and can take every action, including managing administrators, members, settings, policy and billing.] - enterprise_organization_installations:write (Tier 1) [documented; from: Enterprise owners have complete control over the enterprise and can take every action, including managing administrators, members, settings, policy and billing.] - enterprise_billing:write (Tier 2) [documented; from: Enterprise owners have complete control over the enterprise and can take every action, including managing administrators, members, settings, policy and billing.] Docs: https://docs.github.com/en/enterprise-cloud@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles ### Enterprise billing manager [Enterprise, id enterprise-billing-manager] Tier 2, data plane Scope: Enterprise billing. Views and manages licenses and usage-based billing. Tier 2 because it includes Enterprise billing: Write (enterprise), Enterprise Licensing: Write (enterprise). Available on: Enterprise Cloud, EMU (https://docs.github.com/en/enterprise-server@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles) - enterprise_billing:write (Tier 2) [documented; from: Billing managers only have access to your enterprise's billing settings.] - enterprise_licensing:write (Tier 2) [documented; from: Billing managers only have access to your enterprise's billing settings.] Docs: https://docs.github.com/en/enterprise-cloud@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles ### Enterprise App manager [Enterprise, id enterprise-app-manager] Tier 1, management plane Scope: GitHub Apps owned by the enterprise. Views, creates, edits and deletes enterprise-owned App registrations. Cannot install or uninstall Apps. Managing an App registration is not a permission on the reference. It decides what an enterprise-owned App asks for and where its webhooks go, which reaches every organization the App is installed in. Available on: Enterprise Cloud, EMU, Server (https://docs.github.com/en/enterprise-server@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles) Docs: https://docs.github.com/en/enterprise-cloud@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles ### Enterprise security manager [Enterprise, id enterprise-security-manager] Tier 1, data plane Scope: Every organization in the enterprise. Manages security configurations and alerts enterprise-wide (public preview). Tier 1 because it includes Secret scanning alerts: Read (repository). Available on: Enterprise Cloud (public preview), EMU (public preview), Server (public preview) (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles) - secret_scanning_alerts:read (Tier 1) [documented; from: They have read access for code in all repositories and write access for all security alerts in the enterprise.] - secret_scanning_alerts:write (Tier 2) [documented; from: They have read access for code in all repositories and write access for all security alerts in the enterprise.] - security_events:write (Tier 2) [documented; from: They have read access for code in all repositories and write access for all security alerts in the enterprise.] - vulnerability_alerts:write (Tier 2) [documented; from: They have read access for code in all repositories and write access for all security alerts in the enterprise.] - contents:read (Tier 3) [documented; from: They have read access for code in all repositories and write access for all security alerts in the enterprise.] Docs: https://docs.github.com/en/enterprise-cloud@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles ### Enterprise user [Enterprise, id enterprise-user] Tier 3, context-dependent, data plane Scope: The enterprise. A member with no administrative access. Access comes from organization roles. None of its abilities map to a permission on the reference, so it gets the lowest tier. Available on: Enterprise Cloud, EMU, Server (https://docs.github.com/en/enterprise-server@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles) Docs: https://docs.github.com/en/enterprise-cloud@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles ### Guest collaborator [Enterprise, id enterprise-guest-collaborator] Tier 3, context-dependent, data plane Scope: Only what they are added to. A managed user with limited access by default (Enterprise Managed Users only). None of its abilities map to a permission on the reference, so it gets the lowest tier. Available on: EMU (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles) Docs: https://docs.github.com/en/enterprise-cloud@latest/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/abilities-of-roles ### Site administrator (Enterprise Server) [Enterprise, id ghes-site-admin] Tier 0, control plane Scope: The whole Enterprise Server instance. Administers the GitHub Enterprise Server instance: users, organizations and repositories across it, and can promote other users to site administrator. Classic tokens for this role can carry the site_admin scope, which only exists on Enterprise Server. Above every organization and enterprise role on the instance, and able to make more site administrators. No permission on the reference describes it. Available on: Server (https://docs.github.com/en/enterprise-server@latest/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/promoting-or-demoting-a-site-administrator) Docs: https://docs.github.com/en/enterprise-server@latest/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/promoting-or-demoting-a-site-administrator ## Access Tiers 2.0: classic scopes (41) A scope's tier is the highest tier among the permissions it reaches; a top-level scope includes its sub-scopes. documented = shown by GitHub's REST docs, inferred = stated in GitHub's scope descriptions. - (no scope): Tier 3, data plane, on Every setup. Tier 3 because nothing it reaches goes beyond Tier 3. Public data only. Any token can read it. Reaches: metadata:read [inferred] - repo: Tier 0, management plane, on Every setup. Tier 0 because it reaches Administration: Write (organization). Reaches every repository the user can access, in every organization they belong to; admin endpoints only work where the user is an admin. For an organization owner it reaches much further: GitHub documents repo as enough for PATCH /orgs/{org} (organization settings, including the base repository permission), organization secrets and runner registration. That is why repo is Tier 0 even though most of what it covers is repository-level. Reaches: organization_administration:write [documented], actions:write [documented], administration:write [documented], environments:write [documented], organization_secrets:write [documented], organization_self_hosted_runners:write [documented], repository_hooks:write [documented], secret_scanning_alerts:read [documented], secrets:write [documented], workflows:write [documented], actions_variables:write [documented], agent_secrets:write [documented], agent_variables:write [documented], codespaces_secrets:write [documented], contents:write [documented], dependabot_secrets:write [documented], deployments:write [documented], organization_actions_variables:write [documented], organization_agent_secrets:write [documented], organization_agent_variables:write [documented], organization_code_scanning_dismissal_requests:write [documented], organization_dependabot_dismissal_requests:write [documented], organization_secret_scanning_bypass_requests:write [documented], pages:write [documented], repo_secret_scanning_dismissal_requests:write [documented], repository_advisories:write [documented], repository_creation:write [documented], secret_scanning_alerts:write [documented], secret_scanning_bypass_requests:write [documented], secret_scanning_dismissal_requests:write [documented], security_events:write [documented], statuses:write [inferred], vulnerability_alerts:write [documented], actions_variables:read [documented], actions:read [documented], administration:read [documented], agent_secrets:read [documented], agent_variables:read [documented], checks:read [documented], code_quality:read [documented], contents:read [documented], copilot_agent_settings:read [documented], dependabot_secrets:read [documented], environments:read [documented], members:read [documented], metadata:read [documented], organization_actions_variables:read [documented], organization_administration:read [documented], organization_agent_secrets:read [documented], organization_agent_variables:read [documented], organization_campaigns:read [documented], organization_campaigns:write [documented], organization_code_scanning_dismissal_requests:read [documented], organization_custom_roles:read [documented], organization_dependabot_dismissal_requests:read [documented], organization_secret_scanning_bypass_requests:read [documented], organization_secrets:read [documented], pages:read [documented], repository_advisories:read [documented], repository_hooks:read [documented], secret_scanning_bypass_requests:read [documented], secrets:read [documented], security_events:read [documented], vulnerability_alerts:read [documented] - repo:status (part of repo): Tier 2, data plane, on Every setup. Tier 2 because it reaches Commit statuses: Write (repository). Reaches: statuses:write [inferred] - repo_deployment (part of repo): Tier 2, data plane, on Every setup. Tier 2 because it reaches Deployments: Write (repository). Reaches: deployments:write [documented] - public_repo (part of repo): Tier 2, data plane, on Every setup. Tier 2 because it reaches Repository creation: Write (repository), Contents: Write (repository), Deployments: Write (repository) and 1 more. Same as repo, but only for public repositories. Write access to a widely used public repository is still a supply chain risk. Reaches: contents:write [inferred], deployments:write [inferred], repository_creation:write [documented], statuses:write [inferred], administration:read [documented], contents:read [documented] - repo:invite (part of repo): Tier 3, data plane, on Every setup. It reaches no permission on the reference, so it gets the lowest tier. Accepts or declines the user's own repository invitations. No permission on the reference covers it. Reaches: nothing on the reference - security_events (part of repo): Tier 1, data plane, on Every setup. Tier 1 because it reaches Secret scanning alerts: Read (repository). Reaches: secret_scanning_alerts:read [documented], organization_code_scanning_dismissal_requests:write [documented], organization_dependabot_dismissal_requests:write [documented], organization_secret_scanning_bypass_requests:write [documented], repo_secret_scanning_dismissal_requests:write [documented], secret_scanning_alerts:write [documented], secret_scanning_bypass_requests:write [documented], secret_scanning_dismissal_requests:write [documented], security_events:write [documented], vulnerability_alerts:write [documented], contents:read [documented], organization_campaigns:read [documented], organization_campaigns:write [documented], organization_code_scanning_dismissal_requests:read [documented], organization_dependabot_dismissal_requests:read [documented], organization_secret_scanning_bypass_requests:read [documented], secret_scanning_bypass_requests:read [documented], security_events:read [documented], vulnerability_alerts:read [documented] - admin:repo_hook: Tier 1, management plane, on Every setup. Tier 1 because it reaches Webhooks: Write (repository). Reaches: repository_hooks:write [inferred], repository_hooks:read [documented] - write:repo_hook (part of admin:repo_hook): Tier 1, management plane, on Every setup. Tier 1 because it reaches Webhooks: Write (repository). Reaches: repository_hooks:write [documented] - read:repo_hook (part of admin:repo_hook): Tier 3, management plane, on Every setup. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: repository_hooks:read [documented] - admin:org: Tier 0, control plane, on Every setup. Tier 0 because it reaches Administration: Write (organization), Members: Write (organization). Reaches: members:write [documented], organization_administration:write [documented], organization_custom_org_roles:write [documented], organization_custom_roles:write [documented], organization_private_registries:write [documented], organization_secrets:write [documented], organization_self_hosted_runners:write [documented], codespaces_lifecycle_admin:write [documented], codespaces:write [documented], organization_actions_variables:write [documented], organization_agent_secrets:write [documented], organization_agent_variables:write [documented], organization_codespaces_secrets:write [documented], organization_codespaces_settings:write [documented], organization_codespaces:write [documented], organization_copilot_agent_settings:write [documented], organization_copilot_spaces:write [documented], organization_dependabot_secrets:write [documented], codespaces:read [documented], issue_fields:read [documented], issue_fields:write [documented], issue_types:read [documented], issue_types:write [documented], members:read [documented], organization_actions_variables:read [documented], organization_administration:read [documented], organization_agent_secrets:read [documented], organization_agent_variables:read [documented], organization_codespaces_secrets:read [documented], organization_codespaces:read [documented], organization_copilot_agent_settings:read [documented], organization_copilot_metrics:read [documented], organization_copilot_seat_management:read [documented], organization_copilot_seat_management:write [documented], organization_copilot_spaces:read [documented], organization_custom_org_roles:read [documented], organization_custom_roles:read [documented], organization_dependabot_secrets:read [documented], organization_private_registries:read [documented], organization_secrets:read [documented], organization_self_hosted_runners:read [documented] - write:org (part of admin:org): Tier 1, management plane, on Every setup. Tier 1 because it reaches Administration: Write (organization) in part. Reaches: organization_administration:write (in part, Tier 1) [inferred], organization_copilot_spaces:write [documented], organization_administration:read [documented], organization_copilot_seat_management:write [documented] - read:org (part of admin:org): Tier 2, data plane, on Every setup. Tier 2 because it reaches Copilot Spaces: Write (organization). Reaches: organization_copilot_spaces:write [documented], issue_fields:read [documented], issue_types:read [documented], members:read [documented], organization_administration:read [documented], organization_copilot_metrics:read [documented], organization_copilot_seat_management:read [documented], organization_copilot_spaces:read [documented] - admin:public_key: Tier 1, control plane, on Every setup. Tier 1 because it reaches Git SSH keys: Write (user). Reaches: keys:write [documented], keys:read [documented] - write:public_key (part of admin:public_key): Tier 1, control plane, on Every setup. Tier 1 because it reaches Git SSH keys: Write (user). Reaches: keys:write [documented] - read:public_key (part of admin:public_key): Tier 3, control plane, on Every setup. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: keys:read [documented] - admin:org_hook: Tier 1, management plane, on Every setup. Tier 1 because it reaches Webhooks: Write (organization). An OAuth App token only reaches hooks that OAuth App created; a classic PAT reaches all of them. Reaches: organization_hooks:write [documented], organization_hooks:read [documented] - gist: Tier 3, data plane, on Every setup. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: gists:write [inferred] - notifications: Tier 3, data plane, on Every setup. It reaches no permission on the reference, so it gets the lowest tier. Notifications and watching. No permission on the reference covers it. Reaches: nothing on the reference - user: Tier 2, control plane, on Every setup. Tier 2 because it reaches Email addresses: Write (user). Reaches: emails:write [documented], emails:read [documented], followers:write [documented], profile:write [documented] - read:user (part of user): Tier 3, data plane, on Every setup. It reaches no permission on the reference, so it gets the lowest tier. Reads the user's profile. No permission on the reference covers it. Reaches: nothing on the reference - user:email (part of user): Tier 3, control plane, on Every setup. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: emails:read [documented] - user:follow (part of user): Tier 3, data plane, on Every setup. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: followers:write [documented] - project: Tier 3, data plane, on Free, Team, Enterprise Cloud, EMU. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: organization_projects:read [inferred], organization_projects:write [inferred] - read:project (part of project): Tier 3, data plane, on Free, Team, Enterprise Cloud, EMU. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: organization_projects:read [inferred] - delete_repo: Tier 1, management plane, on Every setup. Tier 1 because it reaches Administration: Write (repository). Reaches: administration:write [inferred] - write:packages: Tier 1, data plane, on Every setup. Tier 1 because it reaches Packages: Write (repository). Reaches: packages:write [inferred] - read:packages: Tier 3, data plane, on Every setup. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: packages:read [inferred] - delete:packages: Tier 2, data plane, on Every setup. Tier 2 because it reaches Packages: Write (repository) in part. Reaches: packages:write (in part, Tier 2) [inferred] - admin:gpg_key: Tier 2, data plane, on Every setup. Tier 2 because it reaches GPG keys: Write (user). Reaches: gpg_keys:write [documented], gpg_keys:read [documented] - write:gpg_key (part of admin:gpg_key): Tier 2, data plane, on Every setup. Tier 2 because it reaches GPG keys: Write (user). Reaches: gpg_keys:write [documented] - read:gpg_key (part of admin:gpg_key): Tier 3, data plane, on Every setup. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: gpg_keys:read [documented] - codespace: Tier 2, data plane, on Free, Team, Enterprise Cloud, EMU. Tier 2 because it reaches Codespaces: Write (repository), Codespaces user secrets: Write (user), Codespaces lifecycle admin: Write (repository). Reaches: codespaces_lifecycle_admin:write [documented], codespaces_user_secrets:write [documented], codespaces:write [documented], codespaces_lifecycle_admin:read [documented], codespaces_metadata:read [documented], codespaces_user_secrets:read [documented], codespaces:read [documented] - workflow: Tier 1, data plane, on Every setup. Tier 1 because it reaches Workflows: Write (repository). Without it, a classic token cannot push changes under .github/workflows. With it, the holder can change what workflows run and reach their secrets. Reaches: workflows:write [documented], contents:write [documented] - admin:enterprise: Tier 0, control plane, on Enterprise Cloud, EMU, Server. Tier 0 because it reaches Custom enterprise roles: Write (enterprise). Reaches: enterprise_custom_enterprise_roles:write [documented], enterprise_credentials:write [documented], organization_self_hosted_runners:write [inferred], enterprise_ai_controls:write [documented], enterprise_billing:write [inferred], enterprise_ai_controls:read [documented], enterprise_copilot_usage:read [documented] - manage_runners:enterprise (part of admin:enterprise): Tier 1, management plane, on Enterprise Cloud, EMU, Server. Tier 1 because it reaches Self-hosted runners: Write (organization). Reaches: organization_self_hosted_runners:write [inferred] - manage_billing:enterprise (part of admin:enterprise): Tier 2, data plane, on Enterprise Cloud, EMU, Server. Tier 2 because it reaches Enterprise billing: Write (enterprise). Reaches: enterprise_billing:write [inferred] - read:enterprise (part of admin:enterprise): Tier 3, data plane, on Enterprise Cloud, EMU, Server. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: enterprise_copilot_usage:read [documented] - read:audit_log: Tier 3, management plane, on Every setup. Tier 3 because nothing it reaches goes beyond Tier 3. Reaches: organization_administration:read [documented] - offline_access: Tier 3, data plane, on Free, Team, Enterprise Cloud, EMU. It reaches no permission on the reference, so it gets the lowest tier. Asks for an expiring token with a refresh token. Grants nothing by itself. Reaches: nothing on the reference - site_admin: Tier 0, control plane, on Server. Grants site administrator API access on the Enterprise Server instance: users, organizations and repositories across it. That is above every organization and enterprise role (see the Site administrator role). Only on Enterprise Server. Its REST endpoints are not on the Enterprise Cloud pages ghtier reads, so it shows no reach here. Reaches: nothing on the reference Token types and the highest tier each can carry: - Classic personal access token: up to Tier 0. A token the user creates with classic scopes. It works in every organization the user belongs to. Decided by: Its scopes, and the roles of the user who owns it. repo on an organization owner's token reaches Tier 0. - OAuth App token: up to Tier 0. A token an OAuth App holds after a user approves it. Uses the same scopes as a classic PAT and usually does not expire. Decided by: The scopes on the consent screen, and the roles of the user who approved it. - Fine-grained personal access token: up to Tier 0. A token for one owner (a user or an organization) and chosen repositories, with permissions from the Permissions tab. Expiry is required. Decided by: The permissions picked when it was created, limited by the user's own access. - GitHub App installation token: up to Tier 0. A 1-hour token an App mints with its private key. It has the App's permissions on the installation's repositories, with no user behind it. Decided by: The App's permissions and which repositories it is installed on. Whoever holds the private key can mint new ones. - GitHub App user access token: up to Tier 0. A token an App holds when a user signs in to it. Capped by both the App's permissions and the user's own access. 8 hours, refreshable for 6 months. Decided by: The lower of the App's permissions and the user's role. - GITHUB_TOKEN: up to Tier 1. The token every workflow job gets for its own repository, valid for the job. Its permissions come from the workflow's permissions block or the repository default. Decided by: The permissions block in the workflow, falling back to the organization or repository default. ## Attack paths through permissions Which permissions each attack technique needs and what fixes it (ids from the permissions and checklist sections): - Submit malicious code / Push directly, without review [no-review]: enabled by contents:write; fixed by repo-branch-protection, repo-rulesets - Submit malicious code / Approve your own PR from a second account [sock-puppet]: enabled by administration:write, pull_requests:write; fixed by repo-branch-protection, repo-codeowners, org-access-review - Submit malicious code / Have a workflow approve the PR [robot-approval]: enabled by pull_requests:write, workflows:write; fixed by cicd-default-permissions-read, repo-branch-protection - Submit malicious code / Push more commits after approval [stale-approval]: enabled by contents:write, pull_requests:write; fixed by repo-branch-protection, repo-rulesets - Submit malicious code / Approve someone else's PR, then push to it [last-push]: enabled by contents:write, pull_requests:write; fixed by repo-branch-protection, repo-rulesets - Submit malicious code / Hide it in a change nobody can review [unreviewable]: enabled by contents:write; fixed by repo-codeowners, repo-dependabot - Submit malicious code / Use a trusted colleague's account [other-account]: enabled by contents:write, pull_requests:write; fixed by auth-mfa-org, repo-signed-commits - Submit malicious code / Trick or outlast the reviewer [trick-reviewer]: enabled by pull_requests:write; fixed by repo-codeowners - Submit malicious code / Bypass or switch off the rules [admin-bypass]: enabled by administration:write, organization_administration:write; fixed by repo-rulesets, org-limit-owners, org-audit-log-siem - Delete code or cover tracks / Force push or delete a protected branch [force-push]: enabled by contents:write, administration:write; fixed by repo-prevent-branch-deletion, repo-signed-commits, org-audit-log-siem - Delete code or cover tracks / Delete branches and workflow runs [delete-runs]: enabled by actions:write, contents:write; fixed by org-audit-log-siem - Point a release tag at a bad commit / Push a release tag to an unreviewed commit [push-tag]: enabled by contents:write; fixed by repo-rulesets - Point a release tag at a bad commit / Repoint existing tags on a published Action [repoint-tags]: enabled by contents:write; fixed by repo-rulesets, cicd-pin-actions-sha - Steal CI secrets and tokens / Add a workflow that dumps secrets [inject-workflow]: enabled by workflows:write; fixed by repo-codeowners, repo-rulesets, cicd-secret-inventory, cicd-oidc-federation - Steal CI secrets and tokens / Run PR code in a privileged workflow [pwn-request]: enabled by contents:write, id_token:write; fixed by cicd-audit-pull-request-target, cicd-default-permissions-read, cicd-fork-approval - Steal CI secrets and tokens / Read secrets from the runner [runner-memory]: enabled by contents:write; fixed by cicd-pin-actions-sha, cicd-verified-actions-only, cicd-default-permissions-read - Steal CI secrets and tokens / Steal a session and read secrets from a CI vendor [session-theft]: enabled by no permission (see the technique); fixed by org-ip-allowlist, cicd-rotation-runbook - Poison the build or published package / Poison a shared Actions cache [cache-poison]: enabled by no permission (see the technique); fixed by cicd-audit-pull-request-target, repo-artifact-attestation - Poison the build or published package / Trigger the publish workflow from a tampered branch [dispatch-publish]: enabled by actions:write, contents:write; fixed by repo-rulesets, repo-branch-protection, cicd-default-permissions-read - Poison the build or published package / Backdoor a persistent self-hosted runner [runner-backdoor]: enabled by no permission (see the technique); fixed by cicd-no-selfhosted-public, cicd-fork-approval - Poison the build or published package / Publish a malicious version with a stolen token [malicious-release]: enabled by packages:write; fixed by repo-artifact-attestation, cicd-oidc-federation, cicd-trusted-publishing, cicd-environment-reviewers, cicd-rotation-runbook - Read or expose private code / Clone private repos with a stolen token [token-read]: enabled by contents:read; fixed by auth-pat-expiry-policy, auth-disable-classic-pats, auth-audit-stale-creds, org-ip-allowlist - Read or expose private code / Flip private repos to public [visibility-flip]: enabled by administration:write, organization_administration:write; fixed by org-restrict-member-privileges, org-limit-owners, org-audit-log-siem - Read or expose private code / Get an agent to copy private code into public [agent-leak]: enabled by contents:read, issues:write; fixed by org-review-installed-apps, org-approval-for-new-apps - Keep access / Add a guest collaborator or new app [add-collaborator]: enabled by administration:write, members:write, installation_repositories:write; fixed by org-restrict-member-privileges, org-access-review, org-review-installed-apps, org-approval-for-new-apps - Keep access / Register the attacker's machine as a runner [register-runner]: enabled by administration:write, organization_self_hosted_runners:write; fixed by cicd-no-selfhosted-public, org-audit-log-siem - Keep access / Leave a dormant workflow behind [dormant-workflow]: enabled by workflows:write; fixed by repo-codeowners, repo-branch-protection, org-audit-log-siem - Take over the GHES appliance / Forge a login [auth-bypass]: enabled by no permission (see the technique); fixed by nothing on the checklist - Take over the GHES appliance / Escalate from a GHES role to root [admin-to-root]: enabled by no permission (see the technique); fixed by org-limit-owners Escalations, one permission leading to more: - Change a workflow, get its secrets [workflow-to-secrets]: from workflows:write. Commit a workflow, or change an existing one, that runs on push and sends the repository's secrets out. A workflow can also ask for any token permission, including id-token: write. Gains: Every secret the repository's workflows can read, and cloud access through OIDC. (id_token:write). Seen in: user-dependabot-impersonation, org-ghostaction, org-shai-hulud, org-salesloft-drift - Move release tags on a widely used Action [tags-to-downstream]: from contents:write. Force-move existing version tags on an Action's repository to a malicious commit. Gains: Code execution, with their secrets, in every repository that pins the Action by tag.. Seen in: app-tj-actions-changed-files, org-teampcp - Start a privileged publish job by hand [dispatch-to-publish]: from actions:write. Run the publish workflow with workflow_dispatch against a branch whose build script was changed. Gains: Whatever the publish job holds: package registry tokens and the right to publish. (packages:write). Seen in: org-nx-s1ngularity - Flip private repositories to public [admin-to-public]: from administration:write or organization_administration:write. Change repository visibility, one repository at a time or in bulk with an owner token. Gains: Private source code exposed to everyone.. Seen in: org-toptal, org-nx-s1ngularity, org-shai-hulud - Register a self-hosted runner [admin-to-runner]: from administration:write or organization_self_hosted_runners:write. Create a runner registration token and register a machine the attacker controls. Gains: Every job routed to that runner, with the secrets those jobs receive, and a foothold that outlives the stolen token.. Seen in: org-shai-hulud - Add a collaborator or guest that outlives the attack [members-to-collaborator]: from members:write or administration:write. Add an outside collaborator, a guest, a deploy key or an App grant under the attacker's control. Gains: Access that survives password resets and token revocation. (contents:write). Seen in: org-salesloft-drift - Add yourself to a team with repository access [team-maintainer-to-repos]: from The Team maintainer role on a team with repository access. A team maintainer adds an account they control to their team. Gains: Every repository role the team holds, often Write or Admin on production repositories. (administration:write, workflows:write). Seen in: none on this site - Change a shared reusable workflow [shared-workflow-to-consumers]: from contents:write. Push to the repository that holds a reusable workflow other repositories call by branch or tag. Callers that pass secrets: inherit hand it every secret they have. Gains: Code running in every calling repository's workflow, with the secrets and GITHUB_TOKEN those runs get: an internal version of a compromised Action. (workflows:write). Seen in: none on this site - Harvest leaked secrets from scanning alerts [secret-scanning-harvest]: from secret_scanning_alerts:read. List the secret scanning alerts. Each one includes the leaked value, and many are still valid. Gains: Live credentials found in code and history: cloud keys, tokens and passwords, often for systems outside GitHub.. Seen in: none on this site - Join any organization as an enterprise owner [enterprise-owner-joins-org]: from The Enterprise owner role. An enterprise owner can join any organization owned by the enterprise as a member or as an organization owner. Gains: Organization owner in every organization of the enterprise, even ones they were never added to. (organization_administration:write, members:write). Seen in: none on this site - Claim the old name of a renamed Action [repojacking-action]: from An Action or reusable workflow whose owner renamed or deleted their account or repository. Register the freed owner or repository name and publish code at the same path. Workflows that still use the old owner/repo@ref fetch the new owner's code. Gains: Code running in every workflow that still uses the old path, with its secrets and GITHUB_TOKEN. (workflows:write). Seen in: none on this site - Take the workflow token from a runner [runner-code-to-token]: from Code already running in a workflow job: a compromised Action, dependency or pull request. Read GITHUB_TOKEN from runner memory or from .git/config, where actions/checkout leaves it unless told not to. Gains: The job's token, often with write access to the repository. (contents:write). Seen in: app-tj-actions-changed-files, org-self-hosted-runner-attacks, app-artipacked - Poison a cache the release build restores [fork-pr-to-cache]: from A pull request from a fork whose workflow can write to the shared Actions cache. Write a cache entry under a key the release workflow later restores. Gains: Attacker files inside the release build, and with them the release job's credentials. (packages:write). Seen in: org-ultralytics, org-cache-poisoning - Persist on a non-ephemeral self-hosted runner [fork-pr-to-runner]: from A pull request from a fork that runs on a self-hosted runner that is not wiped between jobs. Leave a process or file behind that the next job, often a release, picks up. Gains: The next jobs on that runner, with their secrets and tokens. (contents:write). Seen in: org-self-hosted-runner-attacks Permissions abused per incident: - user-dependabot-impersonation: contents:write, workflows:write - user-mercedes-benz-token-leak: contents:read - user-oauth-consent-phishing: contents:read, contents:write - user-circleci-session-theft: none (not through a permission) - user-nx-console-github-breach: contents:read - user-glassworm: contents:write - app-heroku-travisci-oauth-theft: contents:read - app-tj-actions-changed-files: contents:write - app-artipacked: contents:write, actions:read - app-agentic-mcp-tokens: contents:read, issues:write, pull_requests:write - app-installation-token-abuse: contents:write, pull_requests:write - org-pwn-requests: contents:write, id_token:write - org-ultralytics: contents:write - org-nx-s1ngularity: actions:write, contents:write - org-self-hosted-runner-attacks: contents:write - org-ghostaction: workflows:write - org-toptal: organization_administration:write, administration:write - org-salesloft-drift: administration:write, workflows:write - org-shai-hulud: contents:read, contents:write, workflows:write, administration:write - org-hackerbot-claw: contents:write - org-teampcp: contents:write - org-cache-poisoning: none (not through a permission) - org-prt-scan: contents:write - org-megalodon: contents:write, workflows:write - org-chaindrop: contents:write, workflows:write - ent-cve-2024-4985: none (not through a permission) - ent-cve-2025-11578: none (not through a permission) - ent-cve-2025-3509: none (not through a permission) - ent-cve-2026-3854: contents:write - ent-cve-2024-1374: none (not through a permission) - ent-cve-2022-23732: none (not through a permission) ## Strange findings (15) Places where GitHub access works differently from what most people expect, each with the GitHub source that states it. ### Removing someone in the IdP does not stop their tokens [sso-removal-keeps-tokens] Applies to: Enterprise Cloud. Involves: key:organization-saml-sso, key:enterprise-saml-sso, key:enterprise-emu-provisioning, key:ssh-key, role:org-member Expected: When a person is removed or disabled in the identity provider, they lose access to the organization. Actual: With SAML SSO and no SCIM, removing a user in the IdP does not remove them from the organization. Their SSO session expires, but personal access tokens, SSH keys and OAuth tokens they already authorized for SSO keep working until an owner removes the member or revokes the authorizations. With SCIM the member is removed automatically. With Enterprise Managed Users the account is suspended and its tokens and keys are deleted. Check: Compare the organization's SSO credential authorizations with the people who are still active in the IdP. Anyone who is gone from the IdP but still listed can use those credentials. Command: gh api /orgs/ORG/credential-authorizations --paginate \ --jq '.[] | [.login, .credential_type, .credential_authorized_at, .credential_accessed_at] | @tsv' Fix: Turn on SCIM provisioning so removal in the IdP removes the member. Until then, make removing the GitHub member and revoking their credential authorizations part of offboarding (DELETE /orgs/{org}/credential-authorizations/{credential_id}). Sources: https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-saml-single-sign-on-for-your-organization/about-scim-for-organizations, https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/provisioning-user-accounts-with-scim/deprovisioning-and-reinstating-users, https://docs.github.com/en/enterprise-cloud@latest/rest/orgs/orgs?apiVersion=2022-11-28#list-saml-sso-authorizations-for-an-organization ### The repo scope can change organization settings [repo-scope-changes-org-settings] Applies to: Every setup. Involves: scope:repo, scope:admin:org, role:org-owner, permission:organization_administration:write, permission:organization_secrets:write, permission:organization_self_hosted_runners:write, key:organization-base-permission Expected: A classic token needs admin:org to change organization settings; repo is about repositories. Actual: GitHub's REST docs accept admin:org or repo for PATCH /orgs/{org}, and list repo for writing organization Actions secrets and creating runner registration tokens. A classic token with only repo, created by an organization owner, can change the organization's settings, including the base repository permission for every member, without ever asking for admin:org. Check: Find organization owners who use classic tokens. In an organization with SAML SSO, the credential authorizations list shows each token's scopes. Command: gh api /orgs/ORG/credential-authorizations --paginate \ --jq '.[] | select(.scopes | index("repo")) | [.login, .credential_authorized_at] | @tsv' Fix: Have owners use fine-grained tokens or a separate account for automation, and restrict classic tokens in the organization's personal access token policy. Sources: https://docs.github.com/en/rest/orgs/orgs?apiVersion=2022-11-28#update-an-organization, https://docs.github.com/en/rest/actions/secrets?apiVersion=2022-11-28, https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28 ### Write access reads every repository secret [write-reads-all-secrets] Applies to: Every setup. Involves: role:repo-write, permission:contents:write, permission:workflows:write, key:environment-protection-rules, key:environment-reviewer Expected: Secrets are hidden. People who can push code cannot see them. Actual: GitHub states that any user with write access to a repository has read access to all secrets configured in it: they can push a workflow on a branch that prints or sends them. Environment secrets are only given to jobs that use the environment, and an environment with required reviewers holds the job until someone approves it. Check: List the repository's secrets and who has write access. Every person and token with write can read each of those secrets. Command: gh api /repos/ORG/REPO/actions/secrets --jq '.secrets[].name' gh api /repos/ORG/REPO/collaborators --paginate \ --jq '.[] | select(.permissions.push) | .login' Fix: Move deployment secrets into environments with required reviewers, or replace them with OIDC so there is no stored secret to read. Sources: https://docs.github.com/en/actions/reference/security/secure-use, https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments ### Workflow files can be pushed without the workflow scope [workflow-file-without-workflow-scope] Applies to: Every setup. Involves: scope:workflow, scope:repo, permission:workflows:write Expected: A classic token without the workflow scope cannot add or change workflow files. Actual: GitHub's scope docs say a workflow file can be committed without the workflow scope when the same file, with the same path and contents, already exists on another branch of the repository. A workflow that sits on any branch can be copied to another branch with a token that only has repo. Check: Look for workflow files on branches other than the default branch, especially ones that use secrets or pull_request_target. Fix: Protect .github/workflows with a push ruleset or code owner review, and delete stale branches that carry workflow files. Sources: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps ### Older organizations still give GITHUB_TOKEN write access [old-github-token-defaults] Applies to: Every setup. Involves: permission:contents:write, key:organization-actions-policy, page:/tier/tokens#token-types Expected: GITHUB_TOKEN is read-only unless a workflow asks for more. Actual: Read-only became the default on 2 February 2023, but only for new enterprises, organizations and repositories. Anything created earlier kept read and write for every workflow without a permissions block. Check: Read the organization's and each repository's default workflow permissions. Command: gh api /orgs/ORG/actions/permissions/workflow gh api /repos/ORG/REPO/actions/permissions/workflow Fix: Set the default to read at the enterprise or organization level, and give each workflow a permissions block with what it needs. Sources: https://github.blog/changelog/2023-02-02-github-actions-updating-the-default-github_token-permissions-to-read-only/ ### GITHUB_TOKEN may approve pull requests in older repositories [github-token-approves-prs] Applies to: Every setup. Involves: permission:pull_requests:write, key:organization-actions-policy Expected: A workflow cannot approve its own pull request, so required reviews always mean a person looked. Actual: The setting that lets Actions approve pull requests arrived in January 2022 turned on, so existing workflows kept working; it was later renamed "Allow GitHub Actions to create and approve pull requests". Only new personal repositories are documented as having it off. Where it is on, a workflow with pull-requests: write can approve a pull request and count towards required reviews. Check: Check can_approve_pull_request_reviews for the organization and its repositories. Command: gh api /orgs/ORG/actions/permissions/workflow --jq .can_approve_pull_request_reviews Fix: Turn the setting off at the enterprise or organization level unless a workflow needs it. Sources: https://github.blog/changelog/2022-01-14-github-actions-prevent-github-actions-from-approving-pull-requests, https://github.blog/changelog/2022-05-03-github-actions-prevent-github-actions-from-creating-and-approving-pull-requests/, https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository ### Branch protection does not apply to admins by default [admins-bypass-branch-protection] Applies to: Every setup. Involves: role:repo-admin, role:org-owner, permission:administration:write, key:ruleset-bypass Expected: A protected branch requires a review from everyone. Actual: By default a branch protection rule does not apply to people with admin on the repository, or to custom roles with "bypass branch protections". Rulesets have an explicit bypass list instead. Check: Check whether each protected branch also enforces its rules for admins. Command: gh api /repos/ORG/REPO/branches/main/protection --jq .enforce_admins.enabled Fix: Turn on "Do not allow bypassing the above settings", or move to rulesets with a short, named bypass list. Sources: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches ### An exempt ruleset bypass leaves no audit entry [ruleset-exempt-no-audit] Applies to: Every setup. Involves: key:ruleset-bypass Expected: Every time someone bypasses a ruleset, it is logged. Actual: A bypass actor with bypass_mode exempt is not checked against the rules at all, and GitHub creates no bypass audit entry for it. Check: Look for exempt bypass actors in organization and repository rulesets. Command: for id in $(gh api /orgs/ORG/rulesets --jq '.[].id'); do gh api /orgs/ORG/rulesets/$id --jq '.name as $n | .bypass_actors[] | select(.bypass_mode == "exempt") | [$n, .actor_type, .actor_id] | @tsv' done Fix: Use the always or pull_request bypass modes, which are logged, unless the actor is an automation you trust not to need review. Sources: https://docs.github.com/en/rest/repos/rules?apiVersion=2022-11-28 ### Deploy keys keep working after the person leaves [deploy-key-outlives-user] Applies to: Every setup. Involves: key:deploy-key, permission:administration:write Expected: Removing someone from the organization removes the access they set up. Actual: A deploy key belongs to the repository, not to the person who added it. It stays active when that person is removed, and it never expires. Check: List deploy keys with write access and ask who holds each private key today. Command: gh api /repos/ORG/REPO/keys --jq '.[] | select(.read_only == false) | [.id, .title, .created_at] | @tsv' Fix: Rotate deploy keys when their owner leaves, prefer read-only keys, and use a GitHub App where write access is needed. Sources: https://docs.github.com/en/authentication/connecting-to-github-with-ssh/managing-deploy-keys ### A GitHub App stays installed when its installer leaves [app-stays-after-installer-leaves] Applies to: Every setup. Involves: key:app-private-key, role:org-app-manager Expected: Integrations someone added go away with them. Actual: A GitHub App stays installed if the person who set it up leaves the organization. Its private keys do not expire either; they must be revoked by hand, and anyone holding one can mint installation tokens. Check: List the organization's installations and confirm an owner for each, and the age of each App's private keys. Command: gh api /orgs/ORG/installations --paginate \ --jq '.installations[] | [.app_slug, .created_at, .repository_selection] | @tsv' Fix: Give every App a named owner, remove unused installations, and rotate private keys on a schedule or keep them in a KMS. Sources: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/differences-between-github-apps-and-oauth-apps, https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps ### Classic tokens only die after a year unused [classic-tokens-live-until-unused] Applies to: Every setup. Involves: scope:repo, page:/tier/tokens#token-types Expected: Tokens expire. Actual: Classic personal access tokens have no expiration requirement and OAuth app tokens are long-lived by default. GitHub revokes them automatically only when they have not been used for a year, so a token used now and then lives on. Check: In an organization with SAML SSO, the credential authorizations list shows each token's expiry and last use. Command: gh api /orgs/ORG/credential-authorizations --paginate \ --jq '.[] | select(.credential_type == "personal access token") | [.login, .authorized_credential_expires_at // "never", .credential_accessed_at] | @tsv' Fix: Restrict classic tokens in the organization's personal access token policy and use fine-grained tokens or GitHub Apps, which do expire. Sources: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/token-expiration-and-revocation, https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization ### A fine-grained token reads every public repository [fine-grained-pat-reads-public-repos] Applies to: Every setup. Involves: permission:contents:read, page:/tier/tokens#token-types Expected: A fine-grained token can only reach the repositories selected for it. Actual: Every fine-grained token also has read-only access to all public repositories on GitHub, whatever repositories are selected. Check: Nothing to check per token; treat public repositories as readable by any token. Fix: Keep anything sensitive out of public repositories. The repository selection only limits private and internal ones. Sources: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens ### Deleting a fork does not delete its commits [fork-network-keeps-data] Applies to: Every setup. Involves: permission:contents:read, role:repo-read Expected: Deleting a fork removes what was pushed to it. Actual: Git data from any repository in a fork network can be read from any other repository in the same network, including the upstream, even after a fork is deleted. A commit pushed to a fork stays reachable by its hash. Check: Assume anything ever pushed to any fork of a public repository is still readable. Fix: Rotate any secret that was pushed to a fork. Deleting the fork or the branch does not remove it. Sources: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-permissions-and-visibility-of-forks, https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/what-happens-to-forks-when-a-repository-is-deleted-or-changes-visibility ### A push ruleset bypass reaches every fork [push-ruleset-bypass-fork-network] Applies to: Team, Enterprise Cloud, EMU, Server. Involves: key:ruleset-bypass, permission:administration:write Expected: A bypass on a repository's push ruleset only applies to that repository, and each fork's admins decide their own. Actual: Push rulesets cover a private or internal repository and its entire fork network, and so does their bypass list: only those who can bypass the ruleset in the root repository can bypass it in any fork. A bypass actor added for the root can push blocked files to every fork, and a fork's own admins cannot bypass it. Check: List the bypass actors on push rulesets for private and internal repositories that have forks. Command: gh api /repos/ORG/REPO/rulesets --jq '.[] | select(.target == "push") | .id' | xargs -I{} gh api /repos/ORG/REPO/rulesets/{} --jq '[.name, (.bypass_actors[] | "\(.actor_type) \(.actor_id) \(.bypass_mode)")] | @tsv' Fix: Keep push ruleset bypass lists empty or to a break-glass team, since each entry applies across the fork network. Sources: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets ### Pull requests a workflow opens need a person to start their checks [bot-pull-requests-wait-for-approval] Applies to: Every setup. Involves: permission:pull_requests:write, permission:contents:write, key:actions-create-approve-prs Expected: A pull request opened by a workflow with GITHUB_TOKEN either runs its checks like any other, or runs nothing at all. Actual: Events made with GITHUB_TOKEN do not start workflow runs, with exceptions. A pull request it opens or updates does start runs, but they wait for approval, and someone with write access has to approve them from the merge box. GitHub's suggested way around the wait is a GitHub App or personal access token, whose pull requests run without approval. Check: Find workflows that open or update pull requests, and see which token they use. Fix: Read the pull request before approving its runs. Where a bot needs runs without approval, use a GitHub App with the fewest permissions, not a personal access token, and keep its pull requests behind required reviews. Sources: https://docs.github.com/en/actions/concepts/security/github_token --- ## Permission model 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. ### Vocabularies - Classic OAuth scopes (oauth-scope), used by 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 (resource-permission), used by 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 (role), used by 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 (key), used by 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 (setting), used by 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. ### Ways to grant access (25) - Enterprise role: Owner, Billing manager, App manager, Security manager, User [Enterprise, role] granted by Enterprise owner, or the IdP for EMU; reaches Every organization; lifetime Until removed. On ghtier: role:enterprise-owner, role:enterprise-app-manager, role:enterprise-security-manager, role:enterprise-billing-manager, role:enterprise-user - Guest collaborator (EMU only) [Enterprise, role] granted by IdP provisioning, then an org or repo admin grants access; reaches Only the repositories and orgs they are added to; lifetime Until deprovisioned. On ghtier: role:enterprise-guest-collaborator - Enterprise custom role and enterprise teams [Enterprise, role] granted by Enterprise owner; reaches The enterprise settings the role covers; lifetime Until removed. On ghtier: page:/tier/roles - Organization role: Owner, Member, Moderator, Billing manager [Organization, role] granted by Organization owner; reaches One organization; lifetime Until removed. On ghtier: role:org-owner, role:org-member, role:org-moderator, role:org-billing-manager - Pre-defined organization role: Security manager, App manager, CI/CD admin, All-repository read to admin [Organization, role] granted by Organization owner; reaches One organization, every repository for the all-repository roles; lifetime Until removed. On ghtier: role:org-cicd-admin, role:org-security-manager, role:org-app-manager, role:org-all-repo-admin, role:org-all-repo-write - Custom organization role: org permissions plus an optional base repository role [Organization, role] granted by Organization owner, or defined once for the enterprise; reaches One organization; lifetime Until removed. On ghtier: page:/tier/roles - Organization base permission: none, read, write or admin [Organization, role] granted by Organization owner; reaches Every repository, for every member; lifetime Until changed. On ghtier: key:organization-base-permission - Team membership: member or maintainer, inherited through child teams [Organization, role] granted by Organization owner, team maintainer, or IdP group sync; reaches The repositories the team was given; lifetime Until removed. On ghtier: page:/tier/roles, role:team-maintainer - Direct repository role: Read, Triage, Write, Maintain, Admin [Organization, role] granted by Repository admin; reaches One repository; lifetime Until removed. On ghtier: role:repo-read, role:repo-triage, role:repo-write, role:repo-maintain, role:repo-admin - Custom repository role: an inherited role plus additional permissions [Organization, role] granted by Organization owner defines it, repository admin assigns it; reaches One repository per assignment; lifetime Until removed. On ghtier: page:/tier/roles - Outside collaborator [Organization, role] granted by Repository admin, if org policy allows it; reaches The named repositories only, with any repository role up to Admin; lifetime Until removed by hand. SSO is not required for outside collaborators, so IdP offboarding and SCIM do not remove them. On ghtier: key:outside-collaborator, role:repo-read, role:repo-write, role:repo-admin - Trigger-borrowed access: a fork pull request or issue comment that starts a workflow [Workload, resource-permission] granted by Whoever wrote the workflow and chose its trigger (pull_request_target, issue_comment, workflow_run); reaches What that workflow's GITHUB_TOKEN, secrets and runner can reach, used by someone who holds no role at all; lifetime One workflow run. On ghtier: page:/attack-paths, permission:contents:write, permission:secrets:read - Special grants: ruleset bypass actor, environment required reviewer, code owner [Organization, role] granted by Repository admin or org owner; anyone who can edit CODEOWNERS; reaches The rules, environments or paths they cover; lifetime Until removed. On ghtier: key:ruleset-bypass, key:environment-reviewer, key:codeowners - Classic personal access token [User, oauth-scope] granted by The user; reaches Every organization the user belongs to; lifetime Expiry optional. On ghtier: scope:repo, scope:workflow, scope:admin:org - Fine-grained personal access token [User, resource-permission] granted by The user, plus org approval when required; reaches One resource owner, selected repositories; lifetime Expiry required. On ghtier: permission:contents:write, permission:administration:write, permission:secrets:write - OAuth App authorization [Application, oauth-scope] granted by User consent, plus org approval when OAuth access is restricted; reaches Every organization the user lets it reach; lifetime Usually no expiry. On ghtier: scope:repo, scope:workflow, scope:admin:org - GitHub App installation [Application, resource-permission] granted by Org owner, App manager, or repo admin for selected repos; reaches All repositories or a selection; lifetime 1-hour tokens; the private key never expires. On ghtier: page:/tier/tokens#token-types, key:app-private-key, permission:workflows:write - GitHub App user access token [Application, resource-permission] granted by A user signing in to the App; reaches The App's permissions and the user's own access, whichever is smaller; lifetime 8 hours, refreshable for 6 months. On ghtier: page:/tier/tokens#token-types - GITHUB_TOKEN [Workload, resource-permission] granted by The workflow's permissions block, or the org or repo default; reaches One repository, one job; lifetime The job's duration. On ghtier: page:/tier/tokens#token-types, permission:contents:write - OIDC token from id-token: write [Workload, resource-permission] granted by The workflow file, and the cloud trust policy that accepts it; reaches Whatever the cloud role allows; lifetime Minutes. On ghtier: permission:id_token:write - Self-hosted runner and runner group access [Workload, setting] granted by Repo admin, org owner or CI/CD admin; reaches The repositories and workflows allowed to use the runner; lifetime Until removed. On ghtier: permission:organization_self_hosted_runners:write - Copilot cloud agent, MCP servers and AI steps in workflows [Workload, resource-permission] granted by A user assigning the task, within org and enterprise policy; reaches Whatever token and tools it is given; lifetime One task or one job. On ghtier: key:ai-agent - SSH key on a user account [User, key] granted by The user; reaches All the user's Git access; lifetime No expiry. On ghtier: key:ssh-key - SSH certificate authority [Organization, key] granted by Organization or enterprise owner; reaches Git access as any member the certificate names; lifetime Certificate validity. On ghtier: key:ssh-ca-key - Deploy key [Organization, key] granted by Repository admin; reaches One repository, read-only or read-write; lifetime No expiry. On ghtier: key:deploy-key ### Capability crosswalk (16) Columns: classic PAT/OAuth scope | fine-grained PAT/App permission | GITHUB_TOKEN | role. A dash means that vocabulary cannot grant it. - 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: workflow | Workflows: write | – | Write. Note: GITHUB_TOKEN has no workflows permission, so a workflow cannot rewrite workflow files with its own token. - Start or re-run workflows: repo | Actions: write | actions: write | Write - Open, approve and merge pull requests: repo | Pull requests: write | pull-requests: write | Write. Note: GITHUB_TOKEN can only approve when the org or repo allows Actions to create and approve pull requests. - 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: write:packages | – (GitHub Packages only accepts classic PATs) | packages: write | Write. Note: One of the few places where a fine-grained PAT is not an option, so teams fall back to long-lived classic tokens. - 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: – | – | – | Org owner, App manager; repo admins for Apps that ask for no org or Administration permission, unless the org turns that off. Note: Installing an App is done in the browser, not with a token scope, so only roles control it. - 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 --- ## Hardening checklist ### Authentication & identity - [Quick win · under an hour] Enforce MFA for every member (prefer hardware/FIDO2 keys over SMS or app codes) Why: MFA is the single biggest blocker to account takeover from a leaked or guessed password. Prerequisites: Organization owner Steps: 1. Go to `github.com/orgs/ORG/people` and filter by 2FA to see who has it off. Ask them to enable it first, since members without 2FA are removed when you turn this on. 2. Go to `github.com/organizations/ORG/settings/security`. 3. Under "Two-factor authentication", select "Require two-factor authentication for everyone in the organization". 4. Also select the option to require secure methods only, which blocks SMS. Save. 5. Ask members to register a security key or passkey as their main method. Docs: Requiring 2FA in your organization (https://docs.github.com/en/organizations/keeping-your-organization-secure/managing-two-factor-authentication-for-your-organization/requiring-two-factor-authentication-in-your-organization) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org} jq: .[0].two_factor_requirement_enabled Note: The API does not say whether SMS is blocked (secure methods only), so check that part by hand. Related scenarios: user-circleci-session-theft Risk: CICD-SEC-2: Inadequate Identity and Access Management Plan: GitHub Free Detect (audit log): org.disable_two_factor_requirement, business.disable_two_factor_requirement, business.remove_disallowed_two_factor_method, business.clear_disallowed_two_factor_methods - [Quick win · under an hour] Disable password-based Git authentication (Enterprise Server only) Why: Password-based Git auth can't be scoped, expired, or tied to one machine the way a key or token can. Prerequisites: Site administrator Steps: 1. On GitHub Enterprise Server, check the Authentication section of the Management Console and turn off password authentication for Git operations if it is still on. 2. On github.com there is nothing to do: password authentication for Git was removed in 2021, so Git over HTTPS already needs a token. Mark this item done. 3. Either way, check that people and scripts use SSH keys or fine-grained PATs, not classic PATs pasted in place of a password. Docs: About authentication to GitHub (https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github) Risk: CICD-SEC-2: Inadequate Identity and Access Management; CICD-SEC-7: Insecure System Configuration Plan: GitHub Enterprise Server. Only a setting on Enterprise Server; github.com has no password Git access. - [Quick win · under an hour] Set an organization-wide maximum PAT lifetime (90 days is a reasonable default) Why: A PAT that never expires is a permanent, unmonitored key sitting in whatever it was pasted into. Prerequisites: Organization owner; or Enterprise owner to set it for every org at once Steps: 1. Go to `github.com/organizations/ORG/settings/personal-access-tokens`. 2. Open the "Fine-grained tokens" tab. Under "Set maximum lifetimes for personal access tokens", set 90 days. Save. 3. Open the "Tokens (classic)" tab and set the same maximum lifetime. Save. 4. Tokens with a longer lifetime are blocked from org resources, so warn automation owners before you save. Docs: Setting a PAT policy for your organization (https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization) Related scenarios: user-mercedes-benz-token-leak Risk: CICD-SEC-6: Insufficient Credential Hygiene Plan: GitHub Free Detect (audit log): personal_access_token.expiration_limit_unset, personal_access_token.expiration_limit_set Last checked against GitHub's docs: 2026-10-11 - [Quick win · under an hour] Require MFA on bot/service accounts, not just human ones Why: Shared automation accounts are rarely rotated and make an attractive, under-monitored target. Prerequisites: Organization owner (to see 2FA status); access to the bot account's login Steps: 1. Go to `github.com/orgs/ORG/people` and list every bot or shared account. 2. Sign in as each one and go to `github.com/settings/security`. Enable 2FA with an authenticator app. 3. Store the TOTP secret and recovery codes in your team's password vault, not with one person. 4. Where the account only exists for automation, plan to replace it with a GitHub App instead. Docs: Configuring two-factor authentication (https://docs.github.com/en/authentication/securing-your-account-with-two-factor-authentication-2fa/configuring-two-factor-authentication) Related scenarios: org-ghostaction Risk: CICD-SEC-2: Inadequate Identity and Access Management; CICD-SEC-6: Insufficient Credential Hygiene Plan: GitHub Free Detect (audit log): org.disable_two_factor_requirement, business.disable_two_factor_requirement - [Moderate · plan for this quarter] Audit and revoke stale PATs, unused SSH keys, and inactive accounts Why: Stale credentials are exactly what attackers enumerate first. They're free to check, and they often still work. Prerequisites: Organization owner; Enterprise owner for the dormant users report Steps: 1. Go to `github.com/organizations/ORG/settings/personal-access-tokens/active`. Revoke fine-grained tokens that are unused or expired. 2. If you use SAML SSO, open each member at `github.com/orgs/ORG/people` and review their authorized credentials. Revoke old PATs and SSH keys there. 3. On the enterprise, go to Compliance → Reports → "Dormant Users" and download the report. 4. Remove dormant members and outside collaborators from the org. 5. In each repo, check `github.com/ORG/REPO/settings/keys` and delete deploy keys nobody owns. Docs: Reviewing and revoking PATs in your organization (https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/reviewing-and-revoking-personal-access-tokens-in-your-organization) Related scenarios: user-mercedes-benz-token-leak Risk: CICD-SEC-2: Inadequate Identity and Access Management; CICD-SEC-6: Insufficient Credential Hygiene Plan: GitHub Free. The dormant users report needs Enterprise. Detect (audit log): public_key.create, personal_access_token.access_granted, org_credential_authorization.grant - [Moderate · plan for this quarter] Disable classic PATs organization-wide, forcing migration to fine-grained PATs Why: Classic PATs are all-or-nothing. One leaked token can mean full read-write to everything that user can touch. Prerequisites: Organization owner Steps: 1. Tell token owners the date, and ask them to move to fine-grained PATs or a GitHub App. 2. Go to `github.com/organizations/ORG/settings/personal-access-tokens`. 3. Open the "Tokens (classic)" tab and select "Restrict personal access tokens (classic) from accessing your organizations". Save. 4. On the "Fine-grained tokens" tab, turn on "Require administrator approval" so new tokens get reviewed. Docs: Setting a PAT policy for your organization (https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization) Related scenarios: user-dependabot-impersonation Risk: CICD-SEC-6: Insufficient Credential Hygiene Plan: GitHub Free Detect (audit log): personal_access_token.access_restriction_disabled, auto_approve_personal_access_token_requests.enable Last checked against GitHub's docs: 2026-10-11 - [Larger initiative · weeks] Migrate to SAML SSO / Enterprise Managed Users for centralized identity control Why: Centralizing identity means one enforced MFA/session policy instead of dozens of personal-account configurations, though the SSO layer itself then becomes worth protecting carefully. Prerequisites: Organization owner or Enterprise owner; admin on your identity provider Steps: 1. Create a GitHub app/connector in your IdP (Entra ID, Okta, etc.) and assign a test group. 2. Go to `github.com/organizations/ORG/settings/security` (or `github.com/enterprises/ENTERPRISE/settings/security` for every org). 3. Under "SAML single sign-on", enable it and paste the IdP sign-on URL, issuer and certificate. 4. Click "Test SAML configuration". When it passes, select "Require SAML SSO authentication". Save. 5. For Enterprise Managed Users, contact GitHub sales. EMU is a new enterprise, not a setting you can flip. Docs: Enabling and testing SAML SSO for your organization (https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-saml-single-sign-on-for-your-organization/enabling-and-testing-saml-single-sign-on-for-your-organization) Related scenarios: ent-cve-2024-4985 Risk: CICD-SEC-2: Inadequate Identity and Access Management Plan: GitHub Enterprise Detect (audit log): org.disable_saml, business.disable_saml, business.disable_oidc, org.update_saml_provider_settings - [Larger initiative · weeks] Manage team membership from identity provider groups instead of by hand Why: When teams follow IdP groups, a leaver loses repo access the moment HR offboards them, not whenever someone remembers. Prerequisites: SAML SSO or Enterprise Managed Users already set up; Organization owner; admin on your identity provider Steps: 1. Decide which IdP groups map to which GitHub teams, and grant repo access to those teams only. 2. With Enterprise Managed Users, open each team at `github.com/orgs/ORG/teams` → Settings and connect it to an IdP group. Membership then comes from SCIM. 3. With SAML SSO on a normal enterprise, turn on team synchronization at `github.com/organizations/ORG/settings/security`, then link each team to a group under the team's settings. 4. Nested IdP groups are not expanded. Link the leaf groups, or flatten membership in the IdP or with Terraform. 5. Remove the members you added by hand once each team is linked. Docs: Managing team memberships with identity provider groups (https://docs.github.com/en/enterprise-cloud@latest/admin/managing-iam/provisioning-user-accounts-with-scim/managing-team-memberships-with-identity-provider-groups) Risk: CICD-SEC-2: Inadequate Identity and Access Management Plan: GitHub Enterprise. Enterprise Cloud with SAML SSO, or Enterprise Managed Users. Detect (audit log): external_group.unlink, team_sync_tenant.disabled, team.add_member ### Organization governance - [Quick win · under an hour] Set the organization's base repository permission to None or Read, not Write or Admin Why: A permissive org-wide default silently grants every member admin-like access to every repo at once. Prerequisites: Organization owner Steps: 1. Find out which teams need write access and grant it to those teams on the repos they work in. 2. Go to `github.com/organizations/ORG/settings/member_privileges`. 3. Under "Base permissions", select Read. Confirm the change. 4. Choose No permission instead if access should come only from team grants. Members then see only public and internal repos and the repos their teams are given. Docs: Setting base permissions for an organization (https://docs.github.com/en/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/setting-base-permissions-for-an-organization) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org} jq: .[0].default_repository_permission | if . == null then null else (. == "read" or . == "none") end Related scenarios: org-toptal Risk: CICD-SEC-2: Inadequate Identity and Access Management Plan: GitHub Free Detect (audit log): org.update_default_repository_permission, business.update_default_repository_permission - [Quick win · under an hour] Limit the number of organization owners to the minimum needed Why: Every additional owner is another full-organization compromise waiting to happen. Prerequisites: Organization owner Steps: 1. Go to `github.com/orgs/ORG/people?query=role%3Aowner`. 2. For each owner who doesn't need full control, select them and use "Change role" to set Member. 3. Keep two or three owners so you never lose ownership of the org. 4. Give people who only need security access the Security manager role instead. Docs: Maintaining ownership continuity for your organization (https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/maintaining-ownership-continuity-for-your-organization) Automated check (org scope, needs Organization member (classic token: read:org)): GET /orgs/{org}/members?role=admin&per_page=100 jq: (.[0] | length) <= 3 Note: Passes at 3 owners or fewer. Related scenarios: org-toptal Risk: CICD-SEC-2: Inadequate Identity and Access Management Plan: GitHub Free Detect (audit log): org.update_member, business.add_admin, business.invite_admin - [Quick win · under an hour] Restrict public repo creation, visibility changes, repo deletion, private forks and outside collaborator invites to owners Why: Any member who can make a repo public or invite an outsider can leak private code without touching a single permission. Prerequisites: Organization owner; or Enterprise owner to enforce it for every org Steps: 1. Go to `github.com/organizations/ORG/settings/member_privileges`. 2. Under "Repository creation", untick Public so members can only create private or internal repos. 3. Under "Repository forking", untick "Allow forking of private and internal repositories" unless teams need it. 4. Under "Repository visibility change" and "Repository deletion and transfer", untick the options that let members do it. Only owners can then change visibility or delete a repo. 5. Under "Repository invitations", untick "Allow members to invite outside collaborators". 6. On an enterprise, set the same policies at `github.com/enterprises/ENTERPRISE/settings/member_privileges` so no org can loosen them. Docs: Restricting repository visibility changes in your organization (https://docs.github.com/en/organizations/managing-organization-settings/restricting-repository-visibility-changes-in-your-organization) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org} jq: .[0] | if .members_can_create_public_repositories == null then null else (.members_can_create_public_repositories == false and .members_can_fork_private_repositories != true) end Note: The API only shows public repo creation and private forking. Check visibility changes, deletion and invites by hand. Related scenarios: org-toptal, org-shai-hulud Risk: CICD-SEC-2: Inadequate Identity and Access Management; CICD-SEC-7: Insecure System Configuration Plan: GitHub Free. Limiting outside collaborator invites to owners needs Enterprise Cloud. Detect (audit log): org.update_member_repository_creation_permission, org.update_member_repository_invitation_permission, repository_visibility_change.enable, private_repository_forking.enable - [Moderate · plan for this quarter] Use custom organization and repository roles instead of handing out Owner or Admin Why: Without a narrower role, the security lead or release manager ends up with Owner or Admin because nothing smaller fits. Prerequisites: Organization owner Steps: 1. List what the people with Owner or repo Admin actually do, for example review security alerts, manage releases or edit webhooks. 2. Go to `github.com/organizations/ORG/settings/org_roles` and create org roles for those jobs, or assign a predefined one such as Security manager or CI/CD admin. 3. Go to `github.com/organizations/ORG/settings/roles` and create repository roles based on Write or Maintain, adding only the extra permissions needed. 4. Assign the roles to teams, then remove the Owner and Admin grants they replace. Docs: About custom repository roles (https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-user-access-to-your-organizations-repositories/managing-repository-roles/about-custom-repository-roles) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/organization-roles GET /orgs/{org}/custom-repository-roles jq: null Note: Lists the roles; whether they fit your teams is your call. Related scenarios: org-toptal Risk: CICD-SEC-2: Inadequate Identity and Access Management Plan: GitHub Enterprise. Custom repository roles need Enterprise Cloud. Detect (audit log): org.update_member, organization_role.assign, organization_role.update, role.update - [Moderate · plan for this quarter] Run a quarterly access review of owners, repo admins, team membership and outside collaborators Why: Access piles up with every project and reorg, and nothing in GitHub removes it for you. Prerequisites: Organization owner; Enterprise owner for enterprise-wide exports Steps: 1. Export members with their roles from `github.com/orgs/ORG/people` (Export → CSV or JSON). 2. List outside collaborators at `github.com/orgs/ORG/outside-collaborators` and check each still has a reason and an internal sponsor. 3. For your most sensitive repos, review `github.com/ORG/REPO/settings/access` for direct Admin grants that bypass teams. 4. Send each team's member list to its maintainer to confirm, and remove what they don't sign off on. 5. Keep the exports and sign-offs as audit evidence. Docs: Exporting member information for your organization (https://docs.github.com/en/enterprise-cloud@latest/organizations/managing-membership-in-your-organization/exporting-member-information-for-your-organization) Related scenarios: user-mercedes-benz-token-leak Risk: CICD-SEC-2: Inadequate Identity and Access Management Plan: GitHub Free Detect (audit log): org.update_member, org.add_outside_collaborator, repo.add_member, team.add_member - [Moderate · plan for this quarter] Classify repos with custom properties (data classification, criticality, compliance scope) Why: Once repos carry a classification, rulesets and security configurations can target the critical ones automatically instead of by a hand-kept list. Prerequisites: Organization owner; or a custom role with permission to edit custom properties Steps: 1. Go to `github.com/organizations/ORG/settings/custom-properties`. 2. Add properties such as `data-classification` (public, internal, confidential, restricted) and `criticality` (critical, high, medium, low). Make them required with a default. 3. Set values per repo, or in bulk from the repository list on the same page. 4. In your rulesets at `github.com/organizations/ORG/settings/rules`, target repositories by property, for example stricter review on `criticality: critical`. 5. Apply security configurations to repos filtered by the same properties. Docs: Managing custom properties for repositories (https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization) Automated check (org scope, needs Organization member (classic token: read:org)): GET /orgs/{org}/properties/schema jq: (.[0] // []) | length > 0 Note: Passes when at least one property is defined. Whether repos have values set is not checked. Risk: CICD-SEC-7: Insecure System Configuration Plan: GitHub Free Detect (audit log): custom_property_definition.destroy, custom_property_definition.update, custom_property_value.update, custom_property_value.destroy - [Moderate · plan for this quarter] Review every currently installed GitHub App / OAuth App and its granted scope Why: An installed app with broad scope is a standing credential that nobody is actively watching. Prerequisites: Organization owner Steps: 1. Go to `github.com/organizations/ORG/settings/installations`. 2. Click Configure on each app. Check its permissions and which repositories it can access. 3. Narrow repository access to "Only select repositories", or uninstall apps nobody uses. 4. Go to `github.com/organizations/ORG/settings/oauth_application_policy` and remove approval for OAuth apps you don't recognize. Docs: Reviewing GitHub Apps installed in your organization (https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/reviewing-github-apps-installed-in-your-organization) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/installations?per_page=100 jq: null Note: Lists the apps; whether each one should be there is your call. Related scenarios: app-heroku-travisci-oauth-theft Risk: CICD-SEC-8: Ungoverned Usage of 3rd Party Services Plan: GitHub Free Detect (audit log): integration_installation.create, integration_installation.version_updated, integration_installation.repositories_added, org.oauth_app_access_approved - [Moderate · plan for this quarter] Keep OAuth app access restrictions on and require security/IT approval for new apps Why: Unreviewed app installs are how a single click grants a third party lasting access to org data. Prerequisites: Organization owner Steps: 1. Go to `github.com/organizations/ORG/settings/oauth_application_policy`. 2. Click "Setup application access restrictions", then "Restrict third-party application access". 3. Only owners can install GitHub Apps. Members can only send a request, so keep the owner list short. 4. Route every app request (they arrive by email to owners) through a security ticket before approving it. Docs: Enabling OAuth app access restrictions (https://docs.github.com/en/organizations/managing-oauth-access-to-your-organizations-data/enabling-oauth-app-access-restrictions-for-your-organization) Related scenarios: user-oauth-consent-phishing Risk: CICD-SEC-8: Ungoverned Usage of 3rd Party Services Plan: GitHub Free Detect (audit log): org.disable_oauth_app_restrictions, integration_installation.create, org.integration_manager_added Last checked against GitHub's docs: 2026-10-11 - [Moderate · plan for this quarter] Enable audit logging and export it to a SIEM for retention and detection Why: Without exported logs, enumeration and quiet persistence leave no trace to investigate later. Prerequisites: Enterprise owner; a SIEM endpoint (Splunk, Datadog, S3, Azure, etc.) Steps: 1. Go to `github.com/enterprises/ENTERPRISE` → Settings → Audit log → Log streaming. 2. Click "Configure stream", pick your SIEM, and enter its endpoint and credentials. 3. Click "Check endpoint", then Save. 4. On the same page, turn on streaming of API request events so token use is logged too. 5. Build alerts for owner changes, new app installs, PAT creation, secret or ruleset changes, repo deletion and repos made public. Docs: Streaming the audit log for your enterprise (https://docs.github.com/en/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise) Related scenarios: org-salesloft-drift Risk: CICD-SEC-10: Insufficient Logging and Visibility Plan: GitHub Enterprise Detect (audit log): audit_log_streaming.destroy, audit_log_streaming.update - [Moderate · plan for this quarter] Enforce an IP allow list for Git and web UI access from trusted ranges Why: A network boundary stops a stolen credential from being usable from outside your own infrastructure. Prerequisites: Organization owner Steps: 1. Collect every range that needs access: office, VPN, self-hosted runners, and external services that call GitHub. 2. Go to `github.com/organizations/ORG/settings/security`. 3. Under "IP allow list", add each range with a description. 4. Select "Enable IP allow list". Save. 5. Also enable the allow list for installed GitHub Apps if your apps publish their ranges. Docs: Managing allowed IP addresses for your organization (https://docs.github.com/en/enterprise-cloud@latest/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-allowed-ip-addresses-for-your-organization) Risk: CICD-SEC-7: Insecure System Configuration Plan: GitHub Enterprise Detect (audit log): ip_allow_list.disable, ip_allow_list_entry.create, ip_allow_list_entry.update, ip_allow_list.disable_for_installed_apps - [Moderate · plan for this quarter] Maintain a documented list of allowed apps, their use case, and their owner Why: An undocumented app estate can't be reviewed, audited, or confidently revoked later. Prerequisites: Organization owner (or a token with admin:org read) Steps: 1. Export the installed apps from `github.com/organizations/ORG/settings/installations` or `gh api /orgs/ORG/installations`. 2. For each app, write down: what it's for, the business owner, its permissions, and which repos it can reach. 3. Keep the list in a repo so changes get reviewed. 4. Put a quarterly review on the calendar and remove apps that no longer have an owner. Docs: REST API: list app installations for an organization (https://docs.github.com/en/rest/orgs/orgs#list-app-installations-for-an-organization) Risk: CICD-SEC-8: Ungoverned Usage of 3rd Party Services Plan: GitHub Free Detect (audit log): integration_installation.create, org.oauth_app_access_approved - [Moderate · plan for this quarter] Decide which AI coding agents and MCP servers may work on the organization's repositories Why: An agent acts with the access it is given and reads untrusted text (issues, pull requests, web pages), so a prompt injection can turn that access against you. Prerequisites: Organization owner; or Enterprise owner, where the enterprise sets the policy Steps: 1. In the organization settings, open the Copilot policies and review the agent ones: the Copilot coding agent, third-party coding agents and MCP servers in Copilot. 2. Turn agents on only for the repositories that need them, not the whole organization. 3. Keep agent work behind pull requests with required human review, and do not give agents environment secrets for production. 4. Limit which MCP servers developers can use. GitHub recommends defining that in the enterprise's managed settings rather than with the registry preview. Docs: Managing policies for Copilot in your organization (https://docs.github.com/en/copilot/how-tos/administer-copilot/manage-for-organization/manage-policies) Related scenarios: app-agentic-mcp-tokens, org-nx-s1ngularity Risk: CICD-SEC-8: Ungoverned Usage of 3rd Party Services Plan: GitHub Free. Needs a Copilot plan for the Copilot agent policies. Detect (audit log): copilot.cfb_org_settings_changed, copilot.cfb_enterprise_settings_changed, mcp_registry.settings_update, mcp_registry.allowlist_add_server Last checked against GitHub's docs: 2026-10-11 - [Larger initiative · weeks] Back up repositories and metadata outside GitHub, and rehearse a restore Why: An attacker with owner or admin access can delete or wipe repositories. A deleted repository can usually be restored within 90 days, but not always, and force-pushed history and settings are not covered. Prerequisites: Organization owner; a storage location outside GitHub Steps: 1. Back up every repository on a schedule with Git (`git clone --mirror`) or the migrations API, to storage the GitHub credentials cannot delete. 2. Include what Git does not hold: issues, pull requests, releases, wikis, and the repository and organization settings you would need to rebuild. 3. Write down how to restore a deleted repository (organization settings, Deleted repositories, within 90 days) and when you need GitHub Support instead, for example for a repository in a fork network. 4. Rehearse restoring one repository from your own backup once a year. Docs: Backing up a repository (https://docs.github.com/en/repositories/archiving-a-github-repository/backing-up-a-repository) Related scenarios: org-toptal Risk: CICD-SEC-7: Insecure System Configuration Plan: GitHub Free Detect (audit log): repo.destroy, repo.transfer, org.delete Last checked against GitHub's docs: 2026-10-11 ### Repository security - [Quick win · under an hour] Branch protection on default/release branches: required reviews + required status checks Why: Without required review, one push is all it takes to land malicious code on the branch everyone trusts. Prerequisites: Organization owner for org-wide rulesets; or repo admin for one repo Steps: 1. Go to `github.com/organizations/ORG/settings/rules` (or `github.com/ORG/REPO/settings/rules` for one repo) and click "New ruleset" → "New branch ruleset". 2. Set enforcement to Active. Target all repositories (or a pilot set) and the default branch. 3. Turn on "Require a pull request before merging" with at least 1 approval and dismiss stale approvals. 4. Turn on "Require status checks to pass" and add your CI checks. Also require branches to be up to date before merging. 5. For repos that ship to production, raise the approval count to 2. 6. Save, then widen the repository target as teams are ready. Docs: Creating rulesets for repositories in your organization (https://docs.github.com/en/organizations/managing-organization-settings/creating-rulesets-for-repositories-in-your-organization) Automated check (repo scope, needs Organization owner, or admin on every repo (classic token: repo, read:org)): GET /repos/{owner}/{repo}/rules/branches/{default_branch} GET /repos/{owner}/{repo}/branches/{default_branch}/protection jq: (((.[0] // []) | any(.type == "pull_request")) or (.[1].required_pull_request_reviews != null)) and (((.[0] // []) | any(.type == "required_status_checks")) or (.[1].required_status_checks != null)) Note: Default branch only. Counts both rulesets and classic branch protection. Related scenarios: org-ultralytics, org-chaindrop Risk: CICD-SEC-1: Insufficient Flow Control Mechanisms Plan: GitHub Team. Free on public repositories. Detect (audit log): protected_branch.destroy, protected_branch.update_pull_request_reviews_enforcement_level, protected_branch.update_required_status_checks_enforcement_level, protected_branch.policy_override - [Quick win · under an hour] Secret scanning and push protection enabled Why: A secret committed once is compromised forever. Scanning catches it before or as it lands. Prerequisites: Organization owner or Security manager Steps: 1. Go to `github.com/organizations/ORG/settings/security_products`. 2. Apply the GitHub recommended configuration, or create one with Secret scanning and Push protection enabled. 3. Apply it to all repositories. 4. Set it as the default for new repositories. Docs: Applying a security configuration (https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/establish-complete-coverage/apply-custom-configuration) Automated check (repo scope, needs Organization owner, or admin on every repo (classic token: repo, read:org)): jq: $repo.security_and_analysis as $s | if $s == null then null else ($s.secret_scanning.status == "enabled" and $s.secret_scanning_push_protection.status == "enabled") end Note: Needs both secret scanning and push protection on. Related scenarios: org-ghostaction Risk: CICD-SEC-6: Insufficient Credential Hygiene Plan: GitHub Team with Secret Protection. Free on public repositories. Detect (audit log): repository_secret_scanning.disable, repository_secret_scanning_push_protection.disable, org.secret_scanning_push_protection_disable, secret_scanning_push_protection.bypass - [Quick win · under an hour] Dependency graph, Dependabot alerts and Dependabot security updates enabled Why: A known-vulnerable dependency is the cheapest way in, and Dependabot is free on every repo. Prerequisites: Organization owner or Security manager Steps: 1. Go to `github.com/organizations/ORG/settings/security_products` and edit the configuration you apply to all repos. 2. Turn on Dependency graph, Dependabot alerts and Dependabot security updates. 3. Apply it to all repositories and set it as the default for new ones. 4. Review open alerts at `github.com/orgs/ORG/security/alerts/dependabot`, starting with critical ones. Docs: Configuring Dependabot security updates (https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/secure-your-dependencies/configure-security-updates) Automated check (repo scope, needs Organization owner, or admin on every repo (classic token: repo, read:org)): jq: $repo.security_and_analysis as $s | if $s == null or $s.dependabot_security_updates == null then null else $s.dependabot_security_updates.status == "enabled" end Note: Reads Dependabot security updates, which cannot be on without alerts and the dependency graph. Risk: CICD-SEC-3: Dependency Chain Abuse Plan: GitHub Free Detect (audit log): dependency_graph.disable, dependabot_alerts.disable, repository_vulnerability_alerts.disable, repository_security_updates.disable - [Quick win · under an hour] Deletion and force-pushes prevented on critical branches Why: Attackers reliably delete branches and rewrite history to erase evidence during cleanup. Prerequisites: Organization owner for org-wide rulesets; or repo admin for one repo Steps: 1. Go to `github.com/organizations/ORG/settings/rules` and open your default-branch ruleset (or create one). 2. Make sure "Restrict deletions" and "Block force pushes" are both on. 3. Add release branches (for example `release/*`) to the target. 4. Keep the bypass list empty, or limited to a break-glass team. Docs: Available rules for rulesets (https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets) Automated check (repo scope, needs Organization owner, or admin on every repo (classic token: repo, read:org)): GET /repos/{owner}/{repo}/rules/branches/{default_branch} GET /repos/{owner}/{repo}/branches/{default_branch}/protection jq: (((.[0] // []) | any(.type == "deletion")) or (.[1].allow_deletions.enabled == false)) and (((.[0] // []) | any(.type == "non_fast_forward")) or (.[1].allow_force_pushes.enabled == false)) Note: Default branch only. Related scenarios: org-shai-hulud, user-glassworm Risk: CICD-SEC-1: Insufficient Flow Control Mechanisms Plan: GitHub Team. Free on public repositories. Detect (audit log): protected_branch.update_allow_force_pushes_enforcement_level, protected_branch.destroy, repository_ruleset.update, protected_branch.policy_override - [Quick win · under an hour] Add a .github/SECURITY.md with a disclosure contact and process Why: Without a disclosure path, a researcher's report is just as likely to go nowhere as to reach you. Prerequisites: Permission to create repositories in the org Steps: 1. Create a public repo named `.github` at `github.com/organizations/ORG/repositories/new`. 2. Add `SECURITY.md` at its root with a contact address, supported versions and expected response time. It applies to every repo that has no policy of its own. 3. Turn on private vulnerability reporting at `github.com/organizations/ORG/settings/security_products` so reports arrive as private advisories. Docs: Adding a security policy to your repository (https://docs.github.com/en/code-security/how-tos/report-and-fix-vulnerabilities/configure-vulnerability-reporting/add-security-policy) Automated check (org scope, needs Read access to the org's .github repo): GET /repos/{org}/.github/contents/SECURITY.md GET /repos/{org}/.github/contents/.github/SECURITY.md GET /repos/{org}/.github/contents/docs/SECURITY.md jq: any(.[]; . != null) Note: Checks the org-wide default only. Repos with their own SECURITY.md are not counted. Risk: CICD-SEC-10: Insufficient Logging and Visibility Plan: GitHub Free - [Moderate · plan for this quarter] CODEOWNERS in place, required on sensitive paths Why: No enforced review on sensitive paths means anyone with write access can change them unseen. Prerequisites: Write access to the repo; owners must be teams or users with write access; Organization owner or repo admin to change rulesets Steps: 1. Add `.github/CODEOWNERS` mapping sensitive paths to teams, for example `/.github/workflows/ @ORG/platform-security`. 2. Cover at least workflows, infra code, auth code and the CODEOWNERS file itself. 3. In your branch ruleset at `github.com/organizations/ORG/settings/rules`, turn on "Require review from Code Owners". 4. Open a test PR that touches a covered path and check the right team is requested. Docs: About code owners (https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners) Automated check (repo scope, needs Organization owner, or admin on every repo (classic token: repo, read:org)): GET /repos/{owner}/{repo}/codeowners/errors GET /repos/{owner}/{repo}/rules/branches/{default_branch} GET /repos/{owner}/{repo}/branches/{default_branch}/protection jq: (.[0] != null and (.[0].errors | length) == 0) and (((.[1] // []) | any(.type == "pull_request" and .parameters.require_code_owner_review == true)) or (.[2].required_pull_request_reviews.require_code_owner_reviews == true)) Note: Needs a valid CODEOWNERS file and code owner review required on the default branch. Related scenarios: org-nx-s1ngularity Risk: CICD-SEC-1: Insufficient Flow Control Mechanisms Plan: GitHub Team. Free on public repositories. Detect (audit log): protected_branch.update_require_code_owner_review, repository_ruleset.update - [Moderate · plan for this quarter] Rulesets applied: push restriction, tag protection, sensitive file-path protection Why: Tag protection is what stops a trusted version from being silently repointed after release. Prerequisites: Organization owner Steps: 1. Go to `github.com/organizations/ORG/settings/rules`. 2. New ruleset → "New tag ruleset". Target `v*`. Turn on "Restrict updates" and "Restrict deletions". Put your release automation on the bypass list only. 3. New ruleset → "New push ruleset". Restrict file paths such as `.github/workflows/**` so only the bypass team can push changes there. 4. On Enterprise, start each ruleset in Evaluate mode, review Rule insights for a week, then switch to Active. Docs: Available rules for rulesets (https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/rulesets?per_page=100 jq: [(.[0] | any(.target == "tag" and .enforcement == "active")), (.[0] | any(.target == "push" and .enforcement == "active"))] | map(select(.)) | length * 50 Note: 50% for an active tag ruleset, 50% for an active push ruleset. Repo-level rulesets are not counted. Related scenarios: app-tj-actions-changed-files, org-teampcp, org-chaindrop Risk: CICD-SEC-1: Insufficient Flow Control Mechanisms Plan: GitHub Team. Free on public repositories; push rulesets need private or internal repositories. Detect (audit log): repository_ruleset.destroy, repository_ruleset.update - [Moderate · plan for this quarter] Require signed commits on the default branch of sensitive repos Why: Git trusts whatever name and email a commit claims, so without signatures a commit can pose as any developer. Prerequisites: Organization owner for org-wide rulesets; or repo admin for one repo Steps: 1. Ask developers to set up SSH or GPG commit signing and upload the key at `github.com/settings/keys` as a signing key. 2. Go to `github.com/organizations/ORG/settings/rules` and open the ruleset for your sensitive repos (a custom property target works well here). 3. Turn on "Require signed commits". Start in Evaluate mode to see who would be blocked. 4. Commits made in the web UI and by GitHub Apps are signed by GitHub, so merge queues and bots keep working. Check other automation that pushes directly. Docs: About commit signature verification (https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) Automated check (repo scope, needs Organization owner, or admin on every repo (classic token: repo, read:org)): GET /repos/{owner}/{repo}/rules/branches/{default_branch} GET /repos/{owner}/{repo}/branches/{default_branch}/protection jq: ((.[0] // []) | any(.type == "required_signatures")) or (.[1].required_signatures.enabled == true) Note: Default branch only. Counts every repo, so a low score is expected if you only require it on sensitive ones. Risk: CICD-SEC-1: Insufficient Flow Control Mechanisms; CICD-SEC-9: Improper Artifact Integrity Validation Plan: GitHub Team. Free on public repositories. Detect (audit log): protected_branch.update_signature_requirement_enforcement_level, repository_ruleset.update - [Larger initiative · weeks] Adopt artifact attestations for build provenance on published binaries/images Why: Provenance is the only way to prove what actually built an artifact, after the fact. Prerequisites: Write access to the workflow Steps: 1. In the release workflow, add `permissions:` with `id-token: write`, `attestations: write` and `contents: read`. 2. After the build step, add `actions/attest-build-provenance` with `subject-path` pointing at the built file (or `subject-name` and `subject-digest` for an image). 3. Run a release and check the attestation at `github.com/ORG/REPO/attestations`. 4. To ship an SBOM with the release, generate one (for example with `anchore/sbom-action`) and attest it with `actions/attest-sbom`. 5. Verify it where the artifact is consumed: `gh attestation verify FILE -R ORG/REPO`. Docs: Using artifact attestations (https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations) Risk: CICD-SEC-9: Improper Artifact Integrity Validation Plan: GitHub Enterprise. Public repositories on any plan. - [Quick win · under an hour] Make releases immutable, or protect release tags with a tag ruleset Why: If a release tag or its assets can be changed after publication, an attacker with write access can point everyone who trusted that version at new code. Prerequisites: Organization owner, or admin on the repository Steps: 1. Turn on immutable releases for the organization or for each repository that publishes releases, so a published release's assets and tag cannot change. 2. For tags without releases, add a tag ruleset on `v*` (or your release pattern) that blocks updates and deletion, with an empty bypass list. 3. Publish Actions and packages only from these tags, and tell consumers to pin by commit SHA. Docs: Immutable releases (https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/immutable-releases) Related scenarios: app-tj-actions-changed-files, user-glassworm, org-chaindrop Risk: CICD-SEC-9: Improper Artifact Integrity Validation Plan: GitHub Free. Tag rulesets on private repositories need Team. Detect (audit log): repo.immutable_releases_settings_disabled, org.update_immutable_releases_settings_policy, repository_ruleset.update, repository_ruleset.destroy Last checked against GitHub's docs: 2026-10-11 ### CI/CD & GitHub Actions - [Quick win · under an hour] Set the default GITHUB_TOKEN permission to read-only at the org level Why: A permissive default token hands write access to every workflow, including third-party actions it calls. Prerequisites: Organization owner; or Enterprise owner to enforce it for all orgs Steps: 1. Go to `github.com/organizations/ORG/settings/actions`. 2. Under "Workflow permissions", select "Read repository contents and packages permissions". Save. 3. Workflows that need more add an explicit `permissions:` block with only the scopes they use. Docs: Setting GITHUB_TOKEN permissions for your organization (https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/actions/permissions/workflow jq: .[0].default_workflow_permissions == "read" Related scenarios: org-nx-s1ngularity Risk: CICD-SEC-5: Insufficient PBAC (Pipeline-Based Access Controls) Plan: GitHub Free Detect (audit log): org.set_default_workflow_permissions, business.set_default_workflow_permissions, repo.set_default_workflow_permissions Last checked against GitHub's docs: 2026-10-11 - [Quick win · under an hour] Turn off "Allow GitHub Actions to create and approve pull requests" Why: With it on, a workflow's approval counts towards required reviews, so anyone who can change a workflow can merge without a person reviewing. Prerequisites: Organization owner; or Enterprise owner to enforce it for all orgs Steps: 1. Go to `github.com/organizations/ORG/settings/actions`. 2. Under "Workflow permissions", untick "Allow GitHub Actions to create and approve pull requests". Save. 3. If a bot needs to open pull requests, give it a GitHub App with Pull requests: write instead, and keep its approvals out of required reviews. Docs: Disabling or limiting GitHub Actions for your organization (https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/actions/permissions/workflow jq: .[0].can_approve_pull_request_reviews | not Note: Repositories can still turn it on for themselves unless the organization or enterprise setting is off. Related scenarios: org-nx-s1ngularity Risk: CICD-SEC-1: Insufficient Flow Control Mechanisms; CICD-SEC-5: Insufficient PBAC (Pipeline-Based Access Controls) Plan: GitHub Free Detect (audit log): org.set_workflow_permission_can_approve_pr, business.set_workflow_permission_can_approve_pr, repo.set_workflow_permission_can_approve_pr Last checked against GitHub's docs: 2026-10-11 - [Quick win · under an hour] Require approval for first-time/external contributor workflow runs on forks Why: An unreviewed fork PR can run arbitrary workflow code the moment it's opened. Prerequisites: Organization owner Steps: 1. Go to `github.com/organizations/ORG/settings/actions`. 2. Under "Approval for running fork pull request workflows from contributors", select "Require approval for all external contributors". Save. 3. For private repos, under the private fork policy, leave "Send write tokens" and "Send secrets" off. 4. Before approving a run, read the diff, especially changes under `.github/`. Docs: Disabling or limiting GitHub Actions for your organization (https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/actions/permissions/fork-pr-contributor-approval jq: .[0].approval_policy == "all_external_contributors" Related scenarios: org-hackerbot-claw Risk: CICD-SEC-4: Poisoned Pipeline Execution (PPE) Plan: GitHub Free Detect (audit log): org.set_actions_fork_pr_approvals_policy, org.set_actions_private_fork_pr_approvals_policy, org.set_fork_pr_workflows_policy, repo.set_actions_fork_pr_approvals_policy - [Quick win · under an hour] Disallow self-hosted runners on public repositories Why: A public repo's self-hosted runner is reachable by anyone who can open a pull request against it. Prerequisites: Organization owner Steps: 1. Go to `github.com/organizations/ORG/settings/actions/runner-groups`. 2. Open each runner group and untick "Allow public repositories". Save. 3. Point public repos at GitHub-hosted runners instead. 4. Check each public repo's `github.com/ORG/REPO/settings/actions/runners` for repo-level self-hosted runners and remove them. Docs: Managing access to self-hosted runners (https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/manage-access) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/actions/runner-groups?per_page=100 jq: .[0].runner_groups | all(.allows_public_repositories | not) Note: Repo-level self-hosted runners are not checked. Related scenarios: org-self-hosted-runner-attacks Risk: CICD-SEC-4: Poisoned Pipeline Execution (PPE); CICD-SEC-5: Insufficient PBAC (Pipeline-Based Access Controls) Plan: GitHub Free Detect (audit log): org.runner_group_updated, org.runner_group_visiblity_updated, repo.register_self_hosted_runner Last checked against GitHub's docs: 2026-10-11 - [Quick win · under an hour] Inventory every Actions secret and its visibility/scope; the results are usually surprising Why: You can't scope what you haven't inventoried. Most orgs are surprised by what's exposed org-wide. Prerequisites: Organization owner for org secrets; repo admin for repo and environment secrets Steps: 1. Go to `github.com/organizations/ORG/settings/secrets/actions`. For each secret, note its repository access. 2. Change any secret set to "All repositories" to "Selected repositories". 3. Check `github.com/ORG/REPO/settings/secrets/actions` in each repo, or list repo and environment secrets with `gh secret list -R ORG/REPO` and `gh secret list -R ORG/REPO --env ENV`. 4. Check the Dependabot and Codespaces secrets tabs too. 5. Record owner and rotation date for each secret. Delete ones nobody claims. Docs: Using secrets in GitHub Actions (https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/actions/secrets?per_page=100 jq: .[0].secrets | all(.visibility != "all") Note: Passes when no org secret is shared with every repo. Repo and environment secrets are not listed. Related scenarios: org-ghostaction Risk: CICD-SEC-6: Insufficient Credential Hygiene Plan: GitHub Free Detect (audit log): org.create_actions_secret, org.update_actions_secret, repo.create_actions_secret, environment.create_actions_secret - [Moderate · plan for this quarter] Third-party Actions pinned to a full commit SHA instead of a mutable tag or branch Why: A mutable tag can point to different code tomorrow than it does today, with no warning to you. Prerequisites: Write access to workflows; Organization owner to enforce it Steps: 1. Find unpinned actions: `grep -rn "uses:" .github/workflows | grep -vE "@[0-9a-f]{40}"`. 2. Replace each tag with its full commit SHA plus a comment, e.g. `actions/checkout@ # v4.2.2`. Tools like pinact or ratchet do this in bulk. 3. Add a Dependabot config with `package-ecosystem: github-actions` so the pinned SHAs get update PRs. 4. Once repos are pinned, go to `github.com/organizations/ORG/settings/actions` and turn on "Require actions to be pinned to a full-length commit SHA". Docs: Secure use reference: using third-party actions (https://docs.github.com/en/actions/reference/security/secure-use) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/actions/permissions jq: if .[0].sha_pinning_required == true then true else null end Note: Only reads the org-wide enforcement setting. Per-workflow pinning is not scanned yet. Related scenarios: app-tj-actions-changed-files, org-teampcp Risk: CICD-SEC-3: Dependency Chain Abuse Plan: GitHub Free Detect (audit log): org.update_actions_settings, workflows.actions_policy_violation - [Moderate · plan for this quarter] Audit every use of pull_request_target and remove or restructure risky ones Why: This trigger hands secrets and write access to a workflow that can be made to run untrusted code. Prerequisites: Read access to the repos; write access to fix them Steps: 1. Search: `github.com/search?q=org:ORG+pull_request_target+path:.github/workflows&type=code`. 2. For each hit, check whether it checks out or runs code from the PR (for example `ref: ${{ github.event.pull_request.head.sha }}`). 3. If it does, switch to `pull_request`, or split it into a `pull_request` job plus a `workflow_run` job that holds the secrets. 4. Never put `${{ github.event.* }}` straight into `run:`. Pass it through `env:` instead. 5. Add a minimal `permissions:` block. Run zizmor or CodeQL for Actions to catch what you missed. Docs: Secure use reference (https://docs.github.com/en/actions/reference/security/secure-use) Related scenarios: org-pwn-requests, org-ultralytics Risk: CICD-SEC-4: Poisoned Pipeline Execution (PPE) Plan: GitHub Free - [Moderate · plan for this quarter] Restrict which Actions can run to GitHub-verified creators or an internal allow-list Why: An unverified action is arbitrary code with access to your repo and secrets by default. Prerequisites: Organization owner Steps: 1. List the actions in use today (search `uses:` across the org) so you know what to allow. 2. Go to `github.com/organizations/ORG/settings/actions`. 3. Under "Policies", select the option that allows your own org's actions plus selected outside actions and reusable workflows. 4. Tick "Allow actions created by GitHub" and "Allow actions by Marketplace verified creators". 5. Add every other action you need to the allow list as `owner/action@`. Save. Docs: Allowing select actions to run (https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/actions/permissions jq: if .[0].enabled_repositories == "none" then true elif .[0].allowed_actions == null then null else .[0].allowed_actions != "all" end Related scenarios: app-tj-actions-changed-files Risk: CICD-SEC-3: Dependency Chain Abuse; CICD-SEC-8: Ungoverned Usage of 3rd Party Services Plan: GitHub Free Detect (audit log): org.update_actions_settings, business.update_actions_settings, repo.update_actions_settings, workflows.actions_policy_violation - [Moderate · plan for this quarter] Write and rehearse a credential-rotation runbook before you need it Why: A rotation plan practiced in advance is the difference between a 30-minute incident and a multi-day one. Prerequisites: Owners for each credential type (GitHub, cloud, registries) Steps: 1. List every credential type: PATs, GitHub App keys, deploy keys, Actions secrets, cloud keys, registry tokens. 2. For each, write who can rotate it, where it's used, and the exact revoke and replace steps. 3. Add how to check the audit log for use of the old credential after revoking it. 4. Store the runbook where on-call can find it during an incident. 5. Rehearse it once a year by rotating one real credential end to end. Risk: CICD-SEC-6: Insufficient Credential Hygiene Plan: GitHub Free - [Larger initiative · weeks] Cloud credentials migrated from static keys to OIDC federation, scoped to repo + branch Why: A static cloud key that leaks works from anywhere, forever; a scoped OIDC trust policy doesn't. Prerequisites: IAM admin in the cloud account; write access to the workflow Steps: 1. In your cloud, add `token.actions.githubusercontent.com` as an OIDC identity provider. 2. Create a role whose trust policy restricts `sub` to one repo and branch or environment, e.g. `repo:ORG/REPO:ref:refs/heads/main`. 3. In the workflow, set `permissions: id-token: write` and use the cloud's login action (e.g. `aws-actions/configure-aws-credentials` with `role-to-assume`). 4. Delete the old static key from Actions secrets and deactivate it in the cloud. Docs: OpenID Connect in GitHub Actions (https://docs.github.com/en/actions/concepts/security/openid-connect) Related scenarios: org-nx-s1ngularity Risk: CICD-SEC-6: Insufficient Credential Hygiene Plan: GitHub Free Detect (audit log): org.create_actions_secret, repo.create_actions_secret, environment.create_actions_secret - [Larger initiative · weeks] Service-account automation migrated from PATs to GitHub Apps Why: A PAT inherits its creator's full permissions; a GitHub App only ever has what you explicitly grant it. Prerequisites: Organization owner or GitHub App manager Steps: 1. Register an app at `github.com/organizations/ORG/settings/apps/new`. Turn off the webhook and grant only the permissions the job needs. 2. Generate a private key and store it with the App ID as Actions secrets. 3. Install the app on only the repositories it needs. 4. In workflows, mint a short-lived token with `actions/create-github-app-token` and use it where the PAT was. 5. Revoke the old PAT. Docs: Authenticating with a GitHub App in a workflow (https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow) Related scenarios: app-installation-token-abuse Risk: CICD-SEC-2: Inadequate Identity and Access Management; CICD-SEC-6: Insufficient Credential Hygiene Plan: GitHub Free Detect (audit log): personal_access_token.access_granted, personal_access_token.request_created, personal_access_token.access_restriction_disabled - [Moderate · plan for this quarter] Set `persist-credentials: false` on actions/checkout wherever later steps do not push Why: By default actions/checkout leaves the job's token on disk for the rest of the job, where any later step, or an uploaded artifact, can pick it up. Prerequisites: Write access to the workflows Steps: 1. Search the organization's workflows for `actions/checkout`. 2. Add `persist-credentials: false` under `with:` on every checkout whose job does not run authenticated git commands afterwards. 3. Where a job does need to push, pass the token only to that step. 4. A workflow linter such as zizmor flags checkouts that persist credentials (see the Tooling page). Docs: actions/checkout: persist-credentials (https://github.com/actions/checkout) Related scenarios: app-artipacked Risk: CICD-SEC-4: Poisoned Pipeline Execution (PPE); CICD-SEC-6: Insufficient Credential Hygiene Plan: GitHub Free Last checked against GitHub's docs: 2026-10-11 - [Moderate · plan for this quarter] Put production secrets in environments with required reviewers Why: Anyone with write access can read repository secrets through a workflow. Environment secrets are only given to a job after the environment's rules, such as required reviewers, pass. Prerequisites: Admin on the repository Steps: 1. In each repository that deploys, create a `production` environment under Settings, Environments. 2. Add required reviewers (a small team, not the people who write the code alone) and restrict which branches can deploy. 3. Move deployment secrets from repository secrets into the environment, and delete the repository copies. 4. Reference the environment in the deploy job with `environment: production`. Docs: Managing environments for deployment (https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) Related scenarios: org-ghostaction, org-chaindrop Risk: CICD-SEC-1: Insufficient Flow Control Mechanisms; CICD-SEC-5: Insufficient PBAC (Pipeline-Based Access Controls) Plan: GitHub Enterprise. Free and Team: public repositories only. Detect (audit log): environment.remove_protection_rule, environment.update_protection_rule, repo.create_actions_secret, workflows.approve_workflow_job Last checked against GitHub's docs: 2026-10-11 - [Moderate · plan for this quarter] Limit each self-hosted runner group to the repositories that need it Why: A runner group open to all repositories lets any repository, including one an attacker can write to, run jobs on those machines and reach what they can reach. Prerequisites: Organization owner Steps: 1. Go to `github.com/organizations/ORG/settings/actions/runner-groups`. 2. For each group, set Repository access to "Selected repositories" and pick only the repositories that need those runners. 3. Keep runners with access to production networks or credentials in their own group, used only by deployment repositories. Docs: Managing access to self-hosted runners (https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners/manage-access) Automated check (org scope, needs Organization owner (classic token: admin:org, read:org)): GET /orgs/{org}/actions/runner-groups?per_page=100 jq: .[0].runner_groups | all(.visibility != "all") Note: The Default group counts too. If it holds no self-hosted runners, it is safe to accept. Related scenarios: org-self-hosted-runner-attacks Risk: CICD-SEC-5: Insufficient PBAC (Pipeline-Based Access Controls) Plan: GitHub Team. Free organizations have only the default group. Detect (audit log): org.runner_group_updated, org.runner_group_visiblity_updated, enterprise.runner_group_updated Last checked against GitHub's docs: 2026-10-11 - [Moderate · plan for this quarter] Do not restore Actions caches in release and publish workflows Why: A cache written by a less trusted workflow can be restored by a more trusted one. A poisoned cache in a release build ships the attacker's code under your name. Prerequisites: Write access to the workflows Steps: 1. List the workflows that build releases, publish packages or deploy. 2. Remove `actions/cache` steps and the `cache:` option of setup actions (setup-node, setup-python and others) from those workflows. 3. Keep caching in pull request and test workflows, where it saves time and a poisoned cache only affects a test run. 4. zizmor's cache-poisoning audit finds release workflows that still restore caches. Docs: Dependency caching reference (https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching) Related scenarios: org-cache-poisoning Risk: CICD-SEC-4: Poisoned Pipeline Execution (PPE); CICD-SEC-9: Improper Artifact Integrity Validation Plan: GitHub Free Last checked against GitHub's docs: 2026-10-11 - [Moderate · plan for this quarter] Publish packages with trusted publishing (OIDC) instead of long-lived registry tokens Why: A stored npm or PyPI token can be stolen and used from anywhere. Trusted publishing gives a short-lived credential to one named workflow instead. Prerequisites: Owner of the package on the registry; Write access to the publishing workflow Steps: 1. On the registry (npm, PyPI and others), add a trusted publisher for the package: the repository and the publishing workflow file, and an environment if you use one. 2. Give the publishing job `permissions: id-token: write` and remove the registry token from the workflow. 3. Revoke the old registry token, and turn off token-based publishing on the registry where it allows that. Docs: npm: Trusted publishing (https://docs.npmjs.com/trusted-publishers) Related scenarios: org-shai-hulud, org-nx-s1ngularity Risk: CICD-SEC-6: Insufficient Credential Hygiene; CICD-SEC-9: Improper Artifact Integrity Validation Plan: GitHub Free Detect (audit log): org.create_actions_secret, repo.create_actions_secret, environment.create_actions_secret Last checked against GitHub's docs: 2026-10-11 ### Optional: Secret Protection and Code Security (formerly GitHub Advanced Security) - Secret scanning on private/internal repos [needs Secret Protection]: Would have caught the harvested secrets in GhostAction before they ever left the repo. Prerequisites: Organization owner or Security manager 1. Go to `github.com/organizations/ORG/settings/security_products`. 2. Create a configuration with Secret Protection enabled, or use the GitHub recommended one. 3. Apply it to your private and internal repositories and set it as the default for new ones. 4. Review open alerts at `github.com/orgs/ORG/security` and rotate anything valid. Docs: https://docs.github.com/en/code-security/concepts/secret-security/secret-scanning - Push protection on private/internal repos [needs Secret Protection]: Stops a secret at the push itself, rather than relying on scanning and rotation after the fact. Prerequisites: Organization owner or Security manager 1. Go to `github.com/organizations/ORG/settings/security_products` and edit your configuration. 2. Turn on Push protection. Apply it to private and internal repositories. 3. Optionally turn on delegated bypass so a push with a secret needs a reviewer's approval to go through. Docs: https://docs.github.com/en/code-security/concepts/secret-security/push-protection - Code scanning (CodeQL) on private/internal repos [needs Code Security]: Catches the injection-class bugs that let attacker-controlled input reach a shell in the first place. Prerequisites: Organization owner or Security manager 1. Go to `github.com/organizations/ORG/settings/security_products`. 2. In your configuration, enable Code Security with code scanning default setup. 3. Apply it to private and internal repositories. 4. Add "Require code scanning results" to your branch ruleset so high-severity findings block merges. Docs: https://docs.github.com/en/code-security/concepts/code-scanning/code-scanning - Dependency review on pull requests [needs Code Security]: Flags a malicious or vulnerable dependency bump directly in the PR diff, before it merges. Prerequisites: write access to workflows 1. Make sure the dependency graph is on in your security configuration at `github.com/organizations/ORG/settings/security_products`. 2. Add a workflow on `pull_request` that runs `actions/dependency-review-action` (pinned to a SHA). 3. Set `fail-on-severity: high` (or lower) in its config. 4. Add the check as a required status check in your branch ruleset. Docs: https://docs.github.com/en/code-security/concepts/supply-chain-security/dependency-review - Security overview & security campaigns [needs Code Security]: One dashboard beats manually checking alerts repo-by-repo across an entire organization. Prerequisites: Organization owner or Security manager 1. Go to `github.com/orgs/ORG/security` to see alerts and coverage across all repos. 2. Filter to the repos or alert types that matter most, for example critical code scanning alerts. 3. Create a campaign from that filter under `github.com/orgs/ORG/security/campaigns`, with a due date and a contact. Docs: https://docs.github.com/en/code-security/concepts/security-at-scale/security-overview - Copilot Autofix for code scanning alerts [needs Code Security]: Lowers the effort of fixing a finding enough that alerts actually get resolved instead of ignored. Prerequisites: Code scanning enabled 1. Go to `github.com/organizations/ORG/settings/security_products` and check Copilot Autofix is on in your configuration. 2. Open a PR with a code scanning alert and review the suggested fix before committing it. Total checklist items: 47 --- ## Open-source tooling (18 tools, GitHub stats as of 2026-10-10) Ratings are editorial, 1-5, relative to the tool's category: - 5 (Top pick): The default choice for its job. Accurate, actively maintained, easy to run in CI, fully usable without a paid account. - 4 (Recommended): Worth running, with a real trade-off: more noise, narrower scope, heavier setup, or the best parts need an account. - 3 (Situational): Good for a specific need or as a second opinion. Smaller project or slower maintenance. - 2 (Niche): Mostly superseded or very narrow. Use it only if you have a specific reason. - 1 (Legacy): Archived or deprecated. Listed for reference. Plan a migration if you still run it. ### Workflow analysis Static analysis of .github/workflows and action.yml files: script injection, dangerous triggers, excessive token permissions, unpinned uses. #### zizmor — 5/5 Top pick, 6683 stars, MIT, Open source id: zizmor | url: /tooling/zizmor | repo: https://github.com/zizmorcore/zizmor Latest release: v1.30.1 (2026-09-09) Summary: Static analysis for GitHub Actions workflows and action.yml files, written in Rust. Rating note: The most thorough open-source scanner for Actions workflows, fast, offline by default and actively released. How it works: zizmor parses every workflow under .github/workflows, composite action definitions (action.yml) and Dependabot config, and runs a set of named audits over the parsed YAML. Each audit knows the semantics of Actions: which triggers run with secrets, which expression contexts an outsider controls, and what a uses: reference resolves to. Most audits are offline: template-injection, dangerous-triggers, excessive-permissions, unpinned-uses, artipacked (credentials left by actions/checkout), cache-poisoning, secrets-inherit and self-hosted-runner. Online audits such as impostor-commit, ref-confusion and known-vulnerable-actions call the GitHub API with a token to check that a pinned SHA really belongs to the action's repository and that the version has no advisory. Findings carry a severity and a confidence. Personas (regular, pedantic, auditor) control how noisy it is, a zizmor.yml file or an inline '# zizmor: ignore[audit]' comment suppresses findings, and output is plain text, JSON, SARIF or GitHub annotations. Catches: Attacker-controlled expressions such as PR titles inside run: steps; pull_request_target and workflow_run used with a checkout of PR code; Actions referenced by tag or branch instead of a full SHA; Commit SHAs that only exist in a fork (impostor commits, online); Write-all or missing permissions blocks; persist-credentials left on before an artifact upload What leaves the machine: Offline audits send nothing. Online audits call the GitHub API with GH_TOKEN; --no-online-audits turns them off. Limits: Static only. It cannot see what an action does at runtime or what a setup script downloads.; Online audits need a token and count against API rate limits, so they suit a scheduled job better than every PR.; The pedantic and auditor personas produce findings that need triage before you can gate on them. Run: pipx install zizmor zizmor . # CI-friendly: no network, SARIF out zizmor --no-online-audits --format sarif . > zizmor.sarif Links: Documentation (https://docs.zizmor.sh) | Audit reference (https://docs.zizmor.sh/audits/) #### actionlint — 4/5 Recommended, 4310 stars, MIT, Open source id: actionlint | url: /tooling/actionlint | repo: https://github.com/rhysd/actionlint Latest release: v1.7.12 (2026-03-31) OpenSSF Scorecard: 3.6/10 (2026-10-05) Summary: Workflow linter that type-checks expressions and runs ShellCheck on run: scripts. Rating note: The standard workflow linter for correctness, with a few security checks; pair it with zizmor for the rest. How it works: actionlint is a Go binary that parses workflow files against the full workflow schema. It type-checks every ${{ }} expression, knows which contexts are available in which keys, and validates inputs for popular actions, runner labels, cron syntax and job dependencies. Shell in run: steps is passed to ShellCheck and Python steps to pyflakes when those are installed. One check flags untrusted inputs such as github.event.pull_request.title used directly in an inline script, which is the classic script injection. Catches: Syntax and schema errors before a push breaks CI; Expressions that reference missing contexts or have the wrong type; Untrusted event fields inside inline scripts; Shell bugs in run: steps (via ShellCheck); Invalid runner labels and needs: graphs What leaves the machine: Nothing. It reads the workflow files locally. Limits: Mostly about correctness. It does not check pinning, permissions or dangerous triggers.; No SARIF writer; output is text or a Go template you supply. Run: brew install actionlint actionlint # or without installing: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:latest Links: List of checks (https://github.com/rhysd/actionlint/blob/main/docs/checks.md) #### poutine — 4/5 Recommended, 523 stars, Apache-2.0, Open source id: poutine | url: /tooling/poutine | repo: https://github.com/boostsecurityio/poutine Latest release: v1.1.6 (2026-05-22) OpenSSF Scorecard: 7.6/10 (2026-10-09) Summary: Scans CI pipelines in a repo or a whole GitHub or GitLab org for supply-chain misconfigurations, with rules in Rego. Rating note: Strong at org-wide scans and build-tool risks, but fewer workflow audits than zizmor and a smaller community. How it works: poutine, from BoostSecurity, reads pipeline definitions (GitHub Actions, GitLab CI and others) into a common model and evaluates rules written in Rego with an embedded Open Policy Agent engine. It can scan a local checkout, one remote repository, or every repository in an organization through the API. Its distinctive rule is untrusted_checkout_exec: it knows which build tools execute code from the working tree (npm install, make, pip and many more, catalogued in BoostSecurity's Living Off The Pipeline project) and flags them when they run on a checkout of PR code in a privileged workflow. Other rules cover injection, self-hosted runners on PR triggers, default permissions on risky events and known-vulnerable build components. Catches: Privileged workflows that build or install untrusted PR code; Expression injection in run: steps and scripts; Self-hosted runners reachable from pull requests; Jobs that receive all secrets; Known-vulnerable actions and build components What leaves the machine: analyze_local sends nothing. analyze_repo and analyze_org call the GitHub or GitLab API with your token and clone the repositories. Limits: Fewer GitHub-specific audits than zizmor (no impostor commit or cache-poisoning checks).; Writing your own rules means learning Rego. Run: brew install poutine poutine analyze_local . # a whole org, with GH_TOKEN set: poutine analyze_org my-org --format sarif Links: Living Off The Pipeline (https://boostsecurityio.github.io/lotp/) ### Action pinning Rewrite uses: references to full commit SHAs, keep them current, and check they still match. #### pinact — 4/5 Recommended, 1222 stars, MIT, Open source id: pinact | url: /tooling/pinact | repo: https://github.com/suzuki-shunsuke/pinact Latest release: v5.0.0 (2026-09-12) Summary: Rewrites uses: references in workflows to full commit SHAs with the version kept as a comment. Rating note: The simplest way to bulk-pin a repository's actions to SHAs, maintained by a prolific Actions tool author. How it works: pinact is a Go CLI that finds every uses: line in workflow and action files, resolves the tag or branch to a commit through the GitHub API, and rewrites the line as owner/repo@ # vX.Y.Z. The trailing comment is the format Dependabot and Renovate understand, so they can keep the pins current afterwards. It can also update pins to the latest release, and a check mode exits non-zero when something is unpinned, so it works as a CI guard. Catches: Tag and branch references in workflows and composite actions; Pins without a version comment; Out-of-date pins, when run with update What leaves the machine: Calls the GitHub API to resolve tags to SHAs. Set GITHUB_TOKEN to avoid the unauthenticated rate limit. Limits: Only pins the top-level uses: line. Nested actions inside a composite action stay as their author wrote them.; Does not check that a SHA is reachable from the real repository; zizmor's impostor-commit audit does. Run: brew install suzuki-shunsuke/pinact/pinact pinact run ### Runner hardening Watch or restrict what a job does while it runs: outbound network, file tampering, and which token permissions it actually used. #### Harden-Runner — 4/5 Recommended, 1279 stars, Apache-2.0, Open core id: harden-runner | url: /tooling/harden-runner | repo: https://github.com/step-security/harden-runner Latest release: v2.22.1 (2026-10-08) OpenSSF Scorecard: 7.8/10 (2026-08-30) Summary: Runner agent from StepSecurity that records and can block outbound network calls, file tampering and processes per job. Rating note: The established runtime monitor for hosted runners and the tool that caught tj-actions, but the insights live in StepSecurity's service. How it works: You add Harden-Runner as the first step of a job. It installs an agent on the runner that watches DNS lookups, outbound connections, processes and writes to the checked-out source for the rest of the job. With egress-policy: audit it only records. The recorded endpoints become a baseline you turn into an allowed-endpoints list, and with egress-policy: block anything else is refused. Each run gets an insights page in StepSecurity's app that shows which step called what. Catches: Exfiltration to unexpected domains or IPs; A compromised action or dependency reaching out at runtime; Source files modified during the build; Unexpected processes started by a step What leaves the machine: Network, process and file events from the job are sent to StepSecurity's API to build the insights page. Limits: Advanced features and private-repository use beyond the free tier need a StepSecurity plan.; Block mode needs a maintained allow-list per job, which takes effort across many repositories.; It protects the job it runs in, not other jobs on the same self-hosted runner. Run: # Runs only inside GitHub Actions, as the first step of a job: - uses: step-security/harden-runner@ # pin to a release SHA with: egress-policy: audit Links: StepSecurity docs (https://docs.stepsecurity.io) ### Secret scanning Find credentials in code, git history, CI logs and artifacts, before or after they are pushed. #### Gitleaks — 5/5 Top pick, 29825 stars, MIT, Open source id: gitleaks | url: /tooling/gitleaks | repo: https://github.com/gitleaks/gitleaks Latest release: v8.30.1 (2026-03-21) Summary: Finds secrets in git history, files or stdin with regex, keyword and entropy rules. Rating note: The default open-source secret scanner: one binary, no network, easy rules and baselines. How it works: Gitleaks is a Go binary. For a repository it runs git log -p and scans each patch, so it sees secrets that were added and later deleted. It can also scan a plain directory or stdin. Rules live in a TOML config: each has a regex, optional keywords that must appear first (a cheap pre-filter), an entropy threshold and allowlists. The default config covers a few hundred token formats. Known findings can be ignored with a baseline report, a .gitleaksignore file of fingerprints, or a gitleaks:allow comment. Output is JSON, CSV, JUnit, SARIF or a custom template, with --redact to keep matches out of logs. The exit code is non-zero when anything is found. Catches: Cloud keys, GitHub tokens, Slack and Stripe tokens and other known formats; Private keys and generic high-entropy secrets; Secrets removed from HEAD but still in history; New secrets in a PR's commit range What leaves the machine: Nothing. Scanning is local. Limits: It does not check whether a secret is live; TruffleHog's verification does.; Custom token formats need your own rules.; The official gitleaks-action needs a free license key for organization accounts, which is why many workflows install the binary instead. Run: brew install gitleaks gitleaks detect --source . --redact # only the commits in a PR: gitleaks detect --source . --redact --log-opts="origin/main..HEAD" Links: gitleaks-action (https://github.com/gitleaks/gitleaks-action) ### Vulnerable dependencies Match the packages you ship against advisory databases such as OSV.dev, the GitHub Advisory Database and the NVD. #### OSV-Scanner — 5/5 Top pick, 11159 stars, Apache-2.0, Open source id: osv-scanner | url: /tooling/osv-scanner | repo: https://github.com/google/osv-scanner Latest release: v2.6.0 (2026-09-14) OpenSSF Scorecard: 8.6/10 (2026-10-09) Summary: Matches packages from lockfiles, SBOMs and container images against the OSV.dev vulnerability database. Rating note: Broad ecosystem coverage, the open OSV.dev data, an offline mode and ignore files with reasons. How it works: OSV-Scanner, from Google, extracts package names and versions from lockfiles and manifests (npm, pip, Go, Cargo, Maven, NuGet and many more), SBOMs and container images. It then queries OSV.dev's batch API. OSV.dev aggregates the GitHub Advisory Database, PyPA, RustSec, the Go vulnerability database, OSS-Fuzz, Linux distribution advisories and the OpenSSF malicious-packages feed in one schema. With --offline-vulnerabilities it uses a local copy of the database instead. For Go (through govulncheck) and Rust it can do call analysis and mark vulnerabilities whose affected functions are never called. An osv-scanner.toml file ignores specific IDs with a reason and an expiry date. Output is a table, JSON, SARIF, Markdown or HTML. Google publishes reusable workflows for PR and scheduled scans. Catches: Known vulnerabilities in direct and transitive dependencies; Known malicious package versions (MAL- IDs); Vulnerable OS packages in container images; Vulnerabilities in vendored C/C++ code identified by commit What leaves the machine: Package names and versions (not source code) go to api.osv.dev, unless you use the offline database. Limits: Lockfile matching reports everything declared, including dev-only packages that never ship. Filter with reachability or ignore entries.; Call analysis covers Go and Rust only.; Like any advisory scanner, it can't see a malicious release before someone reports it. Run: brew install osv-scanner osv-scanner scan -L package-lock.json osv-scanner scan -r . Links: Documentation (https://google.github.io/osv-scanner/) | OSV.dev (https://osv.dev) | osv-scanner-action (https://github.com/google/osv-scanner-action) #### npm audit — 4/5 Recommended, 10182 stars, Artistic-2.0, Open source id: npm-audit | url: /tooling/npm-audit | repo: https://github.com/npm/cli Latest release: v12.2.0 (2026-09-30) OpenSSF Scorecard: 6.8/10 (2026-10-05) Summary: npm's built-in check of your dependency tree against the GitHub Advisory Database. Rating note: Free and already installed, but npm-only, noisy on dev dependencies and without an ignore file. How it works: npm audit builds the dependency tree from package-lock.json and sends the package names and versions to the registry's bulk advisory endpoint. The registry answers with advisories from the GitHub Advisory Database, and npm works out which installed paths are affected. --audit-level sets the severity that fails the command and --omit=dev skips dev dependencies. npm audit fix upgrades within semver ranges, and --force allows breaking upgrades. npm audit signatures separately checks registry signatures and provenance attestations for installed packages. Catches: Known vulnerabilities in npm packages, direct and transitive; The dependency path that pulls each one in; Packages with invalid registry signatures or provenance What leaves the machine: The dependency tree (package names and versions) goes to the npm registry. Limits: No built-in way to ignore an advisory you have assessed.; Reports dev dependencies unless told not to, which is most of the noise.; npm audit fix --force can make major-version changes you didn't review. Run: npm audit --audit-level=high --omit=dev npm audit --json > npm-audit.json npm audit signatures Links: npm audit docs (https://docs.npmjs.com/cli/commands/npm-audit) ### Malicious packages Look at what a package does rather than whether it has a CVE: install scripts, network calls, obfuscation, typosquats, sudden maintainer changes. #### Socket CLI — 4/5 Recommended, 323 stars, MIT, Open client, hosted service id: socket-cli | url: /tooling/socket-cli | repo: https://github.com/SocketDev/socket-cli Latest release: v1.1.146 Summary: Client for Socket, which analyzes what each package version does to spot malware and risky behaviour. Rating note: Catches malicious packages before they have a CVE, but the analysis runs in Socket's service and needs an API key. How it works: Socket analyzes every version published to npm, PyPI and other registries: install scripts, network and shell access, filesystem and environment variable access, obfuscated code, typosquatted names and maintainer changes, plus AI-assisted malware review. It does this when a version is published, so verdicts are ready before you install. The CLI uploads your manifests and lockfiles (socket scan create) and reports alerts against your organization's policy. socket npm wraps npm install and checks packages before they install. The GitHub app comments on pull requests that add risky dependencies. Catches: Known malware and newly published malicious versions; Install scripts, network calls and obfuscation in new dependencies; Typosquats of popular packages; Unexpected new maintainers or protestware What leaves the machine: Manifest and lockfile contents (package names and versions) go to Socket's API. Source code is not uploaded. Limits: Needs a Socket account and API token; fork PRs don't get the secret, so make the step skip cleanly.; Verdicts come from Socket's service, so you can't reproduce them offline. Run: npm install -g socket socket login socket scan create --repo my-repo . Links: Socket docs (https://docs.socket.dev) ### Code scanning (SAST) Pattern matching and dataflow analysis over your own source code. #### Semgrep CE — 4/5 Recommended, 16947 stars, LGPL-2.1, Open core id: semgrep | url: /tooling/semgrep | repo: https://github.com/semgrep/semgrep Latest release: v1.180.0 (2026-10-07) Summary: Pattern-based code scanner whose rules look like the code they match. Rating note: Fast, readable rules and a large registry, but cross-file dataflow and many rules are only in the paid product. How it works: Semgrep parses source into a generic syntax tree for each of its supported languages and matches rules written in YAML. A rule pattern is code with metavariables ($X) and ellipses (...), so a rule for eval($USER_INPUT) looks like the code it finds. Taint mode tracks data from sources to sinks within a file. Rules come from your own files or from the Semgrep Registry (for example --config p/ci). Since December 2024 Semgrep's own rules are under the Semgrep Rules License, which allows internal use but not use in competing products. That change led to the Opengrep fork. Output is text, JSON, SARIF or JUnit, and --error makes the exit code non-zero on findings. The commercial engine adds cross-file and cross-function dataflow. Catches: Injection sinks fed by request data (SQL, command, template); Dangerous functions and insecure defaults per framework; Hardcoded credentials and weak crypto; Your own banned patterns, written in minutes What leaves the machine: Registry rules are downloaded at start. Usage metrics are sent when you use registry configs unless you pass --metrics off. Limits: Community Edition dataflow stops at the file boundary.; Registry rule licensing changed in 2024; check it fits how you use them. Run: pipx install semgrep semgrep scan --config p/ci --metrics off --error . Links: Documentation (https://semgrep.dev/docs/) | Writing rules (https://semgrep.dev/docs/writing-rules/overview) #### CodeQL — 4/5 Recommended, 10182 stars, MIT, Source available id: codeql | url: /tooling/codeql | repo: https://github.com/github/codeql Also covers: Workflow analysis OpenSSF Scorecard: 5.8/10 (2026-10-05) Summary: GitHub's semantic code analysis: code becomes a database, queries do interprocedural taint tracking. Rating note: The deepest analysis here and built into GitHub, but slow and only free for public repositories. How it works: CodeQL first extracts a relational database from the code. For compiled languages it watches the build, for others it reads the source. Queries written in QL then run against that database, with libraries for interprocedural dataflow and taint tracking from sources (request data, event fields) to sinks (queries, shells, file paths). On GitHub you turn it on with default setup in the repository settings or with github/codeql-action in a workflow, and results appear as code scanning alerts on PRs. Since April 2025 it also analyzes GitHub Actions workflows for injection, missing permissions and untrusted checkouts. The queries in github/codeql are MIT licensed. The CLI and engine are under the GitHub CodeQL Terms: free for open source and research, and for private repositories it needs GitHub Code Security. Catches: Injection and path traversal across function and file boundaries; Unsafe deserialization and SSRF; Script injection and untrusted checkout in Actions workflows; Custom variant analysis written as your own queries What leaves the machine: Local analysis sends nothing. On GitHub, results are uploaded as code scanning alerts. Limits: Analysis takes minutes to an hour on large codebases.; Private repositories need a paid GitHub Code Security license.; Writing QL queries is a skill of its own. Run: gh extension install github/gh-codeql gh codeql database create db --language=javascript gh codeql database analyze db --format=sarif-latest --output=codeql.sarif Links: CodeQL docs (https://codeql.github.com/docs/) | Actions workflow analysis GA (https://github.blog/changelog/2025-04-22-github-actions-workflow-security-analysis-with-codeql-is-now-generally-available/) ### IaC and container config Misconfigurations in Terraform, Kubernetes manifests, Dockerfiles and CI config, caught before they are applied. #### Checkov — 5/5 Top pick, 9061 stars, Apache-2.0, Open core id: checkov | url: /tooling/checkov | repo: https://github.com/bridgecrewio/checkov Also covers: Workflow analysis, Org and repo posture, Secret scanning Latest release: 3.3.26 (2026-10-07) OpenSSF Scorecard: 7/10 (2022-11-28) Summary: Scans Terraform, CloudFormation, Kubernetes, Helm, Dockerfiles, CI workflows and GitHub org settings for misconfigurations using Python and YAML policies. Rating note: The widest free IaC rule set, released several times a week and easy to drop into CI, though severities and SCA need a Prisma Cloud key. How it works: Checkov is a Python CLI maintained by Palo Alto Networks (Prisma Cloud, formerly Bridgecrew). Each framework has its own parser (HCL2 for Terraform, YAML and JSON for CloudFormation and Kubernetes, a Dockerfile parser, Bicep and ARM parsers, and Terraform plan JSON). Attribute checks are Python classes that get one resource's parsed config and return passed or failed. They have ids like CKV_AWS_20. For Terraform, CloudFormation, Kubernetes and several other frameworks it also builds an in-memory graph of resources and the references between them (for Terraform, after rendering variables, locals and modules). Graph checks (CKV2_*) are YAML policies made of attribute, connection and filter conditions joined with and/or/not, so a rule can ask whether a bucket has a connected public access block rather than only looking at the bucket. Your own Python or YAML checks load from --external-checks-dir. The github_actions framework parses workflow YAML and runs seven job checks plus one graph check: ACTIONS_ALLOW_UNSECURE_COMMANDS, a regex list of untrusted github.event fields inside run: (CKV_GHA_2), curl with secrets, netcat to an IP, missing cosign signing or SBOM attestation, non-empty workflow_dispatch inputs, and write-all top-level permissions. The github_configuration framework calls the GitHub REST and GraphQL APIs with GITHUB_TOKEN (org settings, branch protection on the current branch, webhooks, collaborators, org admins), saves the JSON under github_conf/ and runs CKV_GITHUB_* checks on it. The secrets framework uses bc-detect-secrets, Bridgecrew's fork of Yelp's detect-secrets, with regex, keyword and entropy detectors. Output is cli, json, sarif, junitxml, csv, cyclonedx, spdx, gitlab_sast or github_failed_only, and the exit code is non-zero on any failed check unless --soft-fail is set. Suppress with a checkov:skip=CKV_AWS_20:reason comment inside the resource, a checkov.io/skip annotation in Kubernetes, --skip-check with wildcards, or a .checkov.baseline file. Severities, SCA (sca_package, sca_image) and enforcement rules come from Prisma Cloud and only work with --bc-api-key. Catches: Public buckets, open security groups and missing encryption in Terraform, CloudFormation, ARM, Bicep and Serverless; Privileged pods, hostPath mounts and missing limits in Kubernetes, Helm and Kustomize; Dockerfiles with no non-root USER or no HEALTHCHECK; Untrusted github.event fields expanded inside workflow run: steps; ACTIONS_ALLOW_UNSECURE_COMMANDS and write-all workflow permissions; Org 2FA, SSO and branch protection gaps read from the GitHub API; Hardcoded credentials in IaC and, with a flag, in any file What leaves the machine: By default it calls Prisma Cloud's API to add guideline links to results (the README says no identifiable customer data is sent); --skip-download turns that off. With --bc-api-key, results and SCA package data go to Prisma Cloud, and the github_configuration framework reads settings from the GitHub API with your token. Limits: Without a Prisma Cloud API key findings have no severity, so you cannot gate CI on HIGH and above; you gate on individual checks instead.; Workflow coverage is thin: eight checks, the injection check is a regex over run: text, and nothing flags unpinned actions, pull_request_target or cache use. Run zizmor for workflows.; The default rule set is large and noisy on real Terraform. Expect to spend time on --skip-check lists, inline skips or a baseline.; The official action runs ghcr.io/bridgecrewio/checkov by version tag, not by digest, so pinning the action SHA does not pin the image. Run: pip3 install checkov checkov -d . --skip-download checkov -d . --framework github_actions --compact Links: CLI command reference (https://www.checkov.io/2.Basics/CLI%20Command%20Reference.html) | GitHub Actions policy index (https://www.checkov.io/5.Policy%20Index/github_actions.html) | YAML custom policies (https://www.checkov.io/3.Custom%20Policies/YAML%20Custom%20Policies.html) | checkov-action (https://github.com/bridgecrewio/checkov-action) #### KICS — 3/5 Situational, 2716 stars, Apache-2.0, Open source id: kics | url: /tooling/kics | repo: https://github.com/Checkmarx/kics Also covers: Workflow analysis Latest release: v2.2.0 (2026-09-17) Summary: Runs Rego queries against Terraform, CloudFormation, Kubernetes, Dockerfiles, Ansible, GitHub workflows and other IaC to find misconfigurations. Rating note: A broad, fully open Rego query set, but its own action and Docker images were hijacked twice in 2026 and new versions now ship as source only. How it works: KICS (Keeping Infrastructure as Code Secure) is a Go binary from Checkmarx. It walks local paths and archives, or copies git, S3 and GCS sources to a temp folder first, works out the platform of each file (Terraform, CloudFormation, Kubernetes, Helm, Dockerfile, Docker Compose, Ansible, OpenAPI, Pulumi, Crossplane, Bicep, GitHub workflows and more) and parses it into normalized JSON. Queries are written in Rego and run by an embedded Open Policy Agent engine against that JSON, with each platform's queries running concurrently. Every query is a folder with query.rego, metadata.json (id, severity, category, description) and test fixtures, so writing your own means adding a folder and pointing --queries-path at it. The GitHub workflow queries flag untrusted event fields in run: and github-script blocks per trigger, unsecured commands, and uses: refs that are not a 40-character SHA (anything under actions/ is exempt). Secret detection is the one query written in Go. It applies regex rules from regex_rules.json, with Shannon entropy bounds on capture groups and allow rules to drop false positives, and you can swap in your own rules with --secrets-regexes-path. Results are written as JSON by default, or SARIF, HTML, PDF, JUnit, SonarQube, GitLab SAST, CycloneDX, ASFF, CSV and Code Climate. The exit code encodes the highest severity found (60 critical down to 20 info), adjustable with --fail-on and --ignore-on-exit. Inline # kics-scan ignore, ignore-line, ignore-block and disable= comments suppress findings, and kics remediate applies automatic fixes for some queries from a results file. Catches: Public storage, open security groups and missing encryption in Terraform, CloudFormation, ARM, Bicep and Pulumi; Privileged containers and host namespace use in Kubernetes, Helm, Knative and Crossplane; Dockerfile and Docker Compose misconfigurations; Untrusted event fields in workflow run: and github-script blocks; Third-party actions not pinned to a full commit SHA; ACTIONS_ALLOW_UNSECURE_COMMANDS in workflows; Hardcoded passwords and keys in scanned IaC files What leaves the machine: A local scan sends nothing except crash reports to sentry.io, which you turn off by setting DISABLE_CRASH_REPORT to 0 or false. Git, S3 and GCS paths are downloaded before scanning. Limits: On 23 March 2026 (TeamPCP) attackers used stolen credentials to repoint all 35 tags of Checkmarx/kics-github-action to a credential stealer. On 22 April 2026 the checkmarx/kics Docker Hub tags (latest, alpine, debian, v2.1.20) were overwritten with a build that sent scan results to an attacker domain. Pin the action by SHA, pin images by digest, and rotate secrets if you ran it in either window.; The action's Dockerfile builds FROM checkmarx/kics:v2.1.20 by tag and on wolfi-base:latest, so pinning the action SHA does not pin the scanner image.; Since the April incident v2.1.21 and v2.2.0 shipped as source code only. Docker Hub and the prebuilt binaries stop at v2.1.20, so a current version means Homebrew or building from source.; Only four GitHub workflow queries, all regex over the YAML with no dataflow through env: or step outputs. Use zizmor for workflow analysis. Run: brew install kics export KICS_QUERIES_PATH="$(brew --prefix kics)/share/kics/assets/queries" kics scan -p . -o kics-results --report-formats json,sarif Links: Architecture (https://docs.kics.io/latest/architecture/) | Command reference (https://docs.kics.io/latest/commands/) | kics-github-action (https://github.com/Checkmarx/kics-github-action) | Docker on the April 2026 KICS image compromise (https://www.docker.com/blog/trivy-kics-and-the-shape-of-supply-chain-attacks-so-far-in-2026/) #### tfsec — 1/5 Legacy, 7048 stars, MIT, Open source id: tfsec | url: /tooling/tfsec | repo: https://github.com/aquasecurity/tfsec Latest release: v1.28.14 (2025-05-02) OpenSSF Scorecard: 4.7/10 (2026-03-16) Summary: Terraform-only static scanner that evaluates HCL with modules and variables. Its engine now lives in Trivy as trivy config. Rating note: Aqua has moved the engine into Trivy and asks users to switch; tfsec only gets dependency bumps and last shipped in May 2025. How it works: tfsec is a Go CLI for Terraform only. It parses .tf files with HashiCorp's HCL library and evaluates them the way Terraform would: variables and tfvars files, locals, functions such as concat(), and local and remote modules. Remote modules are downloaded unless --no-module-downloads is set. Built-in checks come from Aqua's defsec library and are written in Go, covering AWS, Azure, Google Cloud and others, plus four GitHub provider checks (plaintext action secrets, signed commits in branch protection, vulnerability alerts, private repositories). You can add JSON or YAML checks in files ending _tfchecks.json or _tfchecks.yaml under .tfsec/, built from requiredTypes, requiredLabels and matchSpec actions such as contains, equals and regexMatches, or write Rego policies. Output is lovely (the default terminal view), json, sarif, csv, checkstyle, junit or text. The run fails on findings unless --soft-fail is set, and --minimum-severity filters. A #tfsec:ignore: comment on the line or block suppresses a finding, and adding :exp:YYYY-MM-DD makes the ignore expire. Catches: Public S3 buckets and missing bucket encryption; Security groups open to 0.0.0.0/0; Unencrypted databases, disks and queues across clouds; Plaintext values in github_actions_secret resources; GitHub repositories created public or without vulnerability alerts; Misconfigurations inside remote modules after they are evaluated What leaves the machine: Remote Terraform modules are downloaded during the scan unless you pass --no-module-downloads. --custom-check-url and --config-file-url fetch files from the URL you give. Limits: Deprecated in favour of Trivy. Aqua says engineering attention goes to Trivy, and the last release, v1.28.14 in May 2025, only fixed CVEs in dependencies.; New cloud resources and provider changes are not getting checks. Move to trivy config, which has the same engine plus more languages.; Terraform only. It does not read Kubernetes, Dockerfiles or workflow files.; The migration guide maps commands and flags but not ignore comments or custom JSON/YAML checks, so plan to rewrite those. Run: brew install tfsec tfsec . # replacement with the same engine: trivy config . Links: Documentation (https://aquasecurity.github.io/tfsec/latest/) | tfsec to Trivy migration guide (https://github.com/aquasecurity/tfsec/blob/master/tfsec-to-trivy-migration-guide.md) | Custom checks (https://aquasecurity.github.io/tfsec/latest/guides/configuration/custom-checks/) #### Terrascan — 1/5 Legacy, 5213 stars, Apache-2.0, Open source, ARCHIVED id: terrascan | url: /tooling/terrascan | repo: https://github.com/tenable/terrascan Latest release: v1.19.9 (2024-09-18) Summary: Archived Rego-based scanner for Terraform, CloudFormation, ARM, Kubernetes, Helm, Kustomize and Dockerfiles, formerly maintained by Tenable. Rating note: Tenable archived the repository on 20 November 2025 and the last release dates from September 2024. How it works: Terrascan is a Go CLI. On first run (or with terrascan init) it clones its policies from the master branch of github.com/tenable/terrascan into ~/.terrascan, under pkg/policies/opa/rego. IaC providers parse Terraform HCL2, CloudFormation, ARM, Kubernetes YAML or JSON, Helm v3, Kustomize and Dockerfiles into a normalized JSON config per resource type, which you can print with --config-only. Policies are Rego files paired with a JSON rule file (name, severity, category, reference_id) and run in an embedded OPA engine against that config. Results go out through human, JSON, YAML, XML, JUnit XML and SARIF writers. --find-vuln looks up images referenced in the IaC in the registry's own vulnerability scanning (AWS, Azure, GCP, Harbor), --webhook-url posts results to a URL, and terrascan server runs the same scanner as an HTTP API. Catches: Public storage, open security groups and missing encryption in Terraform, CloudFormation and ARM; Privileged pods and missing security contexts in Kubernetes, Helm and Kustomize; Dockerfile misconfigurations; GitHub repository and webhook resources defined with the Terraform GitHub provider; Known CVEs in referenced images, through the registry's scanner What leaves the machine: terrascan init clones the policy repository from GitHub. --find-vuln calls the registry provider's API, --webhook-url posts results, and -r git/s3/gcs/http downloads the remote IaC. Limits: Archived on 20 November 2025. No new policies, parser fixes or dependency updates, and Homebrew has marked its formula deprecated.; Policies are pulled at run time from the archived repository's master branch, so the rule set is frozen.; Migrate to Checkov, KICS or trivy config. Run: brew install terrascan terrascan scan -i terraform -d . Links: Policy format (https://github.com/tenable/terrascan/blob/master/docs/policies.md) | terrascan-action (https://github.com/tenable/terrascan-action) #### Hadolint — 4/5 Recommended, 12466 stars, GPL-3.0, Open source id: hadolint | url: /tooling/hadolint | repo: https://github.com/hadolint/hadolint Latest release: v2.15.1 (2026-07-31) OpenSSF Scorecard: 4.3/10 (2026-10-05) Summary: Lints Dockerfiles against best-practice rules and runs ShellCheck on the shell script in every RUN instruction. Rating note: The standard Dockerfile linter and actively released again, but most rules are about reproducibility and hygiene, so pair it with an image scanner. How it works: Hadolint is written in Haskell. It parses the Dockerfile into an AST with the language-docker library and runs its DL rules over the instruction list. Because rules see the whole file, some track state, such as which USER is set last. The shell code in each RUN instruction is handed to ShellCheck, which hadolint embeds, and those findings come back as SC rules next to the DL ones. Hadolint never builds the image, pulls the base image or looks at what is installed in it. Settings come from .hadolint.yaml (ignored rules, severity overrides, failure-threshold, trustedRegistries for DL3026, a label schema) or from CLI flags. A # hadolint ignore=DL3008,SC2086 comment covers the next instruction and # hadolint global ignore=... covers the file. Output is tty, json, checkstyle, codeclimate, gitlab_codeclimate, gnu, codacy, sonarqube, sarif or junit, and the exit code is non-zero when a finding reaches the failure threshold. Catches: FROM images with no tag or with :latest (DL3006, DL3007); Unpinned package versions in apt-get, apk, pip, npm, gem and go install; Last USER is root, or sudo used in RUN (DL3002, DL3004); ADD used where COPY would do (DL3020); Base images from registries outside your trusted list (DL3026); Credential-looking names in ARG or ENV (DL3064); Shell bugs in RUN lines and pipes without pipefail (ShellCheck, DL4006) What leaves the machine: Nothing. It reads the Dockerfile locally and does not contact registries. Limits: Only sees the Dockerfile text. It cannot tell you about CVEs or secrets baked into the base image or the built layers.; DL3006 and DL3007 accept any explicit tag, and no rule requires pinning FROM to a sha256 digest.; Release gaps have been long (v2.12.0 in 2022, then v2.13 in 2025), so check the rule list against the version you actually run. Run: brew install hadolint hadolint Dockerfile # or without installing: docker run --rm -i hadolint/hadolint < Dockerfile Links: hadolint-action (https://github.com/hadolint/hadolint-action) | Integration guide (https://github.com/hadolint/hadolint/blob/master/docs/INTEGRATION.md) #### Conftest — 4/5 Recommended, 3280 stars, license n/a, Open source id: conftest | url: /tooling/conftest | repo: https://github.com/open-policy-agent/conftest Latest release: v0.71.1 (2026-10-06) OpenSSF Scorecard: 7/10 (2026-10-05) Summary: Tests any structured config file, including workflow YAML, Terraform and Kubernetes manifests, against Rego policies you write. Rating note: Very flexible and actively released, but it ships no rules, so every check is Rego you write and maintain yourself. How it works: Conftest is a Go CLI around the Open Policy Agent Rego engine, maintained under the open-policy-agent GitHub org. It picks a parser by file extension (YAML, JSON, HCL2, Dockerfile, TOML, INI, XML, CUE, Jsonnet, .env, HOCON, CycloneDX, SPDX, CODEOWNERS and others) or by --parser, and turns each file into the Rego input document. HCL is converted as written, so variables and modules are not evaluated. Policies load from policy/ (or --policy). In the main namespace by default, rules named deny, violation (which can return structured data) and warn, optionally with a suffix such as deny_unpinned, produce failures or warnings. An exception rule can exempt matching inputs from named rules. --data adds JSON or YAML data files, and --combine passes all files as one array so a rule can compare files. Output is stdout, json, tap, table, junit, github (workflow annotations), azuredevops or sarif. Failures give a non-zero exit code, and --fail-on-warn makes warnings fail too. conftest verify runs Rego unit tests for your policies, and conftest push and conftest pull share policy bundles through OCI registries, git or HTTPS. For GitHub this means you write the rules: deny pull_request_target triggers, require uses: to end in a 40-character SHA, require a permissions: block, or check CODEOWNERS (parser added in v0.71.0) and github provider Terraform. Catches: Any rule you can write in Rego over YAML, JSON, HCL2, TOML, INI, XML or Dockerfiles; Workflow policies such as no pull_request_target and SHA-pinned uses:; Kubernetes manifests and rendered Helm output (runAsNonRoot, required labels); Terraform plan JSON checks with resolved values; Cross-file checks with --combine; CODEOWNERS coverage rules What leaves the machine: Nothing during conftest test. conftest pull, test --update and plugin installs download from the OCI registry, git or HTTPS location you give. Limits: No built-in policies. Rego has a learning curve, and untested policies quietly pass everything, so write conftest verify tests.; It checks files as parsed. Terraform variables, modules and expressions are not resolved unless you test terraform show -json plan output.; Policies fetched with --update or conftest pull change whenever the source changes. Pin an OCI digest or git ref.; There is no official GitHub Action; run the binary or the Docker image yourself. Run: brew install conftest conftest test --policy policy .github/workflows/ conftest verify --policy policy Links: Documentation (https://www.conftest.dev/) | Options and output formats (https://www.conftest.dev/options/) | Sharing policies (https://www.conftest.dev/sharing/) ### Org and repo posture Check and enforce GitHub settings across an organization: branch protection, admins, 2FA, Actions policy, stale access. #### OpenSSF Scorecard — 4/5 Recommended, 5747 stars, Apache-2.0, Open source id: scorecard | url: /tooling/scorecard | repo: https://github.com/ossf/scorecard Latest release: v5.5.0 (2026-04-23) OpenSSF Scorecard: 8.3/10 (2026-10-10) Summary: Scores a repository's security practices 0 to 10 from about 18 automated checks. Rating note: The standard way to judge a dependency's security hygiene; some checks are heuristics, so read the details, not just the score. How it works: Scorecard reads a repository through the GitHub API and a clone and runs about 18 checks: branch protection, code review, dangerous workflow patterns, token permissions, pinned dependencies, signed releases, SAST, fuzzing, maintenance activity, known vulnerabilities (via OSV) and more. Each check scores 0 to 10 and the overall score is a risk-weighted average. OpenSSF scans over a million popular repositories weekly and publishes the results through api.securityscorecards.dev and the scorecard.dev viewer, which is where this page's Scorecard column comes from. ossf/scorecard-action runs it on your own repository and uploads SARIF to code scanning. Catches: Workflows with dangerous triggers or write-all tokens; Unpinned actions and dependencies; Missing branch protection or code review; Unmaintained projects you depend on What leaves the machine: Calls the GitHub API with your token and clones the repository. scorecard-action can publish your results to the public dataset. Limits: Some checks are heuristics, for example detecting SAST tools or fuzzing by name.; A high score says the project protects itself well, not that its code is free of bugs. Run: brew install scorecard GITHUB_AUTH_TOKEN=... scorecard --repo=github.com/ossf/scorecard Links: scorecard.dev (https://scorecard.dev) | Check documentation (https://github.com/ossf/scorecard/blob/main/docs/checks.md) | scorecard-action (https://github.com/ossf/scorecard-action) ### Reference PR security pipeline 1. Release-age cooldown [shell] fails on: A new or changed dependency version published fewer than N days ago. Malicious releases are usually reported and pulled quickly, so waiting a few days before adopting a new version skips most of them. Renovate (minimumReleaseAge), Dependabot (cooldown) and pnpm (minimumReleaseAge) can do this for you. 2. Version pinning [shell] fails on: Wildcard ranges or git dependencies without a commit (advisory at first). Checks the declared ranges in package.json and the lockfile, without installing anything. 3. Known vulnerabilities [npm-audit, osv-scanner] fails on: Advisories at or above your chosen severity. npm audit asks the registry. OSV-Scanner reads the lockfile and asks OSV.dev. They use overlapping but different data, so running both catches more. 4. Reachability [osv-scanner] fails on: Nothing on its own. It only suppresses findings. Drops findings for packages that are not in the artifact you deploy, such as dev-only tooling, and records each decision as an OpenVEX statement. 5. Workflow analysis [zizmor, actionlint] fails on: Findings in .github/workflows and action.yml. Template injection, pull_request_target misuse, unpinned actions, credential persistence. Run offline in PRs and keep online audits for a scheduled job. 6. Code scanning [semgrep, codeql] fails on: Rule matches in changed code. Semgrep with the p/ci ruleset and metrics turned off is a fast default. CodeQL goes deeper where you can run it. 7. Secrets [gitleaks] fails on: Any match not in the baseline or ignore file. Check out with fetch-depth: 0 so the scan sees every commit in the PR, not only the last one. Redact matches in logs. 8. Malicious packages [socket-cli] fails on: Install scripts, network access, obfuscation or known malware in new dependencies. Catches what has no CVE yet. Socket needs an API key, so make the step skip with a warning when the secret is missing (it is on fork PRs). 9. IaC and pipeline [checkov, poutine] fails on: Misconfigured Terraform, Dockerfiles and build pipelines. Opt-in per repository. Only worth the minutes where there is IaC or a complex release pipeline. 10. Gate [shell] fails on: Any blocking stage above. Every scanner runs with continue-on-error so one failure doesn't hide the others. A PR comment summarizes them, then a single last step fails the check. --- ## State of GitHub security — supporting stats Secrets detected on public GitHub, millions/year: - 2023: 18 - 2024: 23.77 - 2025: 28.65 - 2026: 37.24 (projected) - 2027: 48.41 (projected) - 2028: 62.93 (projected) Non-human-to-human identity ratio (N:1): - 2023: 82:1 - 2024: 92:1 - 2025: 144:1 - 2026: 195:1 (projected) - 2027: 260:1 (projected) Average global data breach cost (USD millions): - 2024: $4.45M - 2025: $4.88M - 2026: $5.25M (projected) External tools referenced: - Zero Day Clock (https://zerodayclock.com/): A running public counter of days since the last disclosed zero-day, tracking the pace of the broader exploit landscape.