Secrets, Environments, Variables, Redaction, and Credential Hygiene: Guided Hands-On Workflow
This lesson turns the lifecycle into a safe experiment using only synthetic values. You will create one repository configuration variable, one repository Actions secret, and—where available—one environment-scoped secret in a disposable repository. Workflows prove presence without printing credentials, a non-secret toy value demonstrates why transformations can escape masking, and repeated manual runs provide rotation and revocation evidence.
Learning objectives
- Create synthetic repository and environment secret state without using a real provider credential.
- Consume a secret through a process environment variable while logging only presence/state evidence.
-
Compare a non-sensitive
varsvalue with a confidentialsecretsvalue in one workflow. - Demonstrate masking limitations using an intentionally non-secret toy string rather than exposing a credential.
- Rotate then remove the lab secret and verify that a later workflow run observes revocation as an empty secret.
1. Scenario and authorization boundary
Use a repository created only for this course chapter. The values below are intentionally synthetic and must not authorize any real service.
| State | Suggested value | Why |
|---|---|---|
Repository variable LAB_STAGE |
training |
Non-sensitive configuration; should be visible in logs. |
Repository variable LAB_SECRET_GENERATION
|
v1 |
Non-secret rotation marker; can be printed safely. |
Repository secret LAB_REPO_CREDENTIAL |
A random course-only fake string with no external authority. | Exercises secret storage and injection. |
Environment lab-gate |
Disposable environment | Exercises environment association. |
Environment secret LAB_ENV_CREDENTIAL |
A different course-only fake string. | Shows environment-specific access/timing. |
For a free mandatory path, a disposable public repository supports environments/environment secrets on current plans. If you deliberately use a private repository on a plan that does not expose environment secrets, complete the repository-secret workflow and use the documented environment section as a simulation; do not upgrade or expose a real project merely for the lab.
2. Create state without putting secret values in source
-
In repository settings, create variables
LAB_STAGE=trainingandLAB_SECRET_GENERATION=v1. -
Create repository secret
LAB_REPO_CREDENTIALwith a synthetic value. -
Create environment
lab-gate. AddLAB_ENV_CREDENTIALthere. Optional: add a reviewer if your repository/plan supports the protection rule and another authorized reviewer is available. - Do not commit the fake secret values, paste them into issue text, or include them in screenshots.
The secret can be fake and still be handled as though it were valuable. That discipline makes the same workflow pattern safe when real credentials are later introduced.
3. Repository and environment scope in one workflow
name: Chapter 07 secret lifecycle lab
on:
workflow_dispatch:
permissions: {}
jobs:
repository_scope:
runs-on: ubuntu-24.04
env:
LAB_REPO_CREDENTIAL: ${{ secrets.LAB_REPO_CREDENTIAL }}
LAB_STAGE: ${{ vars.LAB_STAGE }}
LAB_SECRET_GENERATION: ${{ vars.LAB_SECRET_GENERATION }}
steps:
- name: Inspect non-sensitive configuration
run: |
printf 'stage=%s generation=%s\n' "$LAB_STAGE" "$LAB_SECRET_GENERATION"
- name: Verify repository secret presence only
run: |
set -euo pipefail
if [[ -z "$LAB_REPO_CREDENTIAL" ]]; then
echo 'repo_secret_state=missing'
exit 20
fi
echo 'repo_secret_state=present'
environment_scope:
runs-on: ubuntu-24.04
environment: lab-gate
env:
LAB_ENV_CREDENTIAL: ${{ secrets.LAB_ENV_CREDENTIAL }}
steps:
- name: Verify environment secret presence only
run: |
set -euo pipefail
if [[ -z "$LAB_ENV_CREDENTIAL" ]]; then
echo 'environment_secret_state=missing'
exit 21
fi
echo 'environment_secret_state=present'
The first job receives no environment secret because it does not
reference lab-gate. The second job's environment
association is visible in run state; if a protection rule is active,
the job waits before GitHub makes the environment secret available.
4. Evidence packet for Run A
Record only non-secret facts:
run_id: <record from Actions UI>
run_attempt: 1
source_sha: <exact workflow revision>
repository: <disposable repository>
event: workflow_dispatch
LAB_STAGE: training
LAB_SECRET_GENERATION: v1
repository_secret: present (value not recorded)
environment: lab-gate
environment_secret: present (value not recorded)
protection_review: used / not configured / simulated
external_side_effect: none
If a reviewer is configured, preserve the approval/deployment record. Do not use a screenshot that captures secret-entry forms.
5. Demonstrate masking limitations without touching the real secret
Add a separate step that uses a deliberately non-sensitive toy string. Its purpose is to show mechanics, not to prove anything about the actual secret.
- name: Safe masking demonstration with non-secret data
shell: bash
env:
TOY_VALUE: course-mask-demo-not-a-credential
run: |
set -euo pipefail
echo "::add-mask::$TOY_VALUE"
echo "exact=$TOY_VALUE"
encoded=$(printf '%s' "$TOY_VALUE" | base64 -w 0)
echo "reversible_base64=$encoded"
echo 'The encoded line may remain visible because Base64 changed the string.'
The exact toy value should be masked after registration; its Base64
transformation is a different string and may be visible. That is why
“GitHub masks my secret” is not a license to transform and print it.
Never run this demonstration with LAB_REPO_CREDENTIAL.
6. Rotate: change secret and publish a non-secret generation marker
-
Update
LAB_REPO_CREDENTIALto a second synthetic value. Do not print either old or new value. -
Update
LAB_SECRET_GENERATIONfromv1tov2. - Dispatch Run B.
-
Record that Run B reports
generation=v2andrepo_secret_state=present.
The generation variable is operator-maintained audit metadata, not cryptographic proof of the secret contents. The actual secret remains intentionally opaque. In production, rotation proof should come from the credential issuer's audit trail or successful scoped authentication—not from logging a secret-derived hash.
7. Rotation timing: queued run versus environment job start
Repository and organization secrets are read when the run is queued. Therefore, do not assume editing a repository secret changes an already queued run. Environment secrets are read when a referencing job starts, which is especially relevant if the job waits for an approval gate.
During incident response, cancel stale queued/in-progress work as appropriate, rotate at the issuer, update GitHub storage, and launch a fresh run whose evidence begins after the rotation boundary.
8. Revoke the lab secret and prove future absence
-
Delete
LAB_REPO_CREDENTIALfrom the disposable repository. - Dispatch Run C.
-
The
repository_scopejob should fail at the explicit empty-string check withrepo_secret_state=missing. - Preserve Run C rather than changing the workflow to make it green.
A missing secret reference resolves to an empty string. That observable failure is the revocation evidence for GitHub-side availability. If the value represented a real provider credential, you would also revoke it at the provider; deleting the GitHub copy alone would not invalidate the external credential.
9. Cleanup and rollback
-
Delete
LAB_ENV_CREDENTIALand removelab-gateif it exists only for this lab. -
Delete
LAB_STAGEandLAB_SECRET_GENERATIONif they are lab-only. - Keep the workflow/run evidence if you want an audit packet; it contains no credential values.
- Delete the disposable repository when the course experiment is complete if you no longer need the run history.
10. Challenge: choose configuration, secret, or identity
Classify each value before editing YAML: a public region name, a
service URL, a registry password, a cloud deployment identity, and a
per-run checksum. Use vars for public shared
configuration, a secret only where a confidential stored value is
genuinely required, OIDC for supported cloud identity, and ordinary
outputs/artifacts for non-secret dataflow.
Knowledge check
Why does the lab print LAB_SECRET_GENERATION but
not the secret?
The generation marker is deliberately non-sensitive audit metadata; the credential value remains opaque.
What should Run C observe after deleting
LAB_REPO_CREDENTIAL?
The secret expression becomes an empty string, so the explicit
presence check fails and records
repo_secret_state=missing.
Why is the Base64 demonstration performed on a toy string?
Base64 is reversible and transformed values may evade exact masking. Demonstrating that with a real credential would create an unnecessary leak.
When can an environment secret become available to a protected job?
When the job referencing the environment starts after applicable protection rules, such as required approval, have been satisfied.
Does deleting the GitHub secret revoke a real external API key?
No. It stops future GitHub jobs from retrieving that stored value, but the credential must also be revoked or rotated at its issuer.
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. The mandatory lab intentionally uses only
synthetic credentials and local presence checks. The masking
demonstration uses an explicitly non-secret toy value, never the
stored lab secret.
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.