Secrets, Configuration Variables, GITHUB_TOKEN, Fine-Grained Permissions, and OIDC: Configuration, Design Choices, and Tradeoffs
Secure configuration is a design problem, not a menu choice. This lesson turns the mechanics from Lesson 2 into decision criteria: where each value belongs, which GitHub identity should act, where permissions should be declared, and when federation is safer than a long-lived cloud credential.
Learning objectives
- Choose secret, variable, or repository file based on confidentiality, auditability, reviewability, and runtime ownership.
-
Choose
GITHUB_TOKEN, GitHub App installation token, or fine-grained PAT based on identity and resource boundary. - Use job-level permissions to narrow authority below a workflow-level baseline.
- Compare long-lived cloud secrets with OIDC federation and identify the controls the cloud provider must enforce.
- Apply plan, repository-visibility, and deployment boundaries without making a paid feature mandatory.
Availability: The decision framework is
GitHub.com-first and free-compatible. Repository-local
secrets/variables and GITHUB_TOKEN do not need a paid
plan. Organization-wide secret/variable sharing and
private-repository environment behavior can depend on plan/policy.
OIDC requires a compatible external verifier for a live exchange, so
the mandatory exercises remain provider-neutral.
1. Secret versus variable versus repository file
| Question | Repository file | Configuration variable | Actions secret |
|---|---|---|---|
| Should reviewers see the value? | Yes; that is a benefit. | Yes; it is not confidential. | No. |
| Should a change require a commit/PR? | Usually yes. | No; hosted config can change independently. | No; credential lifecycle should be independent of source. |
| Should the value be visible in logs/UI/API? | Yes, unless the file itself contains sensitive data—which it should not. | Potentially yes. | No; avoid printing and rely on secure use, not masking. |
| Good example | Supported test matrix / policy fixture. | Deployment region, feature mode, harmless endpoint name. | API token, signing key, webhook secret. |
| Bad example | Cloud private key committed to repo. | Password stored as a variable. | Public compiler version hidden in secrets. |
Configuration drift is possible whenever a hosted variable can change without a commit. For critical non-secret policy inputs, decide whether repository review is more valuable than operator flexibility.
2. Choose the GitHub identity that actually owns the automation
| Mechanism | Identity/lifetime | Reach | Use it when |
|---|---|---|---|
GITHUB_TOKEN |
Per-job repository GitHub App installation token | Current workflow repository, constrained by policy/event/permissions. | The workflow needs GitHub operations in its own repository. |
| GitHub App installation token | App installation; short-lived tokens, durable app identity | Installed repositories/org resources allowed by App permissions. | A bot/integration must outlive individuals or operate across several repositories. |
| Fine-grained PAT | Named user; explicit repositories/permissions/expiration | Resources that user may access and the token selects. | Personal automation or a short-lived compatibility gap where an App is not justified. |
Do not choose a PAT merely because it “works everywhere.” Its human ownership and often-longer lifecycle are operational liabilities. Conversely, do not build an App for a five-minute personal API experiment if a narrow, expiring fine-grained PAT is appropriate.
3. Workflow-level versus job-level permissions
A workflow-level policy is convenient when every job needs the same GitHub authority. Job-level permissions are stronger isolation when only one job performs a write. A useful pattern is a deny-by-default workflow plus explicit job grants:
permissions: {}
jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
publish-report:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
The test job cannot inherit the write authority simply because another job has it. That matters when a build executes complex toolchains or third-party code but only a narrow reporting step needs to mutate GitHub state.
4. Long-lived cloud secret versus OIDC federation
| Dimension | Stored cloud key | OIDC federation |
|---|---|---|
| GitHub storage | A long-lived credential exists as a secret. | No long-lived provider credential needs to be stored in GitHub. |
| Runtime credential | Usually the same key until rotation. | Provider issues a short-lived credential after validating GitHub's JWT. |
| Blast radius | Depends on static key permissions and exposure duration. | Bound by token lifetime plus trust-policy claims and provider role permissions. |
| Failure mode | Leak/forgotten rotation/secret copied across repos. | Over-broad audience/subject/claim trust can authorize unintended workloads. |
| Operational cost | Simple initially; rotation/revocation burden later. | More initial provider policy work; stronger workload identity when configured narrowly. |
OIDC does not eliminate authorization policy. It moves the credential source from “stored key” to “verified workload assertion.” A permissive trust policy can still be dangerous.
5. Design a trust policy around immutable repository identity
For a newly created August 2026 GitHub.com repository, start from its actual immutable subject format—not a copied historical name-only subject. The default subject uses one context such as branch or environment. The following provider-neutral policy fixture intentionally shows multiple checks as policy intent; map them to the capabilities of your real provider.
{
"issuer": "https://token.actions.githubusercontent.com",
"audiences": ["https://cloud.example.invalid"],
"subject": "repo:octo-org@123456/octo-repo@456789:environment:production",
"required_claims": {
"repository_owner_id": "123456",
"repository_id": "456789",
"ref": "refs/heads/main",
"environment": "production",
"workflow_ref": "octo-org/octo-repo/.github/workflows/deploy.yml@refs/heads/main"
}
}
If the provider can bind only sub and aud,
use the strongest supported subject and enforce the remaining
branch/environment/workflow restrictions inside GitHub through
environments, branch protections, and reusable-workflow governance.
Never pretend a provider enforces claims it ignores.
6. Git checkout credentials are another persistence boundary
If a workflow checks out source but does not need authenticated Git
commands afterward, do not leave credentials available longer than
necessary. Current actions/checkout supports
persist-credentials: false. This is separate from
GITHUB_TOKEN issuance: the token may exist for the job,
while checkout decides whether Git credential configuration is
persisted for later Git operations.
- uses: actions/checkout@FULL_REVIEWED_COMMIT_SHA
with:
persist-credentials: false
Pin the action to a reviewed full commit SHA according to your supply-chain policy. Chapter 18 established why a mutable action tag and an immutable commit reference have different update and compromise tradeoffs.
7. Plan and deployment boundaries
-
Personal public lab: use repository
secret/variable + explicit
GITHUB_TOKENpermissions. This is the mandatory path. - Private environment workflows: verify the current plan and environment-feature availability before assuming reviewer/wait-timer behavior from Chapter 19.
- Organization configuration: verify organization policy and repository-access selection. Do not assume a Free private repository can consume every organization-scoped value.
- GHES: do not copy GitHub.com's post–July 15, 2026 immutable default subject rollout; verify the server version and OIDC documentation.
8. Worked production decision
A release pipeline needs to read repository metadata, publish one GitHub deployment status, and deploy to a cloud production account. The build job executes a large dependency tree; the deploy job is short.
| Decision | Choice | Why |
|---|---|---|
| Build job token | contents: read only |
Reduces impact if build tooling is compromised. |
| Deployment status | Narrow deployments: write on deploy job |
Only the job that records deployment status receives the mutation permission. |
| Cloud auth | OIDC with production environment subject | No long-lived cloud key; trust binds workload context. |
| Non-secret target metadata | Repository file or configuration variable | Choose reviewable file for policy; variable for operator-tunable harmless value. |
| Cross-repo governance bot | GitHub App | Durable identity independent of a maintainer's personal token. |
This design optimizes maintainability (explicit contracts), security (least privilege and no static cloud key), governance (reviewed workflow/environment), reliability (short-lived credentials tied to the run), compatibility (provider-neutral interface), and cost (no paid cloud required merely to learn the model).
Knowledge checks
A value is harmless but operators must change it without a commit. Which class is the default fit?
A configuration variable, because it is non-sensitive hosted configuration. If the value is policy-critical, consider a repository file so changes are code-reviewed.
Why can a GitHub App be safer operationally than a PAT for a long-lived organization bot?
The App has its own durable identity, fine-grained installation permissions, and short-lived installation tokens rather than depending on one human account.
Why put issues: write only on a reporting
job?
A compromised build/test job then cannot write issues. Job-level permissions reduce privilege exposure to the execution unit that needs it.
Does OIDC remove the need for least-privilege cloud IAM?
No. The provider still grants a role or equivalent authority. OIDC changes how the workload authenticates; the provider role must remain narrowly authorized.
Why is persist-credentials: false relevant even
with a short-lived GITHUB_TOKEN?
It avoids leaving Git credentials available to later steps when authenticated Git operations are unnecessary, reducing credential exposure inside the job.
Lesson summary
Choose configuration by sensitivity and audit requirements, choose GitHub identity by ownership and resource boundary, scope token authority to the smallest job, and use OIDC when a provider can safely exchange verified workflow identity for short-lived credentials. The next lesson diagnoses what happens when any of those boundaries are wrong.
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.