Chapter 07Lesson 03~150 minutes

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.

Design patternsvars vs secretsOIDCsecrets: inheritEnvironment gates

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: inherit across 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.

Avoid convenience promotion.

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:

  1. build/test job: no deployment credential;
  2. registry publish job: registry-scoped identity only;
  3. deployment job: protected environment plus OIDC to the cloud provider;
  4. GitHub issue/status mutation: job-local GITHUB_TOKEN permission.

Each side effect receives its own identity and evidence. Compromise of one job does not automatically grant every other authority.

Next lesson

Failure diagnosis and production credential hygiene

Diagnose why a secret is missing or leaking before broadening scope, changing triggers, or adding another credential.

Knowledge check

When should a repository value be a vars entry rather than a secret?

Why can an environment secret be better than a workflow-wide repository secret for deployment?

What is the main advantage of OIDC over a stored cloud key?

Why prefer named reusable-workflow secrets to secrets: inherit?

What is wrong with one administrator credential shared by build, publish, and deploy jobs?

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 vars scope, 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 and compatibility note

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.