Secrets, Environments, Variables, Redaction, and Credential Hygiene: Configuration, Design Patterns, and Trade-Offs
Secret hygiene improves most when architecture prevents unnecessary
secrets from existing or flowing broadly. This lesson compares
secret versus configuration, repository/environment/organization
scope, long-lived credentials versus OIDC, and explicit named secret
contracts versus secrets: inherit. The goal is to
reduce both credential count and the number of components trusted
with each credential.
Learning objectives
-
Select secret, variable,
GITHUB_TOKEN, or OIDC based on sensitivity, resource boundary, and lifetime. - Choose repository, organization, or environment scope based on ownership and blast radius rather than convenience.
- Separate build/test jobs from credential-bearing deployment/mutation jobs and place approval where the side effect begins.
-
Explain why explicit named secret passing is safer than casual
secrets: inheritacross reusable workflow boundaries. - Document plan/visibility prerequisites and a free-compatible simulation for environment/organization features.
1. Minimize both secret lifetime and secret audience
Least privilege applies to stored credentials too. A credential should exist only if another identity mechanism cannot satisfy the use case, should be scoped to the smallest resource set, should be available only to jobs that need it, and should live only as long as required.
A useful design question is not “where can I store this token?” but “why does this job need a long-lived token at all?”
2. Identity/configuration decision table
| Need | Preferred mechanism | Reason |
|---|---|---|
| Non-sensitive endpoint, region, feature flag | vars |
Designed for visible configuration; no false secrecy. |
| Same-repository GitHub API mutation | GITHUB_TOKEN with narrow permissions |
Ephemeral repository-scoped capability; no stored PAT required. |
| Cross-repository/org GitHub automation | GitHub App where appropriate | Installation-scoped, independently managed identity; avoids user-bound PAT lifecycle. |
| Cloud deployment where provider supports GitHub OIDC | OIDC federation | Short-lived provider credential; no long-lived cloud key stored in GitHub. |
| External service with no federation or app mechanism | Narrow Actions secret | Store only the minimum credential and rotate/revoke it deliberately. |
3. Scope follows ownership, not YAML reuse
A repository secret is a good default when one repository owns the integration. An organization secret is appropriate only when one credential legitimately spans multiple approved repositories and has an access policy. An environment secret is appropriate when the credential belongs to a deployment stage and should not exist in ordinary build/test jobs.
Do not move a repository credential to organization scope merely because several workflow files duplicate its name. First ask whether those repositories should truly share one credential. Separate credentials often improve revocation blast radius and attribution.
4. Build jobs and credential-bearing jobs should be different security zones
permissions: {}
jobs:
build_and_test:
runs-on: ubuntu-24.04
steps:
- run: echo 'Build/test with no deployment credential.'
deploy_gate:
needs: build_and_test
runs-on: ubuntu-24.04
environment: staging
env:
DEPLOY_CREDENTIAL: ${{ secrets.STAGING_DEPLOY_CREDENTIAL }}
steps:
- name: Use credential only after environment eligibility
run: |
set -euo pipefail
test -n "$DEPLOY_CREDENTIAL"
echo 'credential_available=true; external deployment omitted in this course chapter'
The example deliberately does not deploy anything. Its purpose is to show that credential availability begins at the side-effect boundary, not at workflow start. In production, deploy the exact verified artifact from CI rather than rebuilding inside the privileged job.
5. Long-lived cloud secret versus OIDC
| Stored cloud key | OIDC federation |
|---|---|
| Credential must be created, copied into GitHub, rotated in two places, and revoked after compromise. | Provider trusts constrained GitHub identity claims and issues a short-lived credential per eligible job. |
| Leak remains useful until expiry/revocation. | Short lifetime reduces post-run usefulness; provider policy still controls authorization. |
| Simple when provider has no federation support. | Preferred when provider supports it and trust policy can be tightly constrained. |
OIDC does not mean “no security configuration.” The provider trust policy becomes the authorization layer and must constrain repository/workflow/ref/environment/audience claims. Chapter 20 owns the implementation details.
6. Reusable workflows are credential contracts
Secrets are not automatically passed to reusable workflows. A caller
can pass explicitly named secrets or, where supported, use
secrets: inherit to make all secrets available to the
directly called workflow. The latter is convenient but broadens the
callee's credential audience.
jobs:
call_release_workflow:
uses: ./.github/workflows/release.yml
secrets:
registry_token: ${{ secrets.LAB_REGISTRY_TOKEN }}
Prefer named contracts for production boundaries: the callee
documents exactly which credential it requires. Treat
secrets: inherit as an explicit trust decision, not a
default shortcut. Nested workflows do not magically receive every
upstream secret; secrets must cross each call boundary deliberately.
7. Plan and repository visibility are part of the design
Current GitHub environments/environment secrets are available to public repositories on current plans. Environment secrets for private/internal repositories require GitHub Pro, Team, or Enterprise, and some protection features have additional visibility/plan restrictions. Organization-level secrets/variables are not available to private repositories on GitHub Free.
A course design must therefore provide a public disposable or faithful simulation path. A production design must record the repository visibility and plan assumptions rather than silently relying on a UI that another team cannot use.
8. Rotation and revocation need ownership metadata
For every long-lived secret, maintain an owner, source system, scope, purpose, rotation cadence, emergency revocation procedure, and list of consuming workflows. Use separate credentials for unrelated systems so one rotation does not become an organization-wide outage.
Do not expose secret-derived hashes in logs just to “prove” rotation. Prefer provider audit records, successful narrow authentication, version metadata that is explicitly non-secret, and a fresh run whose start falls after the rotation boundary.
9. Worked scenario: choose the smallest credential architecture
A repository builds a container, publishes it to a registry, then deploys to cloud infrastructure. A weak design gives one long-lived cloud administrator key to every job. A stronger design separates:
- build/test job: no deployment credential;
- registry publish job: registry-scoped identity only;
- deployment job: protected environment plus OIDC to the cloud provider;
-
GitHub issue/status mutation: job-local
GITHUB_TOKENpermission.
Each side effect receives its own identity and evidence. Compromise of one job does not automatically grant every other authority.
Knowledge check
When should a repository value be a vars entry
rather than a secret?
When it is non-sensitive configuration and is safe to render unmasked in logs/UI.
Why can an environment secret be better than a workflow-wide repository secret for deployment?
It limits availability to jobs referencing the target environment and can place credential access behind environment protection.
What is the main advantage of OIDC over a stored cloud key?
The provider issues a short-lived credential based on constrained GitHub identity claims, avoiding a long-lived cloud secret stored in GitHub.
Why prefer named reusable-workflow secrets to
secrets: inherit?
Named passing documents and limits the credential interface; inherit broadens the direct callee’s access to all available secrets.
What is wrong with one administrator credential shared by build, publish, and deploy jobs?
It couples unrelated side effects and gives every compromised job a much larger blast radius than necessary.
Official references and version notes
- Secrets concept — current model for repository, organization, and environment Actions secrets and how workflows receive them.
- Secrets reference — current naming, precedence, size/count limits, queue/start read timing, and automatic redaction behavior.
- Using secrets in GitHub Actions — current workflow injection patterns, missing-secret behavior, fork/Dependabot restrictions, masking guidance, CLI setup, and OIDC recommendation.
- Variables concept — current distinction between non-sensitive configuration variables and secrets.
-
Variables reference
— current
varsscope, precedence, limits, and environment-variable semantics. - Deployments and environments — current environment secret availability, protection-rule timing, plan/visibility boundaries, and self-hosted-runner warning.
- Events that trigger workflows — current fork pull-request secret restrictions and read-only token behavior.
- Dependabot on GitHub Actions — current Dependabot-specific token and secret restrictions.
-
Workflow syntax
— current
secrets,environment, reusable-workflow secret passing, and conditional semantics. -
Workflow commands
— current
::add-mask::behavior and environment-file commands. - Secure use reference — current guidance for rotating/removing secrets, approval gates, untrusted input, and credential hygiene.
- OpenID Connect — current model for exchanging GitHub OIDC identity for short-lived provider credentials instead of storing long-lived cloud secrets.
-
Reuse workflows
— current explicit secret passing,
secrets: inherit, and directly-called workflow boundaries.
Version-sensitive secrets/environment behavior was rechecked
against current primary GitHub documentation on
2026-09-09. Mandatory executable workflows use a
learner-owned disposable repository, ubuntu-24.04,
built-in Bash/Python only, synthetic credentials, and
permissions: {}; no third-party action, PAT, real API
key, cloud account, package publication, deployment, or
self-hosted runner is required. At verification time, Actions
secrets can exist at organization, repository, or environment
scope; a lower scope wins on a same-name collision (environment
over repository over organization). Organization/repository
secrets are read when a workflow run is queued, while environment
secrets are read when the job referencing that environment starts.
A secret that is not set resolves to an empty string; normal
Actions secrets are not passed to fork-origin pull-request
workflows (except the special automatic
GITHUB_TOKEN behavior) and are not available to
Dependabot-triggered runs. Secrets are limited to 48 KB; current
counts are up to 1,000 organization, 100 repository, and 100
environment secrets. GitHub log redaction is a safety layer, not a
confidentiality proof; transformed/structured values, artifacts,
caches, process arguments, and third-party systems remain separate
leakage surfaces. For cloud providers that support OIDC, the
course recommends short-lived federation instead of long-lived
provider keys; the full OIDC trust-policy implementation is
deferred to Chapter 20. OIDC is presented here as an architecture
choice only; provider-specific trust policies and
id-token: write implementation are intentionally
deferred to Chapter 20.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.