Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXCII · Protected EnvironmentsSecrets

Environment secrets and isolation — secrets scoped to a deployment target

Advanced⏱ ~24 mingit

What you'll learn

  • Distinguish repository, environment, and organisation secrets by scope
  • Configure environment-scoped secrets using the GitHub CLI
  • Recognise why a PR build must not read the production environment secret
  • Identify the isolation boundary the environment secret draws between validation and deployment

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

Environment-scoped secrets are the deployment-side counterpart of the protected environment. The protected environment restricts who can deploy; the environment secret restricts which secrets the deploy job receives. Together they draw the isolation boundary between a validation build (which must not see production credentials) and a deployment build (which must). This lesson covers the three scopes of secrets and the boundary each one draws.

Three scopes of secrets

GitHub Actions and GitLab CI both expose secrets at three levels of scope:

  • Repository secrets. Visible to every workflow run on the repository, including PR builds from forks.
  • Environment secrets. Visible only to jobs that target the named environment and have passed the gate.
  • Organisation secrets. Visible to every repository in the organisation, subject to a visibility policy.
flowchart TD
    A["Secret value"] --> B{"Scope"}
    B --> C["Repository"]
    B --> D["Environment"]
    B --> E["Organisation"]
    C --> F["Visible to all jobs"]
    D --> G["Visible only to gated deploy job"]
    E --> H["Visible to org repositories"]
    F --> I["Including PR builds"]
    G --> J["After gate evaluation"]
    H --> K["Subject to visibility policy"]

The scope is the security property. A production credential stored as a repository secret is a credential visible to PR builds from forks - which means a malicious PR can exfiltrate it. A production credential stored as an environment secret is a credential visible only after the gate has been evaluated, which means the PR build that does not target the environment cannot read it.

Configuring environment secrets with the GitHub CLI

The real commands to set and remove environment secrets:

gh secret set AWS_PROD_ROLE \
  --env production \
  --body "$PROD_ROLE_ARN"

gh secret list --env production

The first command writes the value to the environment. The second lists the secret names (not the values - the values are write-only). The PROD_ROLE_ARN shell variable is the value to store; in production it is read from a secret manager at deploy time, never from a developer’s shell history.

gh secret remove AWS_PROD_ROLE --env production

The remove subcommand deletes the secret. There is no soft-delete; the value is gone. A team that needs rotation writes the new value, validates the new credential, and only then removes the old one.

Why repository secrets must not hold production credentials

The temptation is real: a repository secret is one command, one secret, one workflow file. It works. It is also the most common production secret leak in the CI/CD space. The attack path is straightforward:

flowchart LR
    A["Fork PR opened"] --> B["CI runs on PR"]
    B --> C["Workflow has access to all repo secrets"]
    C --> D["Malicious step reads production credential"]
    D --> E["Exfiltrates via curl or workflow log"]

The fork PR runs the team’s CI, including the workflow file. The workflow file has access to every repository secret. A malicious step in the workflow reads the production credential and exfiltrates it. The CI run succeeds; the secret is now in an attacker-controlled log. Environment-scoped secrets break this chain because the fork PR does not target the environment and the environment secrets are not passed to its jobs.

OIDC supersedes long-lived secrets

The disciplined production pattern is to skip environment secrets entirely and use OIDC federation: the deploy job exchanges its platform identity token for a short-lived cloud credential. The cloud credential expires in an hour; the production credential is never stored as a GitHub secret at all. The OIDC trust policy is configured per environment, which is why the protected environment is the right boundary for the trust relationship.

Production discipline

  1. Production credentials live in environment secrets, not repository secrets. The environment scope is the isolation boundary.
  2. Prefer OIDC over long-lived secrets. Short-lived credentials expire by design.
  3. Audit the secret list quarterly. Old credentials accumulate; unused ones are leaked ones.
  4. Never echo a secret value. A secret that appears in a workflow log is a secret that has been leaked.

Cross-course references

  • This course, Part XLIII-04 (OIDC in AWS) covers the OIDC trust policy that supersedes long-lived secrets.
  • This course, Part XXXV-02 (Detection with secret scanning) covers the secret-leak failure mode.
  • This course, Part XCI-04 (Ephemeral per-job credentials) covers the short-lived credential pattern.

Quiz

Knowledge check · 4 questions

  1. Q1. A team stores their production AWS access key as a GitHub Actions *repository* secret. A fork pull request opens; the CI workflow runs; a malicious step in the workflow reads the secret and exfiltrates it via a curl call. What is the root cause?

  2. Q2. Environment-scoped secrets are visible to every workflow run on the repository, including pull request builds from forks.

  3. Q3. Name the three scopes of secrets in GitHub Actions and identify the correct scope for a production cloud credential.

  4. Q4. Diagnose how a production credential was exposed to a fork PR build despite the team believing the secret was scoped correctly.

    A team stores their production AWS access key as a GitHub Actions secret named `PROD_AWS_KEY`. They believe it is environment-scoped because the workflow that reads it declares `environment: production`. A security researcher opens a fork PR. The fork PR's CI workflow does not declare `environment: production`, but a workflow step that runs on the fork PR's CI echoes the env var to the workflow log. The secret appears in the log.

Passing score: 75%. Answers are checked in this browser.