Govern GitHub Actions in the Enterprise: Policies, Templates, Secrets and Variables (GH-200)
GitHub Actions governance is the set of enterprise, organization and repository policies that decide where workflows may run, which actions and reusable workflows they may use, what their GITHUB_TOKEN may do and how pull requests from forks are treated — together with the secrets and variables those workflows read at each scope. This lesson covers the GitHub Actions (GH-200) skills for distributing and governing actions and workflows (templates, private and internal sharing, required workflows through rulesets, delegated administration) and for managing encrypted secrets and configuration variables at organization, repository and environment level, in workflows and through the REST API and GitHub CLI.
On this page10 sections
- How GitHub Actions governance is layered
- Allowed actions and reusable workflows policy
- Allow and block patterns, and the SHA-pinning policy
- Default GITHUB_TOKEN permissions and pull request creation
- Fork pull request policies
- Distributing reusable components: templates, sharing and required workflows
- Delegating Actions administration with custom organization roles
- Secrets and variables: choosing, scoping and precedence
- Secret naming rules and limits
- Managing secrets and variables with the REST API and GitHub CLI
- Explain how enterprise, organization and repository Actions policies nest, and choose the enablement and allowed-actions settings that meet a requirement
- Write allow and block patterns for actions and reusable workflows, and predict what the SHA-pinning policy and local-action exemption allow
- Configure default GITHUB_TOKEN permissions, pull request creation and fork pull request policies for public and private repositories
- Choose between workflow templates, Access sharing and ruleset-required workflows to distribute or enforce automation, and delegate Actions administration with custom roles
- Scope secrets and variables at organization, repository and environment level, and predict which value a job receives
- Apply secret naming rules and limits, including the large-secret workaround and the 100-secret window
- Create and update secrets and variables with the REST API (libsodium encryption, repository-access endpoints) and the GitHub CLI
How GitHub Actions governance is layered
GitHub Actions policies are set at three levels — enterprise, organization and repository — and each level can only narrow what the level above allows, never loosen it. An enterprise owner who disables Actions for an organization, restricts the allowed actions or sets a restricted token default has made that choice for everything beneath; the organization and repository settings for it become unavailable or limited to stricter values.
The first policy at every level is enablement: whether workflows may run at all.
| Level | Enablement choices | Effect |
|---|---|---|
| Enterprise | Enable for all organizations · enable for specific organizations · disable | Organizations left off cannot turn Actions on themselves |
| Organization | Enable for all repositories · enable for selected repositories · disable | Repositories left off cannot run any workflow, even a plain run: step in a new file under .github/workflows |
| Repository | Enable or disable, only within what the organization permits | A repository cannot override a parent that disabled it |
Enablement answers where workflows run. It is a different policy from the allowed-actions list (which actions a run may use) and from token permissions (what a run may change). Only enablement stops a workflow from executing. Because the levels nest, turning Actions on somewhere a parent has switched it off is always a change at the parent first: a repository cannot enable what its organization disabled, and an organization cannot enable what its enterprise disabled.
Allowed actions and reusable workflows policy
The allowed-actions policy decides which actions and reusable workflows a workflow may reference. At organization level (the enterprise has the same options with "enterprise" in place of the organization name) the choices are:
| Option | What runs | Watch out for |
|---|---|---|
| Allow all actions and reusable workflows | Anything from any repository | Has no allow/block list at all |
| Allow owner actions and reusable workflows | Only actions stored in the organization's (or enterprise's) own repositories | Also blocks GitHub's own actions such as actions/checkout |
| Allow owner, and select non-owner, actions and reusable workflows | The owner's actions plus the sources you tick or list | The only option with sub-choices and a specified list |
The select option has three independent controls:
- Allow actions created by GitHub — admits actions in the
actionsandgithuborganizations (for exampleactions/setup-node). - Allow Marketplace actions by verified creators — admits actions from publishers GitHub has verified, such as Docker or HashiCorp. This is a broad switch, not a per-action choice.
- Allow or block specified actions and reusable workflows — a comma-separated list of patterns (next section).
Two exemptions matter everywhere: local actions referenced with a ./ path inside the same repository are never restricted by these policies, and the policy governs sources, not reference formats. Because a lower level can never loosen a higher one, a source the enterprise policy excludes can only be admitted by changing the enterprise policy itself.
Allow and block patterns, and the SHA-pinning policy
The specified list accepts one pattern per action or reusable workflow, separated by commas, and it exists only under the "select non-owner" option.
| Pattern | Matches |
|---|---|
OWNER/REPO@ref | An action at one tag, branch or SHA (e.g. octo-org/lint@v2) |
OWNER/REPO/PATH/FILENAME@ref | A reusable workflow file, with its full path (e.g. tools-org/shared-ci/.github/workflows/release.yml@*) |
OWNER/* or @* | * is a wildcard — every repository of an owner, or any reference |
!OWNER/REPO@* | A leading ! turns the entry into a block |
Combining a wildcard with a block entry lets you keep almost everything open while refusing one known-bad action, without listing the hundreds of actions teams already use:
*, !example-org/format-action@*
The * entry keeps every other source allowed and the ! entry refuses every reference of the one action. Since the open "Allow all" option has no list, blocking anything starts by moving to the select option.
Requiring actions pinned to a full-length commit SHA
The SHA-pinning policy makes GitHub refuse any workflow that references an action by a tag or branch instead of a full 40-character commit SHA. Tags and branches can be moved to new code by whoever controls the action's repository; a commit SHA cannot, so the policy closes the "moved tag" supply-chain attack. It is available at enterprise, organization and repository level.
Its scope is narrower than it sounds:
- It applies to actions referenced with
uses:in steps. - Reusable workflows called with
uses:at job level may still be referenced by tag. - Local actions (
./path) are not restricted at all.
Applied to this workflow: the job-level reusable workflow keeps its tag, actions/setup-node@v4 must be rewritten to a 40-character SHA, and the local ./ action is untouched.
jobs:
build:
uses: fabrikam/ci-library/.github/workflows/package.yml@v3
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
- uses: ./.github/actions/format
The policy controls the reference format, not the source: it does not replace the allowed-actions list. Why pinning matters and how to keep pins up to date is covered in the security best-practices lesson of Domain 5.
Default GITHUB_TOKEN permissions and pull request creation
The workflow-permissions setting chooses the default scopes of GITHUB_TOKEN for workflows that do not declare a permissions key.
| Setting | Default token access |
|---|---|
| Read repository contents and packages permissions (restricted) | Read contents and packages (plus metadata); nothing else |
| Read and write permissions (permissive) | Read and write across most scopes |
Three rules decide most scenarios:
- The default only applies when there is no
permissionskey. After a switch to restricted, a workflow without a key can still check out code and pull packages, but a step that pushes a tag fails. - A declared
permissionskey replaces the default completely. Every scope you list gets the access you give it and every scope you omit becomesnone.contents: writelets a release job push tags whatever the default;pull-requests: writealone leavescontentsat none, so a tag push fails. - A restricted enterprise default cannot be loosened. Organizations and repositories cannot choose read and write as their default once the enterprise restricts it. Jobs that need write access declare it in the workflow — which is least-privilege practice anyway.
A separate checkbox, "Allow GitHub Actions to create and approve pull requests", is off by default. While it is off, GITHUB_TOKEN cannot open or approve a pull request even with pull-requests: write: the branch push succeeds and the pull-request API call is refused with a 403.
Fork pull request policies
Fork policies decide whether and how workflows run for pull requests opened from forks, and the options differ for public and private repositories.
Public repositories choose how much approval outside contributors need before workflows run on their pull requests — for example only for first-time contributors who are new to GitHub, for all first-time contributors, or for all external contributors. One trigger is outside this gate: workflows triggered by pull_request_target always run, because they execute from the base branch in the base repository's trusted context. That is exactly why a pull_request_target workflow must never check out and execute the fork's code while secrets or a write token are available.
Private and internal repositories have four separate checkboxes:
| Setting | Effect when enabled |
|---|---|
| Run workflows from fork pull requests | Fork pull requests trigger workflows, with a read-only GITHUB_TOKEN and no secrets |
| Send write tokens to workflows from fork pull requests | The fork run gets a token with write permissions |
| Send secrets and variables to workflows from fork pull requests | The fork run receives repository secrets and variables |
| Require approval for fork pull request workflows | Each fork run waits until someone with write access approves it |
The checkboxes are independent. The two "Send" options are the ones that hand code from a fork a write token or the repository's secrets, so they belong only where every forking contributor is trusted; approval adds a human check before any of it runs.
Distributing reusable components: templates, sharing and required workflows
An organization distributes standard automation in three ways, and they solve different problems: templates suggest a starting file, Access sharing lets other repositories call private code, and rulesets enforce a workflow without touching the repositories.
| Mechanism | What it does | Files needed in each repository? | Enforced? |
|---|---|---|---|
| Workflow template | Offered on the repository's "New workflow" page and copied in | Yes — the copy | No |
| Reusable workflow / shared action | Called at run time with uses: | Yes — a caller | No |
| Ruleset "Require workflows to pass before merging" | Runs a central workflow on pull requests in targeted repositories and blocks merging on failure | No | Yes |
Workflow templates
An organization publishes templates in a repository named .github, inside a workflow-templates directory (not .github/workflows). Each template is a workflow file plus a metadata file with the same base name ending in .properties.json, whose name and description are required (an icon and categories are optional). The placeholder $default-branch is replaced with the target repository's default branch when the workflow is created — the new file contains the literal branch name, such as develop; it is not re-evaluated at run time.
Visibility decides where templates are offered: templates in a public .github repository appear in all repository types; an internal one in internal and private repositories; a private one only in private repositories. With a private or internal .github repository users also need Read access to it. Templates belong to the owning organization — other organizations in the same enterprise do not see them. Authoring templates and reusable workflows in detail is the subject of the Domain 2 lesson on templates and reusable workflows.
Sharing actions and reusable workflows from private and internal repositories
Actions and reusable workflows in a private or internal repository are usable only inside that repository until its Settings > Actions > General > Access option grants more. A private repository can be opened to repositories in the same organization (or, for a user-owned repository, the same user); an internal repository can be opened to repositories in the enterprise. Repositories owned by an enterprise's organizations get a wider choice too: a private repository can also be opened to the whole enterprise (callers must themselves be private), and an internal one can be limited to its own organization (callers must be private or internal). Internal visibility exists only for enterprise accounts, so a GitHub Team organization shares a private repository at organization level. Neither the allowed-actions list nor people's repository roles grant this access, and making the repository public breaks any privacy requirement. The allowed-actions policy still applies on top: sharing makes an action reachable, the policy decides whether it may run.
Required workflows through rulesets
The "Require workflows to pass before merging" rule is configured once in an organization (or enterprise) ruleset. It names a source repository, a workflow file and a ref, and targets repositories by name or property. Its rules:
- It runs on
pull_request,pull_request_targetandmerge_groupwith their default activity types, and ignores filters such asbranches,pathsandtypes— so the check runs on every targeted pull request. Work that should be skipped must be decided inside the jobs. - The source repository's visibility must suit the targets: a public source runs anywhere, an internal one in internal and private repositories, a private one only in private repositories.
- An internal or private source must also allow access from outside its repository in its Actions Access setting, or the workflow never starts.
- Most CodeQL actions (such as
initandanalyze) are blocked in ruleset workflows, even inside a called reusable workflow; onlyupload-sarifis allowed. For organization-wide CodeQL, GitHub recommends default setup plus the "Require code scanning results" rule.
Delegating Actions administration with custom organization roles
Custom organization roles let an owner delegate parts of Actions administration without making someone an organization owner. GitHub provides separate fine-grained permissions for Actions:
| Permission | Covers |
|---|---|
| Manage organization Actions policies | Enablement, allowed actions and patterns, workflow permissions, fork settings |
| Manage organization Actions secrets | Create, update and delete organization secrets and their repository access |
| Manage organization Actions variables | The same for configuration variables |
| Manage organization runners and runner groups | Self-hosted runners and runner groups at organization level |
Build the role from only the permissions the person needs: someone who maintains organization secrets and variables but must not change policies gets those two permissions and nothing else. The Admin role on repositories is the wrong tool — it does not reach organization-level settings and grants far more than needed. Runner and runner-group management itself belongs to the sibling topic on managing runners at scale.
Secrets and variables: choosing, scoping and precedence
Secrets are encrypted values that GitHub redacts from logs; configuration variables are plain-text settings that are not redacted. Anything that grants access — a token, a password, a connection string, a webhook URL with an embedded key — is a secret; a region name, a feature flag or a host name is a variable. Workflows read them through different contexts:
steps:
- run: ./deploy.sh --region "${{ vars.REGION }}"
env:
API_KEY: ${{ secrets.API_KEY }}
An undefined variable evaluates to an empty string rather than failing the run, so a condition such as if: ${{ vars.RUN_LOAD_TESTS == 'yes' }} is false when no such variable exists — handy for optional feature flags. An unset or unreachable secret is also an empty string. Secrets cannot be referenced directly in if: conditions (map them to an environment variable first; see the Domain 1 workflow-structure lesson).
Actions never read secrets on their own: the calling workflow passes a value in through a with: input or an env: variable, and a composite action cannot use the secrets context directly. Reusable workflows receive secrets through workflow_call secrets or secrets: inherit (covered in the Domain 2 reusable-workflows lesson).
Scopes
| Scope | Who receives it |
|---|---|
| Organization | Repositories chosen by the access policy: all repositories, private repositories (private and internal), or selected repositories |
| Repository | Every workflow and job in that repository, on any branch |
| Environment | Only jobs that name the environment with environment:, and only after its protection rules pass |
When the same name exists at several levels, the lowest level wins: environment, then repository, then organization — evaluated name by name. For example, if the organization and the repository both define LOG_LEVEL and a qa environment defines only CDN_HOST, a job targeting qa reads LOG_LEVEL from the repository and CDN_HOST from the environment.
On the GitHub Free plan, organization secrets and variables are not accessible to private repositories at all, whatever access policy is chosen; public repositories receive them, private ones get an empty value.
Environment secrets as a gate
Environment secrets are the only scope that protects the value itself. A job receives them only if it has environment: production (a job that merely needs: a job that used the environment gets nothing), and only after the environment's deployment branch policy and required reviewers pass. A repository or organization secret is available to any branch, and an if: github.ref == ... guard can be edited away by the very developer it is meant to stop. Widening who may deploy is therefore done on the environment itself — by changing its deployment branch policy — not by copying the secret to a broader scope.
Secret naming rules and limits
Secret and variable names follow fixed rules, and secrets have size and count limits that explain several "empty value" puzzles.
- Names may contain only alphanumeric characters and underscores — no spaces.
- Names must not start with the
GITHUB_prefix and must not start with a number. - Names are case-insensitive (stored in uppercase) and must be unique at their level.
| Limit | Value |
|---|---|
| Size of one secret | 48 KB |
| Organization secrets | Up to 1,000 |
| Repository secrets / environment secrets | Up to 100 each |
| Organization secrets a repository's workflows can use | The first 100 shared with it, sorted alphabetically by name |
If more than 100 organization secrets are shared with a repository, names late in the alphabet resolve to an empty string. Share fewer secrets with that repository or use repository secrets.
For a value larger than 48 KB — a key file, for example — GitHub documents one workaround: encrypt the file with gpg --symmetric --cipher-algo AES256, commit only the encrypted file, store the passphrase as a secret, and decrypt in a step at run time. GitHub does not redact the decrypted contents, so the workflow must never print them. Moving the value to an environment or organization secret does not help, and a variable would store it in plain text.
Managing secrets and variables with the REST API and GitHub CLI
Secrets are written to the REST API already encrypted: a client fetches the public key of the scope, encrypts the value with libsodium, and sends the ciphertext. Variables are plain text and use ordinary create/update/read calls.
Secrets
GET /repos/{owner}/{repo}/actions/secrets/public-key(or the/orgs/{org}/...or environment equivalent) returns akeyand akey_id. Use the key of the scope you are writing to.- Encrypt the value with libsodium (a sealed box) using that key and Base64-encode the result.
PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}withencrypted_valueandkey_id— 201 when created, 204 when updated. There is no plain-text or POST form, and secret values can never be read back.
Organization secrets add an access policy: visibility is all, private or selected, with selected_repository_ids for selected. The repository list then has its own endpoints:
| Call | Effect |
|---|---|
PUT /orgs/{org}/actions/secrets/{name}/repositories | Replaces the whole list with selected_repository_ids |
PUT /orgs/{org}/actions/secrets/{name}/repositories/{repository_id} | Adds one repository (204), no value needed |
DELETE …/repositories/{repository_id} | Removes one repository |
The single-repository calls return 409 unless the secret's visibility is selected.
Variables
| Action | Request |
|---|---|
| Create | POST /repos/{owner}/{repo}/actions/variables with name and value |
| Update | PATCH /repos/{owner}/{repo}/actions/variables/{name} (204) |
| Read | GET …/actions/variables/{name} returns the plain value |
| Delete | DELETE …/actions/variables/{name} |
An organization variable is created with one POST /orgs/{org}/actions/variables carrying name, value and the required visibility; selected_repository_ids is accepted only with selected. Its repository list uses the same replace-or-add-one endpoints as secrets.
GitHub CLI
gh secret set SENTRY_DSN --repo fabrikam/shop # repository secret
gh secret set SENTRY_DSN --env qa --repo fabrikam/shop # environment secret
gh secret set SENTRY_DSN --org fabrikam \
--visibility selected --repos shop,mobile # org, selected repos
gh variable set LOG_LEVEL --body info --org fabrikam --visibility all
Without --visibility, the CLI gives an organization secret private visibility (private and internal repositories). Managing organization secrets needs the admin:org scope on the CLI's token.
Tip. Expect scenario items that describe an enterprise or organization requirement — who may run workflows, which publishers are acceptable, a compromised action, a moved tag — and ask which enablement, allowed-actions, pattern or pinning setting meets it, often with YAML showing which references comply. Token and fork items describe a failing release job, a 403 when opening a pull request, or contractors working from forks, and ask which setting or permissions key resolves it. Distribution items contrast workflow templates, Access sharing for private and internal repositories and ruleset-required workflows, including visibility rules, ignored filters and why a required workflow never starts. Secret and variable items show values at several scopes and ask which one a job receives, why a value is empty (plan, access policy, the 100-secret window, a missing environment key, naming rules) or where a credential should live, and REST or GitHub CLI items ask for the request or command that creates, updates or shares a value under constraints such as one rotation point or not resending the value.
- Each policy level can only narrow the level above; organizations cannot loosen an enterprise policy, and repositories cannot override their organization.
- Enablement decides where workflows run at all; the allowed-actions policy decides which actions they use; token permissions decide what they may change.
- Owner-only allowed actions also block GitHub's own actions; the select option with "Allow actions created by GitHub" admits the actions and github organizations.
- Allow/block lists exist only under the select option: OWNER/REPO@ref for actions, OWNER/REPO/PATH/FILE@ref for reusable workflows, * as a wildcard, ! to block. Local ./ actions are never restricted.
- SHA pinning applies to actions; reusable workflows may keep tags. A declared permissions key sets every unlisted scope to none.
- Ruleset-required workflows run on pull_request, pull_request_target and merge_group, ignore branch and path filters, and need a source repository whose visibility and Access setting suit the targets.
- The lowest scope wins per name (environment > repository > organization); environment secrets reach only jobs that name the environment after its protection rules pass.
- Secrets are written via REST as libsodium-encrypted values with the scope's key_id; variables use POST to create and PATCH to update, and an unset variable is an empty string.
- Actions receive secrets only through inputs or env passed by the calling workflow; composite actions cannot read the secrets context.
Frequently asked questions
Can an organization owner allow an action that the enterprise policy blocks?
No. Enterprise GitHub Actions policies set the outer limit, and an organization or repository can only choose the same or a stricter setting. If an enterprise allows only enterprise actions, the enterprise owner must switch to the policy that allows selected non-enterprise actions and admit the source there, for example by ticking "Allow actions created by GitHub".
Why does actions/checkout fail when only organization actions are allowed?
The owner-only allowed-actions option admits only actions stored in the organization's (or enterprise's) own repositories, and actions/checkout lives in GitHub's actions organization. Choosing the option that allows selected non-owner actions and ticking "Allow actions created by GitHub" admits the actions and github organizations while still blocking other publishers.
How do I block one compromised action without listing every action my teams use?
Switch to the allowed-actions option that allows selected non-owner actions and reusable workflows, then enter a specified list such as "*, !example-org/format-action@*". The * wildcard keeps every other action allowed and the ! prefix blocks every reference of the one action. The "Allow all actions" option has no list, so it cannot block anything.
Does the SHA-pinning policy apply to reusable workflows and local actions?
No. The GitHub Actions policy that requires actions to be pinned to a full-length commit SHA applies to actions referenced in steps. Reusable workflows called at job level may still use tags, and local actions referenced with a ./ path are never restricted by Actions policies.
Which value wins when a secret or variable exists at organization, repository and environment level?
The lowest level wins for each name: an environment value overrides a repository value, which overrides an organization value. Environment values apply only to jobs that reference the environment with the environment key, so a job without it falls back to the repository or organization value.
Why is an organization secret empty in some repositories?
Common causes are: the repository is not covered by the secret's access policy (all, private or selected repositories); the organization is on GitHub Free, where organization secrets and variables are not available to private repositories; or more than 100 organization secrets are shared with the repository, in which case workflows can use only the first 100 sorted alphabetically by name.
How do you create a GitHub Actions secret with the REST API?
Fetch the public key of the target scope (for a repository, GET /repos/{owner}/{repo}/actions/secrets/public-key), encrypt the value with libsodium using that key, then send PUT to the secret's endpoint with encrypted_value and the returned key_id. GitHub answers 201 when the secret is created and 204 when it is updated; plain-text values are not accepted.
How can a security team require a workflow in every repository without adding files to them?
Use an organization ruleset with the "Require workflows to pass before merging" rule. It names a workflow file in a central source repository and runs it on pull requests in the targeted repositories, blocking merges when it fails. Workflow templates and reusable workflows both need a file in each repository and enforce nothing.
Source
This lesson covers the "Manage GitHub Actions for the enterprise" domain of the official GH-200 exam guide. Vendors revise their guides — check the source for the current version.
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.