Git, CI/CD & GitOpsXLII · CI SecretsSecrets
Environment scopes — repository, environment, and organisation granularity
What you'll learn
- Describe the three scopes a CI secret can have: repository, environment, and organisation
- Explain the environment protection rules: required reviewers, wait timers, branch restrictions
- Map the principle of least privilege to secret scope: the production secret lives in the production environment, not in the repository
- Configure the scoping model for a multi-repository organisation
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
A CI secret has a scope, and the scope determines which jobs can read it. The scoping model is the structural control that turns a CI secret from a shared credential into a least-privilege credential. A production cloud credential in the repository scope is a credential every job in the repository can read, including the PR-triggered build from a fork. The same credential in an environment scope with a required-reviewer rule is a credential only the protected job can read, only after a human has approved the deployment, and only on a branch that the environment accepts.
The three scopes
CI forges partition secrets into three scopes. The scope is set when the secret is created; the scope determines which jobs can read the value.
flowchart TB
subgraph ORG["Organisation scope"]
OA["Org secret A"]
OB["Org secret B"]
end
subgraph REPO["Repository scope"]
RA["Repo secret A"]
RB["Repo secret B"]
end
subgraph ENV["Environment scope"]
EA["Env: production secret"]
EB["Env: staging secret"]
EC["Env: dev secret"]
end
ORG -->|opt-in per repo| REPO
REPO -->|opt-in per workflow| JOB1["Any job\nin the repo"]
ENV -->|opt-in per workflow and protection rules| JOB2["Protected job\non accepted branch\nafter approval"]
The three scopes, with their properties:
- Repository scope. The secret is available to every workflow in the repository. The repository scope is the default; secrets created through the repository’s settings page are repository secrets. A repository secret is a secret every PR-triggered build can read, including builds from forks (subject to the forge’s fork-PR secret policy).
- Environment scope. The secret is available only to jobs that target a named environment. The environment has protection rules: required reviewers, wait timers, branch restrictions. An environment secret is a secret only the gated job can read, only on the gated branch, only after the gate has cleared.
- Organisation scope. The secret is available to every repository in the organisation that opts in. The organisation scope is the broadest; the secret is shared across the team’s repositories. An organisation secret is the right scope for a credential every repo legitimately needs (a shared artifact registry token, a shared notification webhook), not the right scope for a credential one repo needs.
Environment protection rules
An environment is more than a scope; an environment is a gated surface. The protection rules the forge supports turn an environment into a deploy gate:
- Required reviewers. The deploy job pauses at the environment; the job does not run until a designated reviewer has approved the deployment. The reviewer’s approval is an out-of-band signal: the reviewer’s account is not the runner’s account; the reviewer’s decision is logged in the audit trail.
- Wait timer. The deploy job cannot run until a configured delay has elapsed. The delay is a cool-off window: an attacker who triggers a deploy has minutes to be detected before the deploy runs.
- Branch restriction. The environment accepts jobs
only from a configured branch or branch pattern. A
productionenvironment that accepts onlymainis an environment a fork PR cannot deploy to. - Deployment branches (GitLab). The GitLab equivalent of branch restriction, with the same effect: the job runs only on the configured branch.
flowchart LR
PR["Pull request\nto main"] --> CI["CI build\non PR head"]
CI -->|tests pass| MERGE["Merge to main"]
MERGE --> DEPLOY["Deploy workflow\ntargets env: production"]
DEPLOY --> WAIT{"Wait timer\nelapsed?"}
WAIT -->|no| H["Hold"]
WAIT -->|yes| APPROVE{"Required reviewer\napproved?"}
APPROVE -->|no| H
APPROVE -->|yes| BRANCH{"Branch"}
BRANCH -->|no| H
BRANCH -->|yes| RUN["Job runs\nreads env secret"]
The three rules compose. A deploy job that targets the production environment cannot run until the wait timer has elapsed, a reviewer has approved, and the branch is accepted. The deploy job’s blast radius is the production environment’s blast radius, gated by three controls.
Scoping and least privilege
The principle of least privilege applies to secrets the same way it applies to IAM policies: the credential should hold the permissions the workload needs, no more. In the CI secret model, “permissions” decomposes into two dimensions:
- What the credential grants. A scoped IAM role, a per-environment service account, a per-deploy Vault token. The credential’s authority is bounded at the source.
- Who can read the credential. The forge’s scope model determines which jobs can read the value. The scope should be the narrowest scope the workload needs.
A production AWS access key in the repository scope
violates both dimensions: the key may grant broad
permissions (the team did not scope the IAM role), and
the key is readable by every job in the repo (the
repository scope grants access to every job). The same
key in an environment scope, paired with an OIDC role
that grants only s3:PutObject on the deploy bucket,
satisfies both dimensions.
Multi-repository scoping
An organisation with multiple repositories faces a scoping choice: which secrets are repository-scoped, which are environment-scoped, and which are organisation-scoped. The default that minimises blast radius:
- Repository scope. Secrets that are unique to one repository: a per-repo deploy key, a per-repo integration token, a per-repo webhook. The secret is not shared because the credential is not shared.
- Environment scope. Secrets that are unique to one environment: a production cloud credential, a staging database password. The secret is environment-specific because the credential is environment-specific.
- Organisation scope. Secrets that are shared across the team’s repositories: a shared artifact registry token, a shared notification webhook, a shared code- scanning service. The secret is shared because the credential is shared.
A organisation that puts a production credential in the organisation scope has built an organisation-wide production credential. The blast radius of the organisation scope is the blast radius of the organisation.
Production discipline
- Production secrets in production environments. No
production credential in the repository or organisation
scope. The production credential lives in the
productionenvironment, gated by the protection rules. - Required reviewers for deploy environments. The
productionenvironment requires a reviewer who is not the deploy author. The reviewer’s approval is logged in the audit trail. - Branch restrictions on deploy environments. The
productionenvironment accepts onlymain(or the team’s release branch). A fork PR cannot deploy to production. - Wait timer for production deploys. A 5-minute wait
timer on the
productionenvironment is a cool-off window for an attacker who triggered an unauthorised deploy. - Audit secret scopes quarterly. Review the scope of every secret; flag any production-scoped credential whose scope is broader than the workload.
Cross-course references
- Git, CI/CD & GitOps — Part XXXII-03 (Required approvals and status checks) covers the PR-side approval gating that complements environment gating.
- Git, CI/CD & GitOps — Part XLI-06 (Persistence and lateral movement) covers the post-leak blast radius when a broad scope meets a malicious step.
- AWS for Production Sysadmins — Part XXXI (IAM) covers the role-scoped credentials that pair with the environment-scoped secrets.
Quiz
Knowledge check · 4 questions
Q1. Where should the production AWS deploy credential live in a CI secret model?
Q2. An environment-scoped secret is more secure than a repository-scoped secret because the environment scope encrypts the value with a stronger key.
Q3. Name the three scopes a CI secret can have and describe one appropriate use case for each.
Q4. Diagnose the scoping failure and prescribe the environment-scope changes that close the gap.
Team T has one production AWS access key stored as a repository-scoped secret named AWS_DEPLOY. The key is AdministratorAccess on the production account. The deploy workflow targets no environment; it reads the secret from the repository scope and runs on every push to main. A teammate opens a PR from a fork that adds a step which reads $AWS_DEPLOY and POSTs it to an attacker-controlled webhook. The workflow runs on the PR head; the secret leaks.
Passing score: 75%. Answers are checked in this browser.