Rules
Rules are grouped by OWASP Top 10 CI/CD Security Risks. Each rule links to its full documentation with risk description, trigger examples, and safe alternatives.
Credential Hygiene — CICD-SEC-6
Secrets hardcoded, leaked through logs, or forwarded to unintended consumers.
| ID | Severity | Description |
|---|---|---|
| GL005 | error/warn |
Sensitive file patterns in artifacts: paths |
| GL027 | warn |
Secret-like variable with a value committed in the pipeline file (long form, pre-fills the Run pipeline form) |
| GL004 | warn |
CI_JOB_TOKEN forwarded to a non-GitLab host |
| GL006 | error |
Hardcoded secret in variables: block |
| GL066 | error |
Inline registry credentials in DOCKER_AUTH_CONFIG instead of a masked CI/CD variable |
| GL036 | warn |
Connection string with embedded credentials (scheme://user:pass@host) in variables: block |
| GL010 | warn |
trigger: forward: pipeline_variables: true leaks secrets to downstream pipeline (GitLab ≥ 14.9) |
| GL037 | warn |
trigger: job without inherit: variables: false — top-level secrets implicitly forwarded to downstream |
| GL014 | warn |
dotenv artifact captures all environment variables including secrets (GitLab ≥ 12.9) |
| GL018 | warn |
Secret variable re-exported at pipeline level — available to all jobs including untrusted ones |
| GL021 | warn |
Secret variable value printed to job log via echo/printf |
| GL032 | warn |
SSH private key written to file via echo — key appears in job logs when debug tracing is active |
| GL033 | error/warn |
CI_DEBUG_TRACE/CI_DEBUG_SERVICES committed — debug logging dumps variable values or service-container credentials to job logs |
| GL029 | warn |
docker login -p exposes password in process table — use --password-stdin instead |
| GL035 | warn |
git command uses URL with embedded credentials (user:token@host) — token appears in job logs |
| GL038 | error |
Hardcoded credential literal passed to CLI tool in script (sqlcmd -P, mysql -p, PGPASSWORD=, etc.) |
| GL040 | warn |
Script uses plain ftp:// — credentials and content transmitted unencrypted |
| GL052 | warn |
User-controlled variable in environment:name: — attacker can craft a branch name that resolves to a protected environment and access its secrets |
| GL059 | warn |
docker build --build-arg with a secret-keyword name bakes the value into image layer metadata (visible via docker history) |
| GL062 | warn |
printenv or bare env dumps every variable (including secrets) to the job log |
| GL068 | warn |
set -x / xtrace in a script traces expanded commands — including secrets — to the job log |
| GL070 | warn |
Static cloud service-account credential in script (gcloud --key-file, aws_secret_access_key, az --password) — prefer keyless OIDC |
| GL073 | warn/error |
Artifacts exposed to unauthenticated users (artifacts:public: true / artifacts:access: all) — error when an exposed path looks sensitive |
Dependency & Image Pinning — CICD-SEC-3
Mutable references that allow silent substitution of images, templates, or packages.
| ID | Severity | Description |
|---|---|---|
| GL001 | error/warn |
Mutable image tag (latest, no tag, non-digest pin); version-less variant tags such as docker:dind as warn |
| GL003 | error |
Remote include: with mutable or missing ref |
| GL011 | error |
Download-and-execute pattern in script (curl | bash, wget | sh) |
| GL016 | varies | Insecure transport — HTTP or git:// (include:remote, scripts, variables) |
| GL022 | warn |
Package manager install or fetch-and-run launcher (npx -y, dlx, uvx) without version pin, or explicit update-to-latest in CI |
| GL023 | warn |
Lockfile not enforced (npm install instead of npm ci, yarn install without --frozen-lockfile, etc.) |
| GL046 | error/warn |
Cache poisoning across the protected-branch boundary — cache: key: from a user-controlled variable, or cache: unprotect: true on a job that writes the cache |
| GL026 | warn |
git clone/checkout uses a mutable ref (branch or tag) instead of a pinned commit SHA |
| GL064 | warn |
include: component: path is one edit from a popular component under a different namespace (typosquat) |
| GL069 | warn |
Package signature/auth verification bypassed (apt --allow-unauthenticated, [trusted=yes], apk --allow-untrusted, GOSUMDB=off) |
| GL072 | warn |
needs:project cross-project artifact download with a mutable branch or variable ref: — the consumed artifacts can be poisoned |
| GL075 | warn |
include: source (project / component / remote) not on the opt-in allowed_include_sources allowlist — provenance check complementing GL003's pinning |
| GL079 | warn |
pip install --extra-index-url adds a public index alongside the default — highest version wins across indexes, enabling dependency confusion |
Poisoned Pipeline Execution — CICD-SEC-4 / CICD-SEC-8
User-controlled inputs and unversioned component references that allow malicious code to run inside the pipeline.
| ID | Severity | Description |
|---|---|---|
| GL002 | warn |
User-controlled CI variable used unquoted in script |
| GL007 | error |
CI variable interpolation in image: reference |
| GL015 | warn |
Docker image tag built from user-controlled variable ($CI_COMMIT_REF_SLUG etc.) |
| GL025 | warn |
curl/wget uses a user-controlled CI variable — attacker can redirect the request to an arbitrary host |
| GL041 | warn |
include: component: without a pinned semver tag or commit SHA |
| GL044 | warn |
MR-triggered job checks out $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA — executes attacker-controlled code with $CI_JOB_TOKEN access |
| GL051 | error/warn |
Unconstrained spec:inputs or job inputs: entry interpolated into image:, script: and similar keywords — caller can supply arbitrary values |
| GL053 | warn |
Missing or unrestricted workflow:rules — pipelines run for every event, including untrusted fork merge requests |
| GL065 | warn |
image:/services: from a registry not in the opt-in allowed_registries allowlist |
| GL067 | warn |
SECURE_ANALYZERS_PREFIX (or *_ANALYZER_IMAGE) repoints managed security scanners off registry.gitlab.com |
| GL078 | warn |
Invisible / bidirectional Unicode control characters (Trojan Source) and line breaks only YAML sees — rendered file can differ from what runs |
| GL083 | error/warn |
Reverse shell, backdoor or credential exfiltration in script: — connects the runner back to an attacker-controlled host, or sends a credential file, minted token, or the job environment to one |
| GL084 | error/warn |
CI variable interpolation in an include: target — the included configuration is picked at pipeline start |
| GL085 | error/warn |
AI coding agent (Claude Code, Codex, Gemini CLI, Copilot CLI) run with approvals disabled, or with user-controlled text in its prompt |
Supply Chain Integrity — CICD-SEC-9
Downloads and executions that bypass integrity checks, enabling tampering mid-pipeline.
| ID | Severity | Description |
|---|---|---|
| GL020 | warn |
File downloaded with curl/wget without checksum verification before execution |
| GL045 | warn |
Release job pushes artifacts without a signing step (cosign sign, gpg --detach-sign, notation sign) |
Pipeline Flow & Access Control — CICD-SEC-1 / CICD-SEC-5
Gates bypassed, runners untrusted, or downstream pipelines outside access control.
| ID | Severity | Description |
|---|---|---|
| GL008 | warn |
GitLab security scan job silenced — allow_failure: true, when: manual, or a lone rules: [when: never] |
| GL012 | warn |
when: always on a deploy/release job bypasses upstream quality gates |
| GL013 | warn |
Production deploy job has no rules: or only: branch restriction |
| GL019 | warn |
Deploy/publish job has no resource_group: — concurrent runs risk race conditions or partial deploys |
| GL034 | warn |
trigger: job without strategy: mirror / depend — child pipeline failures are silently ignored |
| GL039 | warn |
Security audit tool silenced with || true — failures discarded, pipeline always green |
| GL009 | warn |
Overly broad OIDC id_tokens audience (GitLab ≥ 15.7) |
| GL017 | warn |
Deploy/publish job has no tags: — can run on any runner including untrusted self-hosted |
| GL043 | warn |
Unanchored regex on user-controlled variable in rules:if — prefix match can be bypassed by crafting a matching value |
| GL071 | warn |
Manual approval gate before a production deploy without allow_failure: false — optional by default, so the pipeline can deploy without it being triggered |
| GL074 | warn |
Tautological (always-true) rules:if condition — the gate is a no-op and the job runs in every context it was meant to restrict |
| GL055 | warn |
DOCKER_HOST points at the host Docker socket (/var/run/docker.sock) — full control of the runner's Docker daemon |
| GL080 | warn |
Sensitive/deploy job with no pipeline-source guard (no rules:/only: or a rules: with no if:) — runs on every source including fork MRs |
| GL081 | warn |
Job issues an OIDC id_tokens: credential on an unrestricted pipeline source — any branch or merge request can mint a cloud-federating token (GitLab ≥ 15.7) |
| GL082 | warn |
Managed security scan switched off by CI/CD variable (SAST_DISABLED, SECRET_DETECTION_DISABLED, …) — the template's jobs never run |
Insecure Configuration — CICD-SEC-7
Misconfigured CI settings that expand the attack surface or leak build context.
| ID | Severity | Description |
|---|---|---|
| GL024 | warn |
Shell pipe without set -o pipefail — failures in all but the last command are silently ignored |
| GL028 | warn |
artifacts: untracked: true without paths: or exclude: may archive .env, keys, and other sensitive files |
| GL030 | warn |
ssh-keyscan at runtime blindly trusts the remote host key — MITM risk on shared runner networks |
| GL031 | error |
DOCKER_TLS_CERTDIR: "" disables Docker daemon TLS — exposes port 2375 unauthenticated on the runner network |
| GL042 | warn |
TLS certificate verification disabled (curl -k, wget --no-check-certificate, GIT_SSL_NO_VERIFY, etc.) |
| GL047 | warn |
SSH connection to remote host as root — use a least-privilege service account instead |
| GL048 | error |
StrictHostKeyChecking disabled in SSH command or config — host identity not verified, enabling MITM attacks |
| GL049 | warn |
Deploy job with interruptible: true — mid-run cancellation leaves the target environment in an undefined state |
| GL050 | warn |
sudo in CI script — escalates to root, amplifying blast radius if the pipeline is compromised |
| GL054 | warn |
docker:dind service (or DOCKER_HOST pointing at it) implies a privileged runner with full host root access |
| GL056 | warn |
docker run --privileged in a script grants the container full host kernel access |
| GL057 | error/warn |
docker run --cap-add with dangerous Linux capabilities (ALL, SYS_ADMIN, SYS_PTRACE, SYS_MODULE, NET_ADMIN) |
| GL058 | warn |
docker run --network host removes network isolation from the runner host |
| GL060 | error/warn |
docker run -v mounts a sensitive host path (/, /etc, /root, /proc, /sys, …) breaking isolation |
| GL061 | warn |
docker run --pid host shares the host PID namespace — container can see/signal all host processes |
| GL076 | warn |
docker run --security-opt seccomp=unconfined / apparmor=unconfined disables kernel-level confinement — widens the escape surface without --privileged |
| GL077 | warn |
docker run --ipc host shares the host IPC namespace — container can read host shared memory and signal host processes |
| GL063 | warn |
chmod grants world-writable permissions (777, a+w, o+w) — TOCTOU risk on shared runners |
OWASP ASVS v4.0.3 — V14 (Build and Deploy) mapping
A subset of rules is mapped to OWASP ASVS v4.0.3 Chapter V14 requirements, surfaced in JSON output (asvs field) and SARIF (an OWASP ASVS taxonomy) for compliance evidence.
Why v4.0.3 and not 5.0.0? ASVS 5.0.0 restructured the standard and removed the V14 "Configuration" CI/CD and build-pipeline requirements — build environment isolation, pipeline integrity, security-tooling gates and the like have no equivalent in 5.0.0, which narrowed its scope to the application itself. Since those controls are exactly what glsec lints, v4.0.3 V14 remains the most accurate mapping for a CI/CD security linter. The pin is reviewed against each new ASVS release (see #290).
| ASVS requirement | Rules |
|---|---|
| V14.2.1 — Components from trusted, maintained sources | GL003, GL011, GL016, GL065, GL067, GL075 |
| V14.2.2 — Components up to date and pinned | GL001, GL022, GL023, GL026, GL041 |
| V14.2.3 — Dependencies verified for integrity | GL020 |
| V14.3.1 — Pipeline config protected from modification | GL003, GL019, GL084 |
| V14.3.2 — Security tools run and failures block the build | GL008, GL039, GL082 |
| V14.3.3 — Secrets absent from source and logs | GL006, GL014, GL018, GL021, GL027, GL032, GL033, GL035, GL036, GL038, GL066, GL068 |
| V14.3.4 — Build environment isolated | GL007, GL015, GL025, GL083 |
| V14.4.1 — Third-party CI/CD service dependence minimised | GL041 |