Rules / GL051
GL051 Unconstrained CI/CD input interpolated into sensitive CI keyword
Severity: warn; error for an unconstrained job input in a deploy-like job
OWASP: CICD-SEC-4 – Poisoned Pipeline Execution
CWE: CWE-829 – Inclusion of Functionality from Untrusted Control Sphere
Risk
GitLab CI Catalog components accept caller-supplied values via spec:inputs. When an input is interpolated into a sensitive keyword — image:, services:, script:, before_script:, after_script:, a run: step script, or environment:name: — without a constraint GitLab enforces, any consumer of the component can supply an arbitrary value.
This differs from GL002 (predefined GitLab variables): here the tainted value originates from whoever calls the component, not from the commit author, making the attack surface wider and the trust model distinct.
Without constraints, an attacker can:
- Substitute an arbitrary image and execute malicious code (
image:,services:) - Inject shell commands into the pipeline (
script:,before_script:,after_script:,run:steps) - Redirect deployments to an unintended environment (
environment:name:)
Trigger example
spec:
inputs:
deploy_image:
description: "Image to deploy"
# no regex, no options, no number/boolean type
---
deploy:
image: $[[ inputs.deploy_image ]] # unconstrained caller input
script:
- ./deploy.sh
Safe alternatives
# Allowlist with options:
spec:
inputs:
environment:
options: [staging, production]
---
deploy:
script:
- ./deploy.sh $[[ inputs.environment ]]
# Pattern constraint with regex:
spec:
inputs:
image_tag:
regex: '^[a-zA-Z0-9._-]+$'
---
deploy:
image: myapp:$[[ inputs.image_tag ]]
script:
- ./deploy.sh
What counts as constrained
options:(also ontype: array) orregex:type: numberortype: boolean, which cannot carry shell metacharactersspec:inputs:ruleswhere every entry hasoptions:. An entry withoutoptions:, such asdefault: null, lets the user type any value, so the input stays unconstrained.
Notes
- Inputs with a
default:but without a constraint are still flagged — the default does not constrain what a caller can pass. - Every interpolation form is matched: array indexing and nested keys (
$[[ inputs.servers[0].host ]]) and piped functions ($[[ inputs.x | expand_vars ]]).posix_escapedoes not clear the finding — GitLab documents it as not a security control for untrusted input — and the message says so. - Inputs used only in non-sensitive positions (e.g.
variables:,artifacts:paths:) are not flagged by this rule. - The interpolation syntax
$[[ inputs.NAME ]]is used by CI Catalog components and by pipelines that declarespec:inputsin the root.gitlab-ci.yml; regular$VARIABLEreferences in normal pipelines are covered by GL002 and GL015.
Job inputs (${{ job.inputs.NAME }})
Since GitLab 18.10, a job can declare its own inputs:. Anyone who can run the job manually, or retry it with Retry job with modified values, sets their values. That includes Developers, also on protected-branch jobs that hold protected variables. GitLab substitutes ${{ job.inputs.NAME }} as text into script, before_script, after_script, image, services, cache and artifacts before the shell runs, so quoting in the script does not help.
GL051 flags an unconstrained job input referenced in any of those keywords, including inside a larger expression (${{ job.inputs.suite | upper }}). The constraints are the same as above: options:, regex:, or type: number/boolean. A default: is mandatory for job inputs and constrains nothing. Severity is error in a deploy-like job and warn otherwise.
deploy:
when: manual
inputs:
version:
default: v1.0.0 # free text: flagged
target_env:
default: staging
options: [staging, production]
script:
- ./deploy.sh --env ${{ job.inputs.target_env }} --version ${{ job.inputs.version }}
Fix: regex: ^v[0-9]+\.[0-9]+\.[0-9]+$ on version.
An image given entirely by a job input (image: ${{ job.inputs.img }}) is also checked by GL001 against the input's options:, or its default: when there are none.
See also
Enable the ShellCheck integration to surface additional shell issues in these script blocks.
Documentation for glsec 1.23.0. Source of this page · Rule implementation