CI/CD Variables, Inputs, Secrets, File Variables, Masking, Protection, and Scope: Concepts, Architecture, and Mental Model
Build a safe mental model for GitLab CI/CD variables, typed inputs, precedence, inheritance, masking, hidden/protected/file variables, expansion, external secret retrieval, and the runner trust boundary.
Learning objectives
- Distinguish runtime CI/CD variables from typed configuration inputs and explain when each is appropriate.
- Reason about project, group, instance, YAML, pipeline, dotenv, deployment, and predefined variable precedence without guessing.
- Explain masked, hidden, protected, environment-scoped, and file variables as separate controls with separate failure modes.
- Explain why masking is a log-redaction aid rather than a complete secret-management boundary.
- Describe how OIDC/ID-token based retrieval can reduce dependence on long-lived stored credentials.
1. The problem: a correct job can still receive the wrong data
Chapters 10–12 established when a pipeline exists, which jobs exist, and how those jobs execute. Chapter 13 asks the next security question: what configuration and credentials does each job receive, from where, and under whose authority?
A variable name such as DEPLOY_TARGET looks simple, but
its effective value can come from several layers. A credential can
be masked yet still be available to malicious pipeline code. A
protected value can disappear on an unprotected ref by design. A
file variable is not the secret text itself—it is a path to a
temporary file. These distinctions are part of the execution model.
2. Mental model: configuration contract → resolved values → runner environment
flowchart TD
I[Pipeline/component inputs] --> C[Configuration creation]
Y[.gitlab-ci.yml variables] --> R[Variable resolution]
G[Group / subgroup variables] --> R
P[Project variables] --> R
X[Pipeline variables / policy variables] --> R
R --> A{Ref / environment / protection filters}
A --> J[Job variable set]
J --> U[Runner execution environment]
S[External secret provider] -->|explicit job request / OIDC| U
U --> L[Logs / commands / artifacts]
Inputs primarily shape configuration at pipeline creation. Variables become runtime values for jobs. GitLab resolves competing variable definitions according to precedence, then applies scoping/protection rules. The runner finally exposes the permitted values to the job. External secrets are different again: the job explicitly requests them, ideally through a short-lived identity token.
3. Inputs and variables solve different problems
| Mechanism | Primary purpose | When evaluated / used | Security posture |
|---|---|---|---|
spec:inputs / pipeline inputs |
Typed, validated configuration parameters. | Interpolated when GitLab creates/fetches configuration. | Prefer for user-selectable pipeline configuration; options/regex/type create an explicit contract. |
YAML variables |
Non-sensitive runtime configuration declared with code. | Made available to jobs, subject to precedence. | Repository-readable; do not store secrets here. |
| Project/group/instance variables | Runtime configuration or, when unavoidable, protected secret-like values. | Resolved into eligible jobs. | Can be masked/hidden/protected/file type; still exposed to pipeline code that receives them. |
| External secret retrieval | Fetch sensitive data only when a job needs it. | Explicit job request, often after OIDC authentication. | Better separation and shorter-lived identity; provider policy remains critical. |
GitLab recommends pipeline inputs over ad-hoc pipeline variables for parameters supplied when a pipeline starts. Inputs support types, defaults, allowed options, regex validation, and a visible contract. They are not a vault and must not be used as a place to type secrets into source-controlled configuration.
4. Variable precedence: “closest in YAML” is not the rule
When the same key exists in multiple places, GitLab uses a defined precedence order. The important operational habit is to trace the key through every possible source before editing anything.
| Higher → lower precedence | Typical source | Governance implication |
|---|---|---|
| 1 | Pipeline execution policy variables | Security policy can intentionally dominate ordinary project configuration. |
| 2 | Scan execution policy variables | Security orchestration may override lower sources. |
| 3 | Pipeline variables: manual/API/trigger/schedule/downstream/manual-job | Powerful because they override project/group/YAML values; restrict who may set them. |
| 4 | Project variables | Override inherited group and YAML job/default variables. |
| 5 | Group/subgroup variables | Closest subgroup wins among group levels; still overrides YAML job/default values. |
| 6 | Instance variables | Self-Managed/Dedicated administrator-defined defaults. |
| 7 | Dotenv report variables | Values imported from a prior job report. |
| 8 | Job variables in YAML | Override top-level YAML defaults, but not project/group variables. |
| 9 | Top-level YAML default variables | Repository-defined defaults for jobs. |
| 10 | Deployment variables | Environment/deployment-provided values. |
| 11 | Predefined variables | GitLab-provided context such as project, SHA, pipeline, and job identifiers. |
Example: if CH13_MODE is job in a job,
group at the parent group, and project in
project settings, the effective value is project. If
the project value is removed, the group value beats the YAML job
value.
5. Inheritance is convenient—and expands blast radius
Project variables affect one project. Group variables are inherited by projects in the group, and subgroup variables recursively participate in the same model. If the same key exists at multiple group depths, the closest subgroup value wins. This makes centralized configuration easy, but a broad group secret can become available to far more pipeline code than its owner intended.
Instance variables are an administrator surface for Self-Managed/Dedicated and can affect all projects/groups on that instance. Treat them as platform configuration, not as a substitute for project-specific secret governance.
6. Masked, hidden, protected, environment-scoped, and file are orthogonal properties
| Property | What it controls | What it does NOT guarantee |
|---|---|---|
| Masked |
Redacts exact matching values in ordinary job trace output
as [MASKED].
|
Does not stop malicious code from transmitting the value elsewhere; transformed output can defeat masking. |
| Hidden | Prevents the saved value from being revealed again in the settings UI. Current hidden values must satisfy masking rules. | Does not stop eligible jobs from receiving the value. |
| Protected | Restricts availability to pipelines on protected branches/tags, with MR nuances controlled by project settings. | Does not make the value safe if protected pipeline code or runner is compromised. |
| Environment scope | Restricts a variable to jobs targeting matching environments. | Does not replace protected refs or runner isolation; and group-variable environment scope is Premium/Ultimate. |
| File | Writes the value to a temporary file and makes the environment variable contain the file path. | Does not make the file automatically non-readable by the job; job code that receives the path can read it. |
7. File variables: the variable contains a path
For a normal variable CH13_CONFIG, the environment
variable contains the configured value. For a file variable, GitLab
Runner writes the configured value into a temporary file and sets
CH13_CONFIG to the path of that file.
# Safe pattern: inspect file semantics without printing its content.
test -f "$CH13_CONFIG"
printf 'file_present=yes\n'
printf 'file_bytes=%s\n' "$(wc -c < "$CH13_CONFIG")"
# A tool that expects a path can consume "$CH13_CONFIG" directly.
This is useful for synthetic certificates, configuration files, and
tools that require --certificate-authority=/path-style
arguments. The temporary file exists within the job execution
context; do not copy it into artifacts or caches unless that is an
explicit, safe design.
8. Expansion and predefined variables: syntax is not a security boundary
GitLab makes predefined variables such as
CI_COMMIT_SHA, CI_PIPELINE_ID, and
CI_PROJECT_ID available automatically. Avoid overriding
predefined keys: higher-precedence pipeline variables can
technically replace many lower-precedence values and make pipeline
behavior difficult to reason about.
UI-defined variable expansion is disabled by default in current GitLab. Expansion must be intentionally enabled, and masked/hidden values cannot use variable-reference expansion. Keep secret values raw whenever possible and let the application consume them directly.
9. Pipeline variables are powerful; inputs are the safer parameter surface
Pipeline variables supplied through manual runs, schedules, APIs,
triggers, or downstream forwarding have high precedence. GitLab lets
projects restrict the minimum role allowed to supply them, including
no_one_allowed. For new GitLab.com projects in new
namespaces, the secure default may prohibit pipeline variables
entirely.
When a pipeline needs operator-selectable parameters, define typed inputs instead. Use pipeline variables only when their runtime mutability and precedence are actually required.
10. External secrets and identity-based retrieval
Long-lived credentials stored in GitLab variables can be acceptable for small labs, but production designs should prefer a secret manager when the environment supports one. GitLab supports OIDC ID tokens on Free across offerings. A job can present a narrowly scoped ID token to a cloud or secret provider and receive a short-lived credential without storing that credential in GitLab.
GitLab's built-in secrets integrations for providers
such as HashiCorp Vault, Google Cloud Secret Manager, Azure Key
Vault, and AWS Secrets Manager are Premium/Ultimate. The mandatory
course path therefore teaches the architecture and claims/policy
model without requiring a paid provider integration.
# Architecture fixture only — no real external provider is required.
job:
id_tokens:
VAULT_ID_TOKEN:
aud: https://vault.example.invalid
script:
- echo "OIDC token exists for provider authentication; do not print it"
11. Read-only inspection before changing anything
- Record GitLab offering/version and your project role.
- Inspect Settings → CI/CD → Variables for keys, type, protected/visibility status, and environment scope. Do not reveal values.
- Inspect group inheritance indicators before adding a same-name project key.
-
Search
.gitlab-ci.ymland included configuration for the variable key, input definition, and anyvariables:block. - Inspect the target ref's protection state before diagnosing a missing protected variable.
- Inspect runner trust and pipeline source before assuming a variable should be exposed.
-
Never run
env,printenv,export, or debug trace in a job that might receive real secrets.
12. DevOps connection: variable delivery is authorization
Secret safety is determined by the intersection of repository write permission, ref protection, pipeline source, variable scope, runner isolation, and external-provider policy. A masked variable available to unreviewed code is still a credential available to that code. A well-designed pipeline minimizes not only who can view a value in the UI, but also which jobs ever receive it.
Knowledge check
A project variable and a YAML job variable share the same key. Which one wins?
The project variable. Project variables have higher precedence than job-level YAML variables.
Does masking prevent malicious pipeline code from using a secret?
No. Masking only redacts matching output in normal logs. Eligible job code still receives the value and can misuse or exfiltrate it.
What does a file-type variable contain inside the job environment?
A path to a temporary file whose contents are the configured variable value.
Why prefer pipeline inputs for operator-selected configuration?
Inputs define a typed, validated configuration contract at pipeline creation and avoid the high-precedence, loosely typed behavior of pipeline variables.
What is the key difference between protected and environment-scoped variables?
Protected checks the ref protection boundary; environment scope checks which environment a job targets. They solve different authorization problems.
Why are OIDC ID tokens useful for secrets?
They let a job prove identity to an external provider and obtain short-lived credentials instead of storing long-lived credentials in GitLab.
Summary
GitLab variable safety is a resolution-and-authorization problem: choose the right mechanism, understand precedence, scope each value, protect the refs that receive it, isolate the runner, and prefer explicit short-lived secret retrieval when possible. Masking and hiding improve exposure resistance, but neither substitutes for trusted pipeline code.
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.