Chapter 13Lesson 05~300 minutes

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.

CheckpointVariable matrixPrediction ledgerCleanupNo secret echoChapter 14 bridge

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.
Availability baseline (verified 2026-08-21; GitLab 19.3 is the latest monthly release, released 2026-08-20). Project and group CI/CD variables, masking, hiding, protection, file variables, pipeline inputs, predefined variables, and OIDC ID tokens are available on Free/Premium/Ultimate across GitLab.com, Self-Managed, and Dedicated unless a narrower note is stated. Instance variables are Self-Managed/Dedicated administrator scope. Group-variable environment scoping is Premium/Ultimate. GitLab's built-in external-secret provider integrations are Premium/Ultimate, while the underlying ID-token/OIDC mechanism is Free. The mandatory chapter path uses only a disposable project, synthetic values, and a no-paid validation/fixture fallback.

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?

What two independent observations prove the file variable model?

What should happen when the unprotected branch requires CH13_PROTECTED?

Why was no pipeline variable used for profile/retries?

What proves cleanup is complete?

What new trust boundary becomes central in Chapter 14?

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

Chapter 14

GitLab Runners, Executors, Tags, Registration, Autoscaling, Isolation, and Security

Next you will connect the resolved job/variable model to the compute that executes it: runner scopes, tags, authentication, executor isolation, persistence, autoscaling, and why an untrusted runner can defeat otherwise correct secret scoping.

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.