Secrets, Configuration Variables, GITHUB_TOKEN, Fine-Grained Permissions, and OIDC: Concepts, Architecture, and Mental Model
Chapter 19 governed where an artifact may be deployed. Chapter 20 governs the identities and configuration that make those workflows powerful: which values are public configuration, which are confidential, which token acts on GitHub, and when a cloud should trust a short-lived identity instead of a stored key.
Learning objectives
- Classify repository data, configuration variables, environment variables, and Actions secrets by sensitivity, scope, storage, and log behavior.
- Explain how repository, environment, and organization scopes affect secret/variable availability and precedence.
-
Trace the lifecycle and repository boundary of
GITHUB_TOKEN, then reason about workflow-level and job-levelpermissions. -
Choose between
GITHUB_TOKEN, a GitHub App installation token, and a fine-grained personal access token when GitHub automation crosses resource boundaries. -
Explain OIDC issuance,
aud/subclaims, cloud trust policy, and the post–July 15, 2026 immutable default subject format.
Availability: The mandatory path targets GitHub.com
and a disposable public personal repository. Repository
secrets/variables and GITHUB_TOKEN are available
without a paid plan. Environment-scoped configuration depends on the
environment features from Chapter 19. Organization-wide sharing is
optional and policy/plan dependent. OIDC itself does not require
buying a cloud service; this chapter uses a provider-neutral
trust-policy simulation.
1. The practical problem: configuration and authority look similar in YAML
A workflow often contains four visually similar substitutions: a normal repository value such as a test fixture path, a configurable deployment region, a confidential credential, and an authorization token. Treating them as interchangeable creates two opposite failures. Teams either hide harmless configuration in secrets—making operations hard to inspect—or expose authority as ordinary data—making compromise much easier.
The safer question is not “where can I put this string?” It is what power does the value convey, who should be able to read or change it, when should it exist, and what evidence proves that the workflow received only the authority it needed?
2. Four data classes before credentials
| Data class | Example | Visibility/lifecycle | Use when |
|---|---|---|---|
| Repository file | config/test-policy.json |
Versioned, reviewable, readable to repository readers. | The value is not secret and should change through code review. |
| Configuration variable | vars.CH20_MODE |
Hosted configuration; not masked; repository/environment/organization scope. | The value is operational configuration and may change independently of source. |
| Environment variable | env.MODE / shell variable |
Exists in workflow/job/step process context; may originate from files, variables, or secrets. | A process needs a value at execution time; this is a transport mechanism, not a secrecy class. |
| Actions secret | secrets.CH20_DEMO_SECRET |
Encrypted hosted value; plaintext is not returned by listing APIs/CLI; log redaction is best-effort. | Disclosure would matter: token, key, password, webhook secret, or synthetic secret used to teach the boundary. |
A configuration variable is deliberately not secret: GitHub may show
it in UI/API and a workflow may print it. An environment variable is
just a process-level name/value; putting a secret into
env does not make the value non-secret, and putting
public text into secrets does not make it sensitive.
3. Scope answers “which automation receives the value?”
| Scope | Typical owner | Availability rule | Important boundary |
|---|---|---|---|
| Repository | Repository administrator / appropriately permitted collaborator | Workflows in that repository. | Broadest repository-local choice; anyone able to modify trusted workflow code may be able to cause the workflow to use it. |
| Environment | Repository environment administrator/policy | Only jobs that reference that environment, after applicable environment gates. | Useful when production authority should appear only after the production gate is reached. |
| Organization | Organization owner / delegated role | Selected repositories according to org policy; plan/visibility restrictions can apply. | Centralizes reuse, but increases blast radius if access policy is too broad. |
When the same secret or variable name is defined at multiple supported scopes, the narrower scope takes precedence: environment over repository over organization. Reason from the job's effective scope rather than assuming the organization value wins. Environment-scoped values are intentionally tied to a job's environment transition. Also remember timing: hosted configuration can be evaluated at different stages, so do not design a policy that depends on a late environment variable changing an expression that GitHub already evaluated earlier.
4. GITHUB_TOKEN: identity created for the job
When a workflow job starts, GitHub creates a unique
GITHUB_TOKEN. It is a GitHub App installation access
token for the repository containing the workflow. It is not your
personal login token, and it is not a cross-organization master key.
The token expires with the job (subject to the platform's effective
maximum lifetime) and its permissions are the intersection of
repository/org policy, workflow/job permissions, and
event-specific restrictions.
That means authentication and authorization remain separate. A
request may prove “this is the workflow's GitHub App token” yet
still receive 403 because issues: write or
another permission was not granted.
permissions: {}
jobs:
inspect:
runs-on: ubuntu-latest
permissions:
contents: read
issues: read
Setting an explicit permission set also makes absence meaningful: configurable permissions not granted are effectively unavailable to that job. This is much easier to audit than relying on repository defaults.
5. The event can reduce what a token or secret may do
Trust is event-dependent. A pull request from a fork contains code
and metadata controlled outside the base repository. GitHub
therefore withholds Actions secrets from ordinary fork pull-request
workflows and restricts the token unless administrators
intentionally choose riskier settings. Dependabot-triggered events
are similarly treated as untrusted for Actions secrets: the default
GITHUB_TOKEN is read-only and Dependabot secrets are a
separate secret class.
Do not “fix” these failures by switching to a more privileged event and immediately executing untrusted code. The correct design is to separate untrusted validation from privileged follow-up, keep write authority out of code paths the contributor controls, and make the privilege transition explicit.
6. When GITHUB_TOKEN cannot reach the resource
| Credential | Identity | Best fit | Primary risk |
|---|---|---|---|
GITHUB_TOKEN |
Repository-installed GitHub App for current workflow job | Most same-repository Actions operations. | Granting more permissions than the job needs. |
| GitHub App installation token | App installation, independent of a human account | Long-lived organization automation, multi-repository integrations, policy-driven bots. | App private-key/install permissions must be governed; installation token still needs least privilege. |
| Fine-grained PAT | A specific user, constrained by selected resources/permissions/expiration | Short-lived user-context scripts or API gaps where an App is disproportionate. | Automation lifecycle depends on the human account; token must expire and remain minimal. |
GitHub currently recommends fine-grained PATs instead of classic PATs where possible, but also recommends a GitHub App for long-lived organization integrations. A token never grants a human permissions they do not already possess.
7. OIDC replaces a stored cloud key with a short-lived assertion
OIDC changes the direction of trust. Instead of storing a cloud
access key in GitHub, a job with id-token: write can
request a signed JWT from GitHub's OIDC issuer. The cloud or
identity provider validates that JWT against a trust policy and, if
the claims match, exchanges it for its own short-lived credential.
flowchart TD E[Repository event] --> J[Actions job] V[vars / env] --> J S[Actions secret] --> J J --> G[GITHUB_TOKEN: GitHub repository API] J --> O[GitHub OIDC issuer] O -->|signed JWT: iss aud sub claims| P[Cloud trust policy] P -->|short-lived provider credential| C[Cloud resource] J --> L[Logs / artifacts]
The GitHub token and the OIDC token solve different trust
problems. GITHUB_TOKEN authorizes GitHub operations.
The OIDC JWT proves workload identity to an external verifier.
id-token: write allows requesting the OIDC token; it
does not itself grant write access to GitHub or to a cloud
account.
Important claims include iss (issuer),
aud (intended audience), sub (subject),
repository_id, repository_owner_id,
ref, environment, and workflow identity
claims. The provider must bind trust tightly enough that an
unintended repository, branch, environment, or reusable workflow
cannot impersonate the intended deployment workload.
8. Current GitHub.com OIDC subjects use immutable repository identity for new repositories
As of this chapter's August 2026 verification, GitHub.com
repositories created after July 15, 2026 use an
immutable default sub that contains the owner and
repository numeric IDs. That prevents a deleted/recycled name from
silently inheriting the same default subject. GitHub Enterprise
Server is excluded from this rollout.
# Historical branch-oriented default:
repo:octo-org/octo-repo:ref:refs/heads/main
# New GitHub.com default for a repository created after 2026-07-15:
repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main
# If the job references an environment, the subject context is the environment:
repo:octo-org@123456/octo-repo@456789:environment:production
Do not invent a subject that concatenates both mutually exclusive default contexts. If the subject is environment-oriented, bind the branch/ref with another claim where the provider supports it or enforce the branch before the environment job. Always inspect the actual token/official subject format before deploying a provider trust policy.
9. Read-only inspection before changing anything
These commands reveal configuration metadata without retrieving secret plaintext. They also show the repository's default workflow-token policy; that repository default is not the same thing as proving the effective permissions of a specific job.
REPO="OWNER/REPO"
gh secret list -R "$REPO" --json name,updatedAt
gh variable list -R "$REPO" --json name,value,updatedAt
gh api -H "X-GitHub-Api-Version: 2026-03-10" "repos/$REPO/actions/permissions/workflow"
For a job's effective authority, combine configuration inspection with a controlled API operation and preserve the resulting HTTP status. That is the pattern used in Lesson 2.
Security boundary: Never print
secrets.GITHUB_TOKEN, OIDC request tokens, PATs, GitHub
App private keys, or a whole secrets/github
context. GitHub masks recognized secret values, but transformations
can defeat redaction. A credential leak response begins with
revocation/rotation, not with editing the old log or Git history.
Knowledge checks
Why is env.MY_VALUE not a security
classification?
env only describes how a process receives a value.
The source may be public configuration, a secret, or generated
runtime data; sensitivity must be governed at the source and use
boundary.
What is GITHUB_TOKEN actually authenticating
as?
A per-job installation access token for the GitHub App that GitHub installs on the workflow repository, constrained to that repository and its effective permissions.
A fork PR cannot read a repository Actions secret. Is that a bug to work around with a privileged event?
No. It is an intentional trust boundary. Separate untrusted validation from privileged work rather than giving untrusted code access to base-repository authority.
What does id-token: write authorize?
It lets the job request an OIDC ID token from GitHub. It does not by itself grant GitHub-content writes or any cloud permission; the external provider decides what a matching token may exchange for.
Why can a 2026 trust policy copied from an older example be wrong?
New GitHub.com repositories created after July 15, 2026 use immutable default subjects containing owner/repository IDs, so a name-only subject copied from an older repository may not match—and is weaker against namespace reuse.
Lesson summary
Workflow security starts by separating data from authority.
Repository files and variables are inspectable configuration;
secrets carry confidential values; GITHUB_TOKEN is a
short-lived repository identity; GitHub Apps and fine-grained PATs
cover different GitHub access cases; and OIDC lets an external
provider trust an attested workflow context without storing a
long-lived cloud key.
Next: Lesson 2 makes these boundaries observable in a disposable repository: one secret, one variable, one allowed metadata read, one intentionally denied write, and a provider-neutral OIDC capability exercise.
Further reading
- GitHub Docs — GITHUB_TOKEN
- GitHub Docs — Workflow syntax: permissions
- GitHub Docs — Secrets
- GitHub Docs — Variables
- GitHub Docs — OpenID Connect reference
- GitHub Docs — Secure use reference
- GitHub Docs — Personal access tokens
- GitHub Docs — Deciding when to build a GitHub App
- GitHub REST API — API versions
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.