Example reports
One pipeline file and what glsec reports for it. Each output below is what glsec 1.23.0 prints for this file, regenerated with every release. The GitLab screenshots show the same file in the example projects and are taken by hand.
# The pipeline of a small Node.js service, with the kind of security mistakes
# glsec reports. Intentionally insecure: don't copy it.
include:
- project: platform/ci-templates
ref: main
file: /templates/notify.yml
- template: Security/Secret-Detection.gitlab-ci.yml
variables:
DOCKER_AUTH_CONFIG: '{"auths":{"registry.example.com":{"auth":"ZGVwbG95OnMzY3IzdA=="}}}'
stages: [build, test, deploy]
build:
stage: build
image: node:latest
script:
- npm install
- npm run build
artifacts:
expire_in: 1 week
paths:
- dist/
- .env
docker-image:
stage: build
image: docker:27.3.1
services:
- docker:27.3.1-dind
variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_TLS_CERTDIR: ""
script:
- docker build -t registry.example.com/shop/api:$CI_COMMIT_SHORT_SHA .
- docker push registry.example.com/shop/api:$CI_COMMIT_SHORT_SHA
test:
stage: test
image: node:22.11.0
script:
- npm ci
- echo Running tests for $CI_COMMIT_REF_NAME
- npm test
secret_detection:
rules:
- when: never
deploy:
stage: deploy
image: alpine:3.20.3
environment: production
id_tokens:
AWS_TOKEN:
aud: https://sts.amazonaws.com
script:
- set -x
- curl -sSL https://get.example.com/install.sh | sh
- 'curl -H "Authorization: Bearer $DEPLOY_TOKEN" "https://hooks.example.com/deploy?branch=$CI_COMMIT_REF_NAME"'
- ssh -o StrictHostKeyChecking=no deploy@prod.example.com ./release.sh
Text --format text
The default. Every finding with the rule, file, line, job and the source line, for the job log or a terminal.
Shows up in: The job log.
Output
error[GL001]: image "node:latest" uses mutable tag "latest" — pin to a specific version or digest
--> bad-pipeline.yml:17:10 (job: build)
|
17 | image: node:latest
| ^
warn[GL002]: unquoted user-controlled variable $CI_COMMIT_REF_NAME in script — value is set by commit authors and may contain shell metacharacters
--> bad-pipeline.yml:44:7 (job: test)
|
44 | - echo Running tests for $CI_COMMIT_REF_NAME
| ^
error[GL003]: project include "platform/ci-templates" uses mutable ref "main" — pin to a commit SHA or tag
--> bad-pipeline.yml:6:10
|
6 | ref: main
| ^
error[GL005]: artifact path ".env" matches sensitive file pattern ".env" — exclude secrets from artifacts
--> bad-pipeline.yml:25:9 (job: build)
|
25 | - .env
| ^
warn[GL008]: security scan job "secret_detection" has rules: [when: never] as its only rule — the job is switched off in every context
--> bad-pipeline.yml:49:13 (job: secret_detection)
|
49 | - when: never
| ^
error[GL011]: script line downloads and executes remote code without integrity verification: "curl -sSL https://get.example.com/install.sh | sh"
--> bad-pipeline.yml:60:7 (job: deploy)
|
60 | - curl -sSL https://get.example.com/install.sh | sh
| ^
warn[GL013]: job "deploy" deploys to "production" but has no rules: or only: clause — any branch can trigger this deployment
--> bad-pipeline.yml:54:16 (job: deploy)
|
54 | environment: production
| ^
warn[GL017]: job "deploy" deploys or publishes but has no tags: — it can run on any available runner, including untrusted self-hosted runners
--> bad-pipeline.yml:51:1 (job: deploy)
|
51 | deploy:
| ^
warn[GL019]: job "deploy" deploys or publishes but has no resource_group: — concurrent pipeline runs can cause race conditions or partial deploys
--> bad-pipeline.yml:51:1 (job: deploy)
|
51 | deploy:
| ^
warn[GL023]: npm install without lockfile enforcement — use "npm ci" to guarantee reproducible dependency resolution
--> bad-pipeline.yml:19:7 (job: build)
|
19 | - npm install
| ^
warn[GL024]: script uses a pipe without set -o pipefail — failures in all but the last command are silently ignored
--> bad-pipeline.yml:60:7 (job: deploy)
|
60 | - curl -sSL https://get.example.com/install.sh | sh
| ^
warn[GL025]: curl/wget request includes user-controlled variable $CI_COMMIT_REF_NAME — whoever names the branch or writes the commit or merge request controls part of the URL or request body
--> bad-pipeline.yml:61:7 (job: deploy)
|
61 | - 'curl -H "Authorization: Bearer $DEPLOY_TOKEN" "https://hooks.example.com/deploy?branch=$CI_COMMIT_REF_NAME"'
| ^
error[GL031]: DOCKER_TLS_CERTDIR set to empty string — disables TLS on the Docker daemon (port 2375), exposing it to any process on the runner network; set it to "/certs" and use port 2376
--> bad-pipeline.yml:34:5 (job: docker-image)
|
34 | DOCKER_TLS_CERTDIR: ""
| ^
error[GL048]: StrictHostKeyChecking disabled — host identity is not verified, enabling MITM attacks on shared runner networks; use a pre-verified known_hosts entry instead
--> bad-pipeline.yml:62:7 (job: deploy)
|
62 | - ssh -o StrictHostKeyChecking=no deploy@prod.example.com ./release.sh
| ^
warn[GL053]: no top-level workflow:rules — pipelines run for every event, including merge requests from forks; gate on $CI_PIPELINE_SOURCE or branch to block untrusted pipeline execution
--> bad-pipeline.yml:1:1
|
1 | # The pipeline of a small Node.js service, with the kind of security mistakes
| ^
warn[GL054]: docker-in-docker service "docker:27.3.1-dind" requires the runner to run privileged (full host root access) — consider rootless build tools like Kaniko or Buildah, or document the privileged-runner requirement
--> bad-pipeline.yml:31:7 (job: docker-image)
|
31 | - docker:27.3.1-dind
| ^
error[GL066]: DOCKER_AUTH_CONFIG contains inline registry credentials ("auth" is base64, not encryption) — store it as a masked, protected CI/CD variable (Settings → CI/CD → Variables) instead
--> bad-pipeline.yml:11:23
|
11 | DOCKER_AUTH_CONFIG: '{"auths":{"registry.example.com":{"auth":"ZGVwbG95OnMzY3IzdA=="}}}'
| ^
warn[GL068]: set -x enables shell xtrace — every command is printed to the job log after variable expansion, so any command referencing a secret (token in a git URL, Authorization header, --key flag) leaks it in plaintext; drop the x (use set -euo pipefail) or scope tracing with set +x around secret-touching commands
--> bad-pipeline.yml:59:7 (job: deploy)
|
59 | - set -x
| ^
warn[GL081]: job "deploy" issues an OIDC id_tokens: credential with no guard restricting $CI_PIPELINE_SOURCE or the ref — any branch or merge request can mint a cloud-federating token; add a rules:if that restricts to protected refs and condition the cloud trust policy on the ref_protected claim
--> bad-pipeline.yml:51:1 (job: deploy)
|
51 | deploy:
| ^Table --format table
One aligned row per finding, for a quick overview of many findings.
Shows up in: The job log.
Output
SEVERITY RULE JOB FILE LINE MESSAGE
ERROR GL001 build bad-pipeline.yml 17 image "node:latest" uses mutable tag "latest" — pin to a specific version or digest
WARN GL002 test bad-pipeline.yml 44 unquoted user-controlled variable $CI_COMMIT_REF_NAME in script — value is set by commit authors and may contain shell metacharacters
ERROR GL003 - bad-pipeline.yml 6 project include "platform/ci-templates" uses mutable ref "main" — pin to a commit SHA or tag
ERROR GL005 build bad-pipeline.yml 25 artifact path ".env" matches sensitive file pattern ".env" — exclude secrets from artifacts
WARN GL008 secret_detection bad-pipeline.yml 49 security scan job "secret_detection" has rules: [when: never] as its only rule — the job is switched off in every context
ERROR GL011 deploy bad-pipeline.yml 60 script line downloads and executes remote code without integrity verification: "curl -sSL https://get.example.com/install.sh | sh"
WARN GL013 deploy bad-pipeline.yml 54 job "deploy" deploys to "production" but has no rules: or only: clause — any branch can trigger this deployment
WARN GL017 deploy bad-pipeline.yml 51 job "deploy" deploys or publishes but has no tags: — it can run on any available runner, including untrusted self-hosted runners
WARN GL019 deploy bad-pipeline.yml 51 job "deploy" deploys or publishes but has no resource_group: — concurrent pipeline runs can cause race conditions or partial deploys
WARN GL023 build bad-pipeline.yml 19 npm install without lockfile enforcement — use "npm ci" to guarantee reproducible dependency resolution
WARN GL024 deploy bad-pipeline.yml 60 script uses a pipe without set -o pipefail — failures in all but the last command are silently ignored
WARN GL025 deploy bad-pipeline.yml 61 curl/wget request includes user-controlled variable $CI_COMMIT_REF_NAME — whoever names the branch or writes the commit or merge request controls part of the URL or request body
ERROR GL031 docker-image bad-pipeline.yml 34 DOCKER_TLS_CERTDIR set to empty string — disables TLS on the Docker daemon (port 2375), exposing it to any process on the runner network; set it to "/certs" and use port 2376
ERROR GL048 deploy bad-pipeline.yml 62 StrictHostKeyChecking disabled — host identity is not verified, enabling MITM attacks on shared runner networks; use a pre-verified known_hosts entry instead
WARN GL053 - bad-pipeline.yml 1 no top-level workflow:rules — pipelines run for every event, including merge requests from forks; gate on $CI_PIPELINE_SOURCE or branch to block untrusted pipeline execution
WARN GL054 docker-image bad-pipeline.yml 31 docker-in-docker service "docker:27.3.1-dind" requires the runner to run privileged (full host root access) — consider rootless build tools like Kaniko or Buildah, or document the privileged-runner requirement
ERROR GL066 - bad-pipeline.yml 11 DOCKER_AUTH_CONFIG contains inline registry credentials ("auth" is base64, not encryption) — store it as a masked, protected CI/CD variable (Settings → CI/CD → Variables) instead
WARN GL068 deploy bad-pipeline.yml 59 set -x enables shell xtrace — every command is printed to the job log after variable expansion, so any command referencing a secret (token in a git URL, Authorization header, --key flag) leaks it in plaintext; drop the x (use set -euo pipefail) or scope tracing with set +x around secret-touching commands
WARN GL081 deploy bad-pipeline.yml 51 job "deploy" issues an OIDC id_tokens: credential with no guard restricting $CI_PIPELINE_SOURCE or the ref — any branch or merge request can mint a cloud-federating token; add a rules:if that restricts to protected refs and condition the cloud trust policy on the ref_protected claimGitLab Code Quality --format codeclimate
The Code Climate JSON that GitLab's Code Quality report reads.
Shows up in: The merge request's Code Quality report on all tiers. With Premium also a Code Quality tab in the pipeline, with Ultimate inline in the merge request diff.

