Checkpoint Lab — CI/CD Variables, Inputs, Secrets, File Variables, Masking, Protection, and Scope
Build and verify a synthetic variable matrix covering precedence, protected availability, file semantics, typed inputs, and cleanup while proving that no secret value was intentionally exposed.
Learning objectives
- Predict the effective value and availability of every synthetic variable before running the checkpoint.
- Prove project-over-YAML precedence with non-sensitive data and protected-ref filtering with presence-only evidence.
- Validate file-variable behavior without printing file contents.
- Use typed inputs for non-secret pipeline parameters and keep high-precedence pipeline variables out of the lab.
- Delete all synthetic variables and verify the project no longer contains the checkpoint state.
1. Checkpoint mission
Build a tiny, auditable variable contract for a disposable project. The project has one harmless precedence key, one file variable containing synthetic text, one masked/protected marker whose value is never printed, and two typed pipeline inputs. You will prove behavior on a protected default branch and an unprotected feature branch, then remove all state.
2. Preflight and assumptions
| Dimension | Checkpoint assumption |
|---|---|
| Project | Disposable and clearly named. No existing real CI/CD secrets. |
| Tier/offering | GitLab Free path on GitLab.com, Self-Managed, or Dedicated. |
| Role | Maintainer to create/delete project variables; ability to push default and feature branches in the disposable project. |
| Runner | Optional. If unavailable, use validated configuration plus expected fixtures; do not buy compute. |
| Default branch | Protected; verify before relying on it. |
| Data | All values synthetic. No real credentials, keys, customer data, or private source content. |
| External secrets | Design-only extension; not required. |
3. Define the variable matrix before creating anything
| Key/source | Value/class | Flags | Expected protected default branch | Expected unprotected branch |
|---|---|---|---|---|
CH13_PRIORITY YAML top-level |
yaml-default, non-secret |
none | Overridden | Overridden |
CH13_PRIORITY YAML job |
yaml-job, non-secret |
none | Overridden | Overridden |
CH13_PRIORITY project |
project-setting, non-secret |
visible | Effective | Effective |
CH13_FILE project |
synthetic two-line fixture | file | Path exists | Path exists |
CH13_PROTECTED project |
synthetic marker | masked + protected | Present | Absent |
profile input |
lab/audit | typed options; default lab | Interpolated | Interpolated |
retries input |
number | default 1 | Interpolated | Interpolated |
4. Create only the synthetic project variables
printf 'profile=checkpoint\nregion=example\n' > ch13-checkpoint-file.txt
glab variable set CH13_PRIORITY "project-setting" --description "Checkpoint precedence"
glab variable set CH13_FILE --type file < ch13-checkpoint-file.txt
glab variable set CH13_PROTECTED "synthetic-protected-marker" --masked --protected
# Display metadata only in this disposable project.
glab variable list --output json --jq '.[] | select(.key | startswith("CH13_")) | {key, variable_type, protected, masked, hidden, environment_scope}'
Prediction 1: CH13_PRIORITY should resolve to
project-setting even if the job declares
yaml-job. Prediction 2:
CH13_PROTECTED should exist only on an eligible
protected ref.
5. Checkpoint pipeline configuration
spec:
inputs:
profile:
description: "Checkpoint profile"
options: [lab, audit]
default: lab
retries:
type: number
default: 1
---
variables:
CH13_PRIORITY: "yaml-default"
variable_matrix:
variables:
CH13_PRIORITY: "yaml-job"
script:
- printf 'priority=%s\n' "$CH13_PRIORITY"
- test -f "$CH13_FILE"
- printf 'file_present=yes\n'
- printf 'file_bytes=%s\n' "$(wc -c < "$CH13_FILE")"
- grep -q '^profile=checkpoint$' "$CH13_FILE"
- printf 'file_shape=valid\n'
- if [ -n "${CH13_PROTECTED:-}" ]; then printf 'protected_present=yes\n'; else printf 'protected_present=no\n'; fi
- printf 'input_profile=%s\n' "$[[ inputs.profile ]]"
- printf 'input_retries=%s\n' "$[[ inputs.retries ]]"
Notice what is intentionally absent: no
env/printenv, no cat of the
file variable, no output of the protected marker, no job token
output, and no external network request.
6. Validate before any pipeline
Use Pipeline Editor/CI Lint. Confirm the input header has the YAML document separator, both inputs have defaults, and the job contains only safe allowlisted output. If validation fails, repair configuration before pushing.
Write the expected log for the protected branch before executing:
priority=project-setting
file_present=yes
file_bytes=34
file_shape=valid
protected_present=yes
input_profile=lab
input_retries=1
7. Protected default-branch run
git add -- .gitlab-ci.yml
git diff --cached --check
git diff --cached
git commit -m "ch13: checkpoint variable matrix"
PROTECTED_SHA="$(git rev-parse HEAD)"
printf 'protected_sha=%s\n' "$PROTECTED_SHA"
git push origin HEAD
Verify pipeline source/ref/SHA and the expected log fields.
priority=project-setting proves settings precedence
over job/default YAML. protected_present=yes proves
eligibility without revealing the protected value. If runner
capacity is unavailable, record the pending state and use the
expected fixture as the documented no-runner path.
8. Unprotected branch comparison
git switch -c ch13/checkpoint-unprotected
git push -u origin ch13/checkpoint-unprotected
UNPROTECTED_SHA="$(git rev-parse HEAD)"
printf 'unprotected_sha=%s\n' "$UNPROTECTED_SHA"
Expected differences are intentionally small:
priority and file behavior remain identical, but
protected_present=no. This isolates the protected-ref
boundary rather than changing multiple variables at once.
9. Manual input variation
From Build → Pipelines → New pipeline, run the
disposable branch with profile=audit and a small
numeric retries value. Verify only those non-sensitive
input-derived fields change. Do not add a pipeline variable with the
same key; the exercise is specifically demonstrating inputs without
a high-precedence override channel.
10. Prediction ledger and independent proof
| Prediction | Primary evidence | Independent evidence |
|---|---|---|
| Project value beats YAML job/default |
Job prints safe priority=project-setting.
|
Variable metadata shows project key exists; repository shows lower YAML definitions. |
| File variable is a path |
test -f and byte-count/shape checks pass.
|
Metadata reports variable_type=file. |
| Protected variable exists on default branch | Presence-only log says yes. | Default branch protection rule is recorded. |
| Protected variable absent on feature branch | Presence-only log says no. | Feature branch is confirmed unprotected. |
| Input is configuration, not secret storage | Safe selected values appear in job context. |
spec:inputs defines type/options/default in
source.
|
11. Controlled failure: require the protected marker on the unprotected branch
Temporarily change only the unprotected branch so the job requires the protected marker:
- test -n "${CH13_PROTECTED:-}" || { echo "protected variable unavailable as expected"; exit 42; }
Prediction: the unprotected pipeline fails with exit
42. Diagnose it as an authorization/scope result, not a
missing variable configuration defect. Repair by restoring the
presence-only probe or restricting the secret-requiring job to
protected refs. Do not unprotect the variable.
12. Prove the checkpoint did not leak values
-
Search the job log for only the allowlisted keys:
priority,file_present,file_bytes,file_shape,protected_present,input_profile,input_retries. - Confirm the protected marker value never appears in source, logs, artifacts, screenshots, notes, or evidence files.
- Confirm the file fixture contents were not printed or uploaded as an artifact.
-
Confirm no
CI_JOB_TOKEN, auth token, environment dump, debug trace, or service debug log was emitted.
13. Cleanup: delete variables first, then refs/files
Variable cleanup is part of the checkpoint, not optional housekeeping.
glab variable delete CH13_PRIORITY
glab variable delete CH13_FILE
glab variable delete CH13_PROTECTED
glab variable list --output json --jq '[.[] | .key | select(startswith("CH13_"))]'
rm -f -- ch13-checkpoint-file.txt
# After evidence is captured and the branch name is verified:
git push origin --delete ch13/checkpoint-unprotected
Expected metadata result after deletion: an empty list for the
CH13_ prefix. If the whole project was disposable,
delete it only after visually verifying the project path and
retaining the evidence you actually need.
14. Final verification checklist
- ☐ Exactly one effective non-secret precedence value was observed and explained.
- ☐ File variable semantics were proven without printing content.
- ☐ Protected value was checked by presence only.
- ☐ Protected versus unprotected behavior matched the prediction.
- ☐ Inputs were typed/defaulted and contained no secret.
- ☐ No pipeline variable override was introduced.
- ☐ No debug trace or environment dump was used.
- ☐ All three synthetic project variables were deleted and absence verified.
- ☐ Temporary local fixture and synthetic branch were removed.
15. Production operating model and Chapter 14 bridge
Chapter 13 adds a data-authorization layer to the GitLab operating model. Jobs no longer “just have environment variables”; each value has an owner, source, precedence, protection rule, representation, lifetime, and runner trust boundary. Inputs are explicit configuration contracts, and long-lived secrets have a migration path toward identity-based retrieval.
Chapter 14 moves to the compute boundary: GitLab Runners, executors, tags, registration, autoscaling, isolation, and security. Variable scoping only protects secrets if the runner that receives them is itself appropriately isolated and trusted.
Knowledge check
Why did the checkpoint print CH13_PRIORITY but not CH13_PROTECTED?
CH13_PRIORITY is intentionally non-sensitive test data used to prove precedence. The protected marker tests availability only; printing its value would teach the wrong secret-handling habit.
What two independent observations prove the file variable model?
Metadata says variable_type=file, and the job proves the environment value names a readable temporary file without printing its contents.
What should happen when the unprotected branch requires CH13_PROTECTED?
The job should fail because the protected variable is intentionally unavailable. The fix is to align job/ref policy, not weaken protection.
Why was no pipeline variable used for profile/retries?
Typed inputs are the intended configuration interface and avoid introducing a high-precedence untyped override channel.
What proves cleanup is complete?
Variable inventory contains no CH13_ keys, the synthetic branch is removed, and local fixture files are deleted.
What new trust boundary becomes central in Chapter 14?
The runner/executor: once a job receives variables or secrets, runner isolation determines what else can observe or retain them.
Summary
The checkpoint completed the variable-control loop: classify → define scope → predict precedence/availability → validate → run on protected and unprotected refs → observe only safe metadata → diagnose a deliberate scope failure → delete all synthetic state. The project is now ready to treat runner selection and executor isolation as the next authorization boundary.
Official references
- GitLab Docs — CI/CD variables
- GitLab Docs — CI/CD inputs
- GitLab Docs — Pipeline security
- GitLab Docs — External secrets in CI/CD
- GitLab Docs — OIDC authentication using ID tokens
- GitLab Docs — Connect to cloud services with OIDC
- GitLab Docs — Project-level CI/CD Variables API
- GitLab Docs — Group-level Variables API
- GitLab Docs — Predefined CI/CD variables
- GitLab Docs — Where variables can be used
- GitLab Docs — CI/CD YAML syntax reference
- GitLab CLI — variable commands
- GitLab CLI — variable set
- GitLab CLI — variable delete
- GitLab 19.3 — latest monthly release
- GitLab Docs — GitLab Secrets Manager (Self-Managed)
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.