glsec

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 claim

GitLab 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.

GitLab merge request Code Quality report: Code Quality scans found 19 new findings. The first nine are shown: seven Critical (mutable image tag node:latest, include from the main branch, .env in artifacts, curl piped to sh, inline registry credentials, StrictHostKeyChecking disabled, Docker daemon TLS disabled) and two Major (OIDC token without a guard, set -x), each with its line in bad-pipeline.yml
The Code Quality report of a merge request on GitLab.com (Free tier) that brings in this file: 19 new findings, the first nine shown. Open it on GitLab.
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.json
Output
[
  {
    "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.

GitLab pipeline Tests tab for the glsec-junit job: 19 tests, 19 failures. The first nine failed test cases are shown, one per finding with its line in bad-pipeline.yml, such as GL001 in job build at line 17, GL002 in job test at line 44, GL003 at line 6 and GL008 in job secret_detection at line 49
The Tests tab of a pipeline on GitLab.com (Free tier) that scans this file: every finding is a failed test case, the first nine shown. Open it on GitLab.
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.xml
Output
<?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 &#34;build&#34;" classname="bad-pipeline.yml:17">
      <failure message="image &#34;node:latest&#34; uses mutable tag &#34;latest&#34; — pin to a specific version or digest" type="error">GL001 [error] bad-pipeline.yml:17</failure>
    </testcase>
    <testcase name="GL002 in job &#34;test&#34;" 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 &#34;platform/ci-templates&#34; uses mutable ref &#34;main&#34; — pin to a commit SHA or tag" type="error">GL003 [error] bad-pipeline.yml:6</failure>
    </testcase>
    <testcase name="GL005 in job &#34;build&#34;" classname="bad-pipeline.yml:25">
      <failure message="artifact path &#34;.env&#34; matches sensitive file pattern &#34;.env&#34; — exclude secrets from artifacts" type="error">GL005 [error] bad-pipeline.yml:25</failure>
    </testcase>
    <testcase name="GL008 in job &#34;secret_detection&#34;" classname="bad-pipeline.yml:49">
      <failure message="security scan job &#34;secret_detection&#34; 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 &#34;deploy&#34;" classname="bad-pipeline.yml:60">
      <failure message="script line downloads and executes remote code without integrity verification: &#34;curl -sSL https://get.example.com/install.sh | sh&#34;" type="error">GL011 [error] bad-pipeline.yml:60</failure>
    </testcase>
    <testcase name="GL013 in job &#34;deploy&#34;" classname="bad-pipeline.yml:54">
      <failure message="job &#34;deploy&#34; deploys to &#34;production&#34; 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 &#34;deploy&#34;" classname="bad-pipeline.yml:51">
      <failure message="job &#34;deploy&#34; 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 &#34;deploy&#34;" classname="bad-pipeline.yml:51">
      <failure message="job &#34;deploy&#34; 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 &#34;build&#34;" classname="bad-pipeline.yml:19">
      <failure message="npm install without lockfile enforcement — use &#34;npm ci&#34; to guarantee reproducible dependency resolution" type="warn">GL023 [warn] bad-pipeline.yml:19</failure>
    </testcase>
    <testcase name="GL024 in job &#34;deploy&#34;" 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 &#34;deploy&#34;" 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 &#34;docker-image&#34;" 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 &#34;/certs&#34; and use port 2376" type="error">GL031 [error] bad-pipeline.yml:34</failure>
    </testcase>
    <testcase name="GL048 in job &#34;deploy&#34;" 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 &#34;docker-image&#34;" classname="bad-pipeline.yml:31">
      <failure message="docker-in-docker service &#34;docker:27.3.1-dind&#34; 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 (&#34;auth&#34; 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 &#34;deploy&#34;" 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 &#34;deploy&#34;" classname="bad-pipeline.yml:51">
      <failure message="job &#34;deploy&#34; 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.