Job that publishes it
glsec:
image:
name: ghcr.io/glsec/glsec:1.23.0
entrypoint: [""]
script:
- glsec --format codeclimate .gitlab-ci.yml > gl-code-quality.json || true
artifacts:
when: always
expire_in: 30 days
reports:
codequality: gl-code-quality.jsonOutput
[
{
"type": "issue",
"check_name": "GL001",
"description": "image \"node:latest\" uses mutable tag \"latest\" — pin to a specific version or digest",
"categories": [
"Security"
],
"severity": "critical",
"fingerprint": "cfed8e2a997e40deb74b918f4acc396d42195f870cf22a398fe75ebb2b0fc481",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 17
}
}
},
{
"type": "issue",
"check_name": "GL002",
"description": "unquoted user-controlled variable $CI_COMMIT_REF_NAME in script — value is set by commit authors and may contain shell metacharacters",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "92c1362f834ee4c3d6330284db814775ac58019514cc4976425fd5461504ac91",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 44
}
}
},
{
"type": "issue",
"check_name": "GL003",
"description": "project include \"platform/ci-templates\" uses mutable ref \"main\" — pin to a commit SHA or tag",
"categories": [
"Security"
],
"severity": "critical",
"fingerprint": "619f2153dfe697bad599cf8a1a3324b941377c56b9682ff66cb65ae7cc5ad16d",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 6
}
}
},
{
"type": "issue",
"check_name": "GL005",
"description": "artifact path \".env\" matches sensitive file pattern \".env\" — exclude secrets from artifacts",
"categories": [
"Security"
],
"severity": "critical",
"fingerprint": "93853b91eb9f7920d5cac9e46651ce7cd8003054bafe92bd28658416f51575e1",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 25
}
}
},
{
"type": "issue",
"check_name": "GL008",
"description": "security scan job \"secret_detection\" has rules: [when: never] as its only rule — the job is switched off in every context",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "212b1fcd50ef808b305ef0e47272f522388d7da99d307388c8b54de7f232c8e4",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 49
}
}
},
{
"type": "issue",
"check_name": "GL011",
"description": "script line downloads and executes remote code without integrity verification: \"curl -sSL https://get.example.com/install.sh | sh\"",
"categories": [
"Security"
],
"severity": "critical",
"fingerprint": "9e50a8ea9041669079c971833ecc87a2dd9c980a34697a2d05c30b60d4777266",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 60
}
}
},
{
"type": "issue",
"check_name": "GL013",
"description": "job \"deploy\" deploys to \"production\" but has no rules: or only: clause — any branch can trigger this deployment",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "540542f517e391a5411ac66ce5e924cea0769eca7e0a4daed19fc14daa4355c2",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 54
}
}
},
{
"type": "issue",
"check_name": "GL017",
"description": "job \"deploy\" deploys or publishes but has no tags: — it can run on any available runner, including untrusted self-hosted runners",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "fdda5788fc3aeb1dc3ad3e9495d085b4300a893872625485cb5a6a1b74db56b1",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 51
}
}
},
{
"type": "issue",
"check_name": "GL019",
"description": "job \"deploy\" deploys or publishes but has no resource_group: — concurrent pipeline runs can cause race conditions or partial deploys",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "b5140e594ed0e69eeb9a5bc89986ced191d454621f37ea2453ffcadca6069e45",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 51
}
}
},
{
"type": "issue",
"check_name": "GL023",
"description": "npm install without lockfile enforcement — use \"npm ci\" to guarantee reproducible dependency resolution",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "b5356c3ba1e482598e88c4118dd6613260a97be39a1993dcd73ec8eee74008a4",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 19
}
}
},
{
"type": "issue",
"check_name": "GL024",
"description": "script uses a pipe without set -o pipefail — failures in all but the last command are silently ignored",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "4be1e70c1d8f9c7d7ef0fec5503ad489458911d7fd783b3b9ae5d6ee0c3e62e1",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 60
}
}
},
{
"type": "issue",
"check_name": "GL025",
"description": "curl/wget request includes user-controlled variable $CI_COMMIT_REF_NAME — whoever names the branch or writes the commit or merge request controls part of the URL or request body",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "66c45cf9982412192fa00ecc470083d576620e11c5bdc537190772423b5c468f",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 61
}
}
},
{
"type": "issue",
"check_name": "GL031",
"description": "DOCKER_TLS_CERTDIR set to empty string — disables TLS on the Docker daemon (port 2375), exposing it to any process on the runner network; set it to \"/certs\" and use port 2376",
"categories": [
"Security"
],
"severity": "critical",
"fingerprint": "e528de3405a8a13bcc4ac611e5e764e49c20eb181522311500c250eeb2d2ddce",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 34
}
}
},
{
"type": "issue",
"check_name": "GL048",
"description": "StrictHostKeyChecking disabled — host identity is not verified, enabling MITM attacks on shared runner networks; use a pre-verified known_hosts entry instead",
"categories": [
"Security"
],
"severity": "critical",
"fingerprint": "ff431ef958d71e74ac7cbe2604b3e2ab9d01b9ff8188c4d3489ca507be1fcced",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 62
}
}
},
{
"type": "issue",
"check_name": "GL053",
"description": "no top-level workflow:rules — pipelines run for every event, including merge requests from forks; gate on $CI_PIPELINE_SOURCE or branch to block untrusted pipeline execution",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "84a14e1c2efa162e1248b2ded46156f772ae89e49ebe66cf1ceb7b847013e776",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 1
}
}
},
{
"type": "issue",
"check_name": "GL054",
"description": "docker-in-docker service \"docker:27.3.1-dind\" requires the runner to run privileged (full host root access) — consider rootless build tools like Kaniko or Buildah, or document the privileged-runner requirement",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "936fb7c61719a76abc038b06b41088784c75b8478957137bd9874b351d95d6f1",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 31
}
}
},
{
"type": "issue",
"check_name": "GL066",
"description": "DOCKER_AUTH_CONFIG contains inline registry credentials (\"auth\" is base64, not encryption) — store it as a masked, protected CI/CD variable (Settings → CI/CD → Variables) instead",
"categories": [
"Security"
],
"severity": "critical",
"fingerprint": "52725c5d51e52f0d4cfe72dd0a81023e36ef9224f7e4391e09e2a3dcf1c5d647",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 11
}
}
},
{
"type": "issue",
"check_name": "GL068",
"description": "set -x enables shell xtrace — every command is printed to the job log after variable expansion, so any command referencing a secret (token in a git URL, Authorization header, --key flag) leaks it in plaintext; drop the x (use set -euo pipefail) or scope tracing with set +x around secret-touching commands",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "d6d0ed60cc97fd9b70390cdd75241d1a94b16fcedab19cc3496bf2e955495574",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 59
}
}
},
{
"type": "issue",
"check_name": "GL081",
"description": "job \"deploy\" issues an OIDC id_tokens: credential with no guard restricting $CI_PIPELINE_SOURCE or the ref — any branch or merge request can mint a cloud-federating token; add a rules:if that restricts to protected refs and condition the cloud trust policy on the ref_protected claim",
"categories": [
"Security"
],
"severity": "major",
"fingerprint": "1b687d2d3ba6575dc10dbe5f9ea5eb1211e85c0ba8c4251f7fb46b354e39256b",
"location": {
"path": "bad-pipeline.yml",
"lines": {
"begin": 51
}
}
}
]JUnit --format junit
Each finding as a failed test case.
Shows up in: The pipeline's Tests tab and the test summary in the merge request, on all tiers.

Job that publishes it
glsec:
image:
name: ghcr.io/glsec/glsec:1.23.0
entrypoint: [""]
script:
- glsec --format junit .gitlab-ci.yml > glsec-junit.xml || true
artifacts:
when: always
expire_in: 30 days
reports:
junit: glsec-junit.xmlOutput
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="glsec" tests="19" failures="19">
<testsuite name="glsec" tests="19" failures="19">
<testcase name="GL001 in job "build"" classname="bad-pipeline.yml:17">
<failure message="image "node:latest" uses mutable tag "latest" — pin to a specific version or digest" type="error">GL001 [error] bad-pipeline.yml:17</failure>
</testcase>
<testcase name="GL002 in job "test"" classname="bad-pipeline.yml:44">
<failure message="unquoted user-controlled variable $CI_COMMIT_REF_NAME in script — value is set by commit authors and may contain shell metacharacters" type="warn">GL002 [warn] bad-pipeline.yml:44</failure>
</testcase>
<testcase name="GL003" classname="bad-pipeline.yml:6">
<failure message="project include "platform/ci-templates" uses mutable ref "main" — pin to a commit SHA or tag" type="error">GL003 [error] bad-pipeline.yml:6</failure>
</testcase>
<testcase name="GL005 in job "build"" classname="bad-pipeline.yml:25">
<failure message="artifact path ".env" matches sensitive file pattern ".env" — exclude secrets from artifacts" type="error">GL005 [error] bad-pipeline.yml:25</failure>
</testcase>
<testcase name="GL008 in job "secret_detection"" classname="bad-pipeline.yml:49">
<failure message="security scan job "secret_detection" has rules: [when: never] as its only rule — the job is switched off in every context" type="warn">GL008 [warn] bad-pipeline.yml:49</failure>
</testcase>
<testcase name="GL011 in job "deploy"" classname="bad-pipeline.yml:60">
<failure message="script line downloads and executes remote code without integrity verification: "curl -sSL https://get.example.com/install.sh | sh"" type="error">GL011 [error] bad-pipeline.yml:60</failure>
</testcase>
<testcase name="GL013 in job "deploy"" classname="bad-pipeline.yml:54">
<failure message="job "deploy" deploys to "production" but has no rules: or only: clause — any branch can trigger this deployment" type="warn">GL013 [warn] bad-pipeline.yml:54</failure>
</testcase>
<testcase name="GL017 in job "deploy"" classname="bad-pipeline.yml:51">
<failure message="job "deploy" deploys or publishes but has no tags: — it can run on any available runner, including untrusted self-hosted runners" type="warn">GL017 [warn] bad-pipeline.yml:51</failure>
</testcase>
<testcase name="GL019 in job "deploy"" classname="bad-pipeline.yml:51">
<failure message="job "deploy" deploys or publishes but has no resource_group: — concurrent pipeline runs can cause race conditions or partial deploys" type="warn">GL019 [warn] bad-pipeline.yml:51</failure>
</testcase>
<testcase name="GL023 in job "build"" classname="bad-pipeline.yml:19">
<failure message="npm install without lockfile enforcement — use "npm ci" to guarantee reproducible dependency resolution" type="warn">GL023 [warn] bad-pipeline.yml:19</failure>
</testcase>
<testcase name="GL024 in job "deploy"" classname="bad-pipeline.yml:60">
<failure message="script uses a pipe without set -o pipefail — failures in all but the last command are silently ignored" type="warn">GL024 [warn] bad-pipeline.yml:60</failure>
</testcase>
<testcase name="GL025 in job "deploy"" classname="bad-pipeline.yml:61">
<failure message="curl/wget request includes user-controlled variable $CI_COMMIT_REF_NAME — whoever names the branch or writes the commit or merge request controls part of the URL or request body" type="warn">GL025 [warn] bad-pipeline.yml:61</failure>
</testcase>
<testcase name="GL031 in job "docker-image"" classname="bad-pipeline.yml:34">
<failure message="DOCKER_TLS_CERTDIR set to empty string — disables TLS on the Docker daemon (port 2375), exposing it to any process on the runner network; set it to "/certs" and use port 2376" type="error">GL031 [error] bad-pipeline.yml:34</failure>
</testcase>
<testcase name="GL048 in job "deploy"" classname="bad-pipeline.yml:62">
<failure message="StrictHostKeyChecking disabled — host identity is not verified, enabling MITM attacks on shared runner networks; use a pre-verified known_hosts entry instead" type="error">GL048 [error] bad-pipeline.yml:62</failure>
</testcase>
<testcase name="GL053" classname="bad-pipeline.yml:1">
<failure message="no top-level workflow:rules — pipelines run for every event, including merge requests from forks; gate on $CI_PIPELINE_SOURCE or branch to block untrusted pipeline execution" type="warn">GL053 [warn] bad-pipeline.yml:1</failure>
</testcase>
<testcase name="GL054 in job "docker-image"" classname="bad-pipeline.yml:31">
<failure message="docker-in-docker service "docker:27.3.1-dind" requires the runner to run privileged (full host root access) — consider rootless build tools like Kaniko or Buildah, or document the privileged-runner requirement" type="warn">GL054 [warn] bad-pipeline.yml:31</failure>
</testcase>
<testcase name="GL066" classname="bad-pipeline.yml:11">
<failure message="DOCKER_AUTH_CONFIG contains inline registry credentials ("auth" is base64, not encryption) — store it as a masked, protected CI/CD variable (Settings → CI/CD → Variables) instead" type="error">GL066 [error] bad-pipeline.yml:11</failure>
</testcase>
<testcase name="GL068 in job "deploy"" classname="bad-pipeline.yml:59">
<failure message="set -x enables shell xtrace — every command is printed to the job log after variable expansion, so any command referencing a secret (token in a git URL, Authorization header, --key flag) leaks it in plaintext; drop the x (use set -euo pipefail) or scope tracing with set +x around secret-touching commands" type="warn">GL068 [warn] bad-pipeline.yml:59</failure>
</testcase>
<testcase name="GL081 in job "deploy"" classname="bad-pipeline.yml:51">
<failure message="job "deploy" issues an OIDC id_tokens: credential with no guard restricting $CI_PIPELINE_SOURCE or the ref — any branch or merge request can mint a cloud-federating token; add a rules:if that restricts to protected refs and condition the cloud trust policy on the ref_protected claim" type="warn">GL081 [warn] bad-pipeline.yml:51</failure>
</testcase>
</testsuite>
</testsuites>SARIF --format sarif
SARIF 2.1.0 with CWE, OWASP and ASVS mappings for each rule.
Shows up in: GitHub Code Scanning and other tools that read SARIF.
Output
{
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "glsec",
"informationUri": "https://github.com/glsec/glsec",
"rules": [
{
"id": "GL001",
"relationships": [
{
"target": {
"id": "CWE-1104",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-3",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "ASVS-V14.2.2",
"toolComponent": {
"name": "OWASP ASVS"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL002",
"relationships": [
{
"target": {
"id": "CWE-78",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-4",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL003",
"relationships": [
{
"target": {
"id": "CWE-829",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-3",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "ASVS-V14.2.1",
"toolComponent": {
"name": "OWASP ASVS"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "ASVS-V14.3.1",
"toolComponent": {
"name": "OWASP ASVS"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL005",
"relationships": [
{
"target": {
"id": "CWE-538",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-6",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL008",
"relationships": [
{
"target": {
"id": "CWE-390",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-1",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "ASVS-V14.3.2",
"toolComponent": {
"name": "OWASP ASVS"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL011",
"relationships": [
{
"target": {
"id": "CWE-494",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-3",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "ASVS-V14.2.1",
"toolComponent": {
"name": "OWASP ASVS"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL013",
"relationships": [
{
"target": {
"id": "CWE-284",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-1",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL017",
"relationships": [
{
"target": {
"id": "CWE-284",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-5",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL019",
"relationships": [
{
"target": {
"id": "CWE-362",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-1",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "ASVS-V14.3.1",
"toolComponent": {
"name": "OWASP ASVS"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL023",
"relationships": [
{
"target": {
"id": "CWE-1104",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-3",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "ASVS-V14.2.2",
"toolComponent": {
"name": "OWASP ASVS"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL024",
"relationships": [
{
"target": {
"id": "CWE-390",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-7",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL025",
"relationships": [
{
"target": {
"id": "CWE-441",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-4",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "ASVS-V14.3.4",
"toolComponent": {
"name": "OWASP ASVS"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL031",
"relationships": [
{
"target": {
"id": "CWE-319",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-7",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL048",
"relationships": [
{
"target": {
"id": "CWE-295",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-7",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL053",
"relationships": [
{
"target": {
"id": "CWE-284",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-4",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL054",
"relationships": [
{
"target": {
"id": "CWE-250",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-7",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL066",
"relationships": [
{
"target": {
"id": "CWE-798",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-6",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "ASVS-V14.3.3",
"toolComponent": {
"name": "OWASP ASVS"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL068",
"relationships": [
{
"target": {
"id": "CWE-532",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-6",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "ASVS-V14.3.3",
"toolComponent": {
"name": "OWASP ASVS"
}
},
"kinds": [
"superset"
]
}
]
},
{
"id": "GL081",
"relationships": [
{
"target": {
"id": "CWE-284",
"toolComponent": {
"name": "CWE"
}
},
"kinds": [
"superset"
]
},
{
"target": {
"id": "CICD-SEC-5",
"toolComponent": {
"name": "OWASP Top 10 CI/CD Security Risks"
}
},
"kinds": [
"superset"
]
}
]
}
]
}
},
"taxonomies": [
{
"name": "CWE",
"version": "4.14",
"organization": "MITRE",
"taxa": [
{
"id": "CWE-78",
"name": "Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')"
},
{
"id": "CWE-829",
"name": "Inclusion of Functionality from Untrusted Control Sphere"
},
{
"id": "CWE-538",
"name": "Insertion of Sensitive Information into Externally-Accessible File or Directory"
},
{
"id": "CWE-390",
"name": "Detection of Error Condition Without Action"
},
{
"id": "CWE-494",
"name": "Download of Code Without Integrity Check"
},
{
"id": "CWE-441",
"name": "Unintended Proxy or Intermediary ('Confused Deputy')"
},
{
"id": "CWE-250",
"name": "Execution with Unnecessary Privileges"
},
{
"id": "CWE-798",
"name": "Use of Hard-coded Credentials"
},
{
"id": "CWE-1104",
"name": "Use of Unmaintained Third-Party Components"
},
{
"id": "CWE-284",
"name": "Improper Access Control"
},
{
"id": "CWE-362",
"name": "Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')"
},
{
"id": "CWE-319",
"name": "Cleartext Transmission of Sensitive Information"
},
{
"id": "CWE-295",
"name": "Improper Certificate Validation"
},
{
"id": "CWE-532",
"name": "Insertion of Sensitive Information into Log File"
}
]
},
{
"name": "OWASP Top 10 CI/CD Security Risks",
"version": "2022",
"organization": "OWASP",
"taxa": [
{
"id": "CICD-SEC-1",
"name": "Insufficient Flow Control Mechanisms"
},
{
"id": "CICD-SEC-5",
"name": "Insufficient PBAC"
},
{
"id": "CICD-SEC-7",
"name": "Insecure System Configuration"
},
{
"id": "CICD-SEC-3",
"name": "Dependency Chain Abuse"
},
{
"id": "CICD-SEC-4",
"name": "Poisoned Pipeline Execution"
},
{
"id": "CICD-SEC-6",
"name": "Insufficient Credential Hygiene"
}
]
},
{
"name": "OWASP ASVS",
"version": "4.0.3",
"organization": "OWASP",
"taxa": [
{
"id": "ASVS-V14.2.2",
"name": "Verify that all components are up to date and pinned to a specific version"
},
{
"id": "ASVS-V14.2.1",
"name": "Verify that all components come from trusted, continually maintained sources"
},
{
"id": "ASVS-V14.3.1",
"name": "Verify that build pipeline configuration is protected from unauthorized modification"
},
{
"id": "ASVS-V14.3.2",
"name": "Verify that security tooling runs in the pipeline and failures block the build"
},
{
"id": "ASVS-V14.3.4",
"name": "Verify that the build environment is isolated"
},
{
"id": "ASVS-V14.3.3",
"name": "Verify that secrets are not present in source code or pipeline logs"
}
]
}
],
"results": [
{
"ruleId": "GL001",
"level": "error",
"message": {
"text": "image \"node:latest\" uses mutable tag \"latest\" — pin to a specific version or digest"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 17
}
}
}
]
},
{
"ruleId": "GL002",
"level": "warning",
"message": {
"text": "unquoted user-controlled variable $CI_COMMIT_REF_NAME in script — value is set by commit authors and may contain shell metacharacters"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 44
}
}
}
]
},
{
"ruleId": "GL003",
"level": "error",
"message": {
"text": "project include \"platform/ci-templates\" uses mutable ref \"main\" — pin to a commit SHA or tag"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 6
}
}
}
]
},
{
"ruleId": "GL005",
"level": "error",
"message": {
"text": "artifact path \".env\" matches sensitive file pattern \".env\" — exclude secrets from artifacts"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 25
}
}
}
]
},
{
"ruleId": "GL008",
"level": "warning",
"message": {
"text": "security scan job \"secret_detection\" has rules: [when: never] as its only rule — the job is switched off in every context"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 49
}
}
}
]
},
{
"ruleId": "GL011",
"level": "error",
"message": {
"text": "script line downloads and executes remote code without integrity verification: \"curl -sSL https://get.example.com/install.sh | sh\""
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 60
}
}
}
]
},
{
"ruleId": "GL013",
"level": "warning",
"message": {
"text": "job \"deploy\" deploys to \"production\" but has no rules: or only: clause — any branch can trigger this deployment"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 54
}
}
}
]
},
{
"ruleId": "GL017",
"level": "warning",
"message": {
"text": "job \"deploy\" deploys or publishes but has no tags: — it can run on any available runner, including untrusted self-hosted runners"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 51
}
}
}
]
},
{
"ruleId": "GL019",
"level": "warning",
"message": {
"text": "job \"deploy\" deploys or publishes but has no resource_group: — concurrent pipeline runs can cause race conditions or partial deploys"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 51
}
}
}
]
},
{
"ruleId": "GL023",
"level": "warning",
"message": {
"text": "npm install without lockfile enforcement — use \"npm ci\" to guarantee reproducible dependency resolution"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 19
}
}
}
]
},
{
"ruleId": "GL024",
"level": "warning",
"message": {
"text": "script uses a pipe without set -o pipefail — failures in all but the last command are silently ignored"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 60
}
}
}
]
},
{
"ruleId": "GL025",
"level": "warning",
"message": {
"text": "curl/wget request includes user-controlled variable $CI_COMMIT_REF_NAME — whoever names the branch or writes the commit or merge request controls part of the URL or request body"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 61
}
}
}
]
},
{
"ruleId": "GL031",
"level": "error",
"message": {
"text": "DOCKER_TLS_CERTDIR set to empty string — disables TLS on the Docker daemon (port 2375), exposing it to any process on the runner network; set it to \"/certs\" and use port 2376"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 34
}
}
}
]
},
{
"ruleId": "GL048",
"level": "error",
"message": {
"text": "StrictHostKeyChecking disabled — host identity is not verified, enabling MITM attacks on shared runner networks; use a pre-verified known_hosts entry instead"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 62
}
}
}
]
},
{
"ruleId": "GL053",
"level": "warning",
"message": {
"text": "no top-level workflow:rules — pipelines run for every event, including merge requests from forks; gate on $CI_PIPELINE_SOURCE or branch to block untrusted pipeline execution"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 1
}
}
}
]
},
{
"ruleId": "GL054",
"level": "warning",
"message": {
"text": "docker-in-docker service \"docker:27.3.1-dind\" requires the runner to run privileged (full host root access) — consider rootless build tools like Kaniko or Buildah, or document the privileged-runner requirement"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 31
}
}
}
]
},
{
"ruleId": "GL066",
"level": "error",
"message": {
"text": "DOCKER_AUTH_CONFIG contains inline registry credentials (\"auth\" is base64, not encryption) — store it as a masked, protected CI/CD variable (Settings → CI/CD → Variables) instead"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 11
}
}
}
]
},
{
"ruleId": "GL068",
"level": "warning",
"message": {
"text": "set -x enables shell xtrace — every command is printed to the job log after variable expansion, so any command referencing a secret (token in a git URL, Authorization header, --key flag) leaks it in plaintext; drop the x (use set -euo pipefail) or scope tracing with set +x around secret-touching commands"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 59
}
}
}
]
},
{
"ruleId": "GL081",
"level": "warning",
"message": {
"text": "job \"deploy\" issues an OIDC id_tokens: credential with no guard restricting $CI_PIPELINE_SOURCE or the ref — any branch or merge request can mint a cloud-federating token; add a rules:if that restricts to protected refs and condition the cloud trust policy on the ref_protected claim"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "bad-pipeline.yml"
},
"region": {
"startLine": 51
}
}
}
]
}
]
}
]
}JSON --format json
One object per finding with its OWASP and ASVS mappings, for scripts and dashboards.
Shows up in: Wherever you send it.
Output
{
"findings": [
{
"rule": "GL001",
"severity": "error",
"job": "build",
"file": "bad-pipeline.yml",
"line": 17,
"message": "image \"node:latest\" uses mutable tag \"latest\" — pin to a specific version or digest",
"owasp": [
"CICD-SEC-3"
],
"asvs": [
"ASVS-V14.2.2"
]
},
{
"rule": "GL002",
"severity": "warn",
"job": "test",
"file": "bad-pipeline.yml",
"line": 44,
"message": "unquoted user-controlled variable $CI_COMMIT_REF_NAME in script — value is set by commit authors and may contain shell metacharacters",
"owasp": [
"CICD-SEC-4"
]
},
{
"rule": "GL003",
"severity": "error",
"file": "bad-pipeline.yml",
"line": 6,
"message": "project include \"platform/ci-templates\" uses mutable ref \"main\" — pin to a commit SHA or tag",
"owasp": [
"CICD-SEC-3"
],
"asvs": [
"ASVS-V14.2.1",
"ASVS-V14.3.1"
]
},
{
"rule": "GL005",
"severity": "error",
"job": "build",
"file": "bad-pipeline.yml",
"line": 25,
"message": "artifact path \".env\" matches sensitive file pattern \".env\" — exclude secrets from artifacts",
"owasp": [
"CICD-SEC-6"
]
},
{
"rule": "GL008",
"severity": "warn",
"job": "secret_detection",
"file": "bad-pipeline.yml",
"line": 49,
"message": "security scan job \"secret_detection\" has rules: [when: never] as its only rule — the job is switched off in every context",
"owasp": [
"CICD-SEC-1"
],
"asvs": [
"ASVS-V14.3.2"
]
},
{
"rule": "GL011",
"severity": "error",
"job": "deploy",
"file": "bad-pipeline.yml",
"line": 60,
"message": "script line downloads and executes remote code without integrity verification: \"curl -sSL https://get.example.com/install.sh | sh\"",
"owasp": [
"CICD-SEC-3"
],
"asvs": [
"ASVS-V14.2.1"
]
},
{
"rule": "GL013",
"severity": "warn",
"job": "deploy",
"file": "bad-pipeline.yml",
"line": 54,
"message": "job \"deploy\" deploys to \"production\" but has no rules: or only: clause — any branch can trigger this deployment",
"owasp": [
"CICD-SEC-1"
]
},
{
"rule": "GL017",
"severity": "warn",
"job": "deploy",
"file": "bad-pipeline.yml",
"line": 51,
"message": "job \"deploy\" deploys or publishes but has no tags: — it can run on any available runner, including untrusted self-hosted runners",
"owasp": [
"CICD-SEC-5"
]
},
{
"rule": "GL019",
"severity": "warn",
"job": "deploy",
"file": "bad-pipeline.yml",
"line": 51,
"message": "job \"deploy\" deploys or publishes but has no resource_group: — concurrent pipeline runs can cause race conditions or partial deploys",
"owasp": [
"CICD-SEC-1"
],
"asvs": [
"ASVS-V14.3.1"
]
},
{
"rule": "GL023",
"severity": "warn",
"job": "build",
"file": "bad-pipeline.yml",
"line": 19,
"message": "npm install without lockfile enforcement — use \"npm ci\" to guarantee reproducible dependency resolution",
"owasp": [
"CICD-SEC-3"
],
"asvs": [
"ASVS-V14.2.2"
]
},
{
"rule": "GL024",
"severity": "warn",
"job": "deploy",
"file": "bad-pipeline.yml",
"line": 60,
"message": "script uses a pipe without set -o pipefail — failures in all but the last command are silently ignored",
"owasp": [
"CICD-SEC-7"
]
},
{
"rule": "GL025",
"severity": "warn",
"job": "deploy",
"file": "bad-pipeline.yml",
"line": 61,
"message": "curl/wget request includes user-controlled variable $CI_COMMIT_REF_NAME — whoever names the branch or writes the commit or merge request controls part of the URL or request body",
"owasp": [
"CICD-SEC-4"
],
"asvs": [
"ASVS-V14.3.4"
]
},
{
"rule": "GL031",
"severity": "error",
"job": "docker-image",
"file": "bad-pipeline.yml",
"line": 34,
"message": "DOCKER_TLS_CERTDIR set to empty string — disables TLS on the Docker daemon (port 2375), exposing it to any process on the runner network; set it to \"/certs\" and use port 2376",
"owasp": [
"CICD-SEC-7"
]
},
{
"rule": "GL048",
"severity": "error",
"job": "deploy",
"file": "bad-pipeline.yml",
"line": 62,
"message": "StrictHostKeyChecking disabled — host identity is not verified, enabling MITM attacks on shared runner networks; use a pre-verified known_hosts entry instead",
"owasp": [
"CICD-SEC-7"
]
},
{
"rule": "GL053",
"severity": "warn",
"file": "bad-pipeline.yml",
"line": 1,
"message": "no top-level workflow:rules — pipelines run for every event, including merge requests from forks; gate on $CI_PIPELINE_SOURCE or branch to block untrusted pipeline execution",
"owasp": [
"CICD-SEC-4"
]
},
{
"rule": "GL054",
"severity": "warn",
"job": "docker-image",
"file": "bad-pipeline.yml",
"line": 31,
"message": "docker-in-docker service \"docker:27.3.1-dind\" requires the runner to run privileged (full host root access) — consider rootless build tools like Kaniko or Buildah, or document the privileged-runner requirement",
"owasp": [
"CICD-SEC-7"
]
},
{
"rule": "GL066",
"severity": "error",
"file": "bad-pipeline.yml",
"line": 11,
"message": "DOCKER_AUTH_CONFIG contains inline registry credentials (\"auth\" is base64, not encryption) — store it as a masked, protected CI/CD variable (Settings → CI/CD → Variables) instead",
"owasp": [
"CICD-SEC-6"
],
"asvs": [
"ASVS-V14.3.3"
]
},
{
"rule": "GL068",
"severity": "warn",
"job": "deploy",
"file": "bad-pipeline.yml",
"line": 59,
"message": "set -x enables shell xtrace — every command is printed to the job log after variable expansion, so any command referencing a secret (token in a git URL, Authorization header, --key flag) leaks it in plaintext; drop the x (use set -euo pipefail) or scope tracing with set +x around secret-touching commands",
"owasp": [
"CICD-SEC-6"
],
"asvs": [
"ASVS-V14.3.3"
]
},
{
"rule": "GL081",
"severity": "warn",
"job": "deploy",
"file": "bad-pipeline.yml",
"line": 51,
"message": "job \"deploy\" issues an OIDC id_tokens: credential with no guard restricting $CI_PIPELINE_SOURCE or the ref — any branch or merge request can mint a cloud-federating token; add a rules:if that restricts to protected refs and condition the cloud trust policy on the ref_protected claim",
"owasp": [
"CICD-SEC-5"
]
}
]
}Tiers as listed in GitLab's documentation for Code Quality and unit test reports. All flags are in the README.