Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

GitLab CI security scanners: SAST, DAST, secrets and dependencies

By updated

Improve this page

GitLab logo

Your pipeline builds, tests and deploys, but an npm dependency contains a critical CVE (a known vulnerability, publicly listed under a unique identifier), a developer committed an AWS token, and your Docker image ships a vulnerable OpenSSL. You will only find out after the incident. GitLab builds five security scanners straight into CI/CD: they analyze your code, your dependencies, your images and your secrets on every pipeline, with a vulnerability report that developers can act on.

Each one covers a family of risks: SAST analyzes the source code without running it, Secret Detection spots committed tokens, API keys and passwords, Dependency Scanning looks for known CVEs in the libraries the project depends on, Container Scanning inspects the Docker images you build, and DAST tests the deployed application the way an external attacker would. Each scanner has its own section below.

Branch pipelines vs merge request pipelines. By default, the scanners run on branch pipelines (on every push). To run them specifically on merge request pipelines and show the vulnerability diff, add the variable SAST_ENABLE_MR_PIPELINES: "true" (and its equivalent for each scanner) or use the latest templates (Jobs/SAST.latest.gitlab-ci.yml).

What you will learn

  • Enable each scanner in a GitLab CI pipeline with the official templates
  • Read a vulnerability report in a merge request and prioritize the fixes
  • Configure Secret Detection to detect committed tokens before the merge
  • Scan your Docker images built in the same pipeline
  • Configure the governance mechanisms to react to detected vulnerabilities
  • Choose between Auto DevOps and manual configuration

In which context?

The GitLab security scanners meet a precise need: detect vulnerabilities as early as possible in the development cycle, ideally at the merge request, before the code reaches the main branch.

Without these scanners, teams discover security problems:

  • In production, when an external scanner (Trivy, Snyk) detects a critical CVE on a deployed image
  • During an audit, when they find that secrets have been lying in the Git history for months
  • After an incident, when an attacker exploits a dependency vulnerability that had been known for a long time

In daily practice, the scanners are used to:

  • Catch secrets (AWS tokens, SSH keys, passwords) in commits before they are merged into the main branch
  • Check dependencies on every update of package-lock.json, go.sum or requirements.txt
  • Validate Docker images before pushing them to the Container Registry
  • Test the attack surface of an application deployed in a review app
  • Produce reports for compliance audits (see Audit & compliance)

Prerequisites

  • A GitLab project with a working pipeline (see First pipeline)
  • Understand templates and includes (the scanners use include: template:)
  • For Container Scanning: a job that builds a Docker image (see Registries)
  • For DAST: a deployed application that is reachable (review app or staging environment)

The 5 scanners explained

Overview

Source code ──► SAST ──────────► Vulnerabilities in the code
│
├──► Secret Detection ──► Tokens/keys detected
│
├──► Dependency Scanning ► CVEs in the dependencies
│
└──► Container Scanning ─► CVEs in the Docker image
│
Deployed application ──► DAST ─────────► Web vulnerabilities (XSS, injection, etc.)

SAST, Static Application Security Testing

SAST analyzes your source code without running it. It detects programming flaws: SQL injections, XSS, use of dangerous functions, poor handling of user input.

GitLab automatically detects the languages and enables the matching analyzers. Depending on the project, several analyzers can run in parallel: Semgrep (Python, JS, Go, Java…), SpotBugs (Java, Kotlin), Sobelow (Elixir), PMD (Apex), Kubesec (Kubernetes manifests) and GitLab Advanced SAST (Ultimate, inter-procedural analysis).

include:
- template: Jobs/SAST.gitlab-ci.yml

That's it. GitLab automatically creates one or more jobs (e.g. semgrep-sast, spotbugs-sast) in the test stage which:

  1. Detect the project's languages
  2. Run the appropriate analyzers
  3. Produce a gl-sast-report.json report (GitLab Security Report format, not SARIF)
  4. Show the results in the merge request (Ultimate)

Common customization:

include:
- template: Jobs/SAST.gitlab-ci.yml
variables:
SAST_EXCLUDED_PATHS: "spec,test,tests,tmp,vendor,node_modules"
SEARCH_MAX_DEPTH: 4

Secret Detection

The Secret Detection scanner goes through the commits to find secrets (tokens, API keys, passwords, certificates) that should not have been committed.

include:
- template: Jobs/Secret-Detection.gitlab-ci.yml

Among other things, it detects:

Type of secretExample pattern
AWS keysAKIA[0-9A-Z]{16}
GitLab tokensglpat-[0-9a-zA-Z_-]{20}
SSH/RSA private keys-----BEGIN RSA PRIVATE KEY-----
GitHub tokensghp_[0-9a-zA-Z]{36}
Clear-text passwordsVariables containing password, secret, token

Initial scan of the history (run only once):

After enabling Secret Detection, run a pipeline manually with the variable SECRET_DETECTION_HISTORIC_SCAN: "true" to scan the whole Git history. It is a one-shot scan, do not leave it in your permanent configuration, because it makes every pipeline considerably longer.

# Single manual pipeline for the historic scan
# Build > Pipelines > New pipeline > Add variable:
# SECRET_DETECTION_HISTORIC_SCAN = true

Standard (daily) configuration:

include:
- template: Jobs/Secret-Detection.gitlab-ci.yml
variables:
SECRET_DETECTION_EXCLUDED_PATHS: "tests/"

Dependency Scanning

Dependency Scanning analyzes your lock files (package-lock.json, go.sum, Gemfile.lock, requirements.txt) to identify the dependencies with known CVEs (NVD database and GitLab Advisory Database).

include:
- template: Jobs/Dependency-Scanning.gitlab-ci.yml

It produces a report listing each vulnerable dependency with:

  • The CVE identifier
  • The severity (Critical, High, Medium, Low)
  • The fixed version (when there is one)
  • The link to the advisory

Customization:

include:
- template: Jobs/Dependency-Scanning.gitlab-ci.yml
variables:
DS_EXCLUDED_PATHS: "vendor/"

Container Scanning

Container Scanning analyzes the Docker image that your pipeline has just built to find vulnerabilities in the system packages (apt, apk, rpm) and the embedded libraries.

include:
- template: Jobs/Container-Scanning.gitlab-ci.yml
container_scanning:
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

DAST, Dynamic Application Security Testing

DAST tests an application that is deployed and reachable by simulating real attacks: SQL injections, XSS, CSRF, missing security headers, etc.

include:
- template: Security/DAST.gitlab-ci.yml
variables:
DAST_TARGET_URL: "https://staging.mon-app.com"

DAST is the heaviest scanner: it launches a headless browser and explores the application. It is generally run on a staging environment or a review app, not in production.

Customization:

include:
- template: Security/DAST.gitlab-ci.yml
variables:
DAST_TARGET_URL: "https://$CI_ENVIRONMENT_SLUG.review.mon-app.com"
DAST_FULL_SCAN_ENABLED: "false" # Quick scan by default
DAST_BROWSER_SCAN: "true" # Browser-based scan (recommended)

Complete security pipeline

Here is a pipeline that combines the 5 scanners with the build and test steps:

stages:
- build
- test
- deploy
- dast # Stage required by the DAST template
include:
- template: Jobs/SAST.gitlab-ci.yml
- template: Jobs/Secret-Detection.gitlab-ci.yml
- template: Jobs/Dependency-Scanning.gitlab-ci.yml
- template: Jobs/Container-Scanning.gitlab-ci.yml
- template: Security/DAST.gitlab-ci.yml
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
DAST_TARGET_URL: "https://$CI_ENVIRONMENT_SLUG.staging.mon-app.com"
build-image:
stage: build
image: docker:27@sha256:aa3df78ecf320f5fafdce71c659f1629e96e9de0968305fe1de670e0ca9176ce
services:
- docker:27-dind@sha256:aa3df78ecf320f5fafdce71c659f1629e96e9de0968305fe1de670e0ca9176ce
variables:
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
script:
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
test:
stage: test
image: node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2
script:
- npm ci
- npm test
deploy-staging:
stage: deploy
script:
- deploy-to-staging.sh
environment:
name: staging/$CI_COMMIT_REF_SLUG
url: https://$CI_ENVIRONMENT_SLUG.staging.mon-app.com
rules:
- if: $CI_MERGE_REQUEST_IID

The build-image job reads the registry password on standard input with --password-stdin, rather than in a -p argument: a command-line argument shows up in the process list (ps) and can end up in the job traces.

Reading the vulnerability reports

In the merge request

When the scanners run on an MR, GitLab shows a security widget summarizing:

  • The number of new vulnerabilities introduced by the MR
  • The number of vulnerabilities fixed
  • The breakdown by severity (Critical, High, Medium, Low)

Each vulnerability is clickable and displays:

  • The description of the flaw
  • The file and line concerned (SAST) or the dependency (Dependency Scanning)
  • The severity and the CVSS score
  • The recommended solution (update, workaround)
  • The CVE / CWE identifier

In the Security Dashboard

The Security Dashboard (Security & Compliance > Vulnerability Report) centralizes all the vulnerabilities detected on the default branch. It lets you:

  • Filter by scanner, severity, status
  • Mark a vulnerability as "confirmed", "dismissed" or "resolved"
  • Create a GitLab issue directly from a vulnerability
  • Track the evolution over time

First governance mechanisms

Two complementary mechanisms let you react to the scanners' results:

1. allow_failure: false, detect scanner failures

By default, the scanners use allow_failure: true: if the scanner crashes (timeout, network error, image unavailable), the pipeline continues. By switching to false, a failure of the scanner itself blocks the pipeline, but the vulnerabilities detected block nothing:

semgrep-sast:
allow_failure: false # Blocks if the scanner crashes, NOT if findings exist

2. Merge request approval policies, block on findings (Ultimate)

To prevent the merge when critical vulnerabilities are detected, configure a merge request approval policy in a security policy project:

  1. Create a Security Policy Project (Security & Compliance > Policies > New policy project)

  2. Create a Merge request approval policy with a scan_finding rule:

    • Minimum severity: critical or high
    • Scanners concerned: SAST, Dependency Scanning, Container Scanning, Secret Detection
    • Required approvers: members of the security team
  3. Link the policy project to your projects (Settings > Security & Compliance > Security policy project)

Auto DevOps vs manual configuration

GitLab offers Auto DevOps, which automatically enables all the scanners without configuration. It is tempting, but here is when to choose each approach:

CriterionAuto DevOpsManual configuration
Setup timeInstant15 min
ControlLimitedTotal
Scanners enabledAllYour choice
CustomizationVia variablesVia full YAML
Recommended forPrototyping, small projectsProduction, compliance

To enable Auto DevOps: Settings > CI/CD > Auto DevOps > Enable.

For most teams in production, manual configuration via include: template: is preferable because it gives explicit control over what runs.

Troubleshooting

SymptomLikely causeSolution
Scanner not runTemplate not included, or template rules that exclude the jobCheck include: and the template variables
Pipeline failed with no job at all, no messageA job (even one from an include) uses a stage missing from stages:Open the project's CI linter: it names the missing stage
SAST: 0 findings on a project with codeLanguage not detectedAdd SAST_DEFAULT_ANALYZERS explicitly
Container Scanning failsImage not foundCheck CS_IMAGE and that the build job succeeded
Secret Detection: false positivesPattern too broadAdd a .gitlab-secret-detection.toml file to exclude
DAST timeoutApplication not reachable from the runnerCheck the URL and the runner's network
Security widget missing in the MRInsufficient license or missing reportsCheck the GitLab tier and the jobs' artifacts
Dependency Scanning: 0 findingsLock file missingGenerate package-lock.json or go.sum beforehand

Hands-on lab

The lab "Turn the scanners on, and read what they say" replays this guide on a GitLab CE forge set up on a virtual machine, with its runner and its registry. The project's pipeline only runs the application tests: it is green, and nobody has ever looked at what the repository holds from a security point of view. You enable two of the three scanners available on the Free tier, SAST and Secret Detection, with the same include: template: lines as above.

Because the lab instance runs Community Edition, it has no Security Dashboard and no security widget in the merge request: the report is a JSON file dropped as an artifact (gl-sast-report.json, gl-secret-detection-report.json), and reading it is part of the job. A committed token has been sitting in the code for a long time, which calls for the historic scan described above, run once by hand. You fix the flaw the report points at, then record in a LECTURE.md file the file and the line it named.

Grading is taken on what the forge really produced: two successful jobs that dropped a readable report, a report that names the token, and a fresh scan on main that no longer names it while the tests still pass. Deleting the line without running the scanner is therefore not enough. The lab starts with dsoxlab run gl-21-activer-les-scanners, after dsoxlab provision; installing DSOXLab is described in the DSOXLab documentation.

Key takeaways

  • GitLab builds in 5 security scanners that you enable via include: template:, no external tools needed
  • SAST, Secret Detection and Container Scanning are available on all tiers (including Free, the tier of a CE instance), Dependency Scanning and DAST require Ultimate
  • Results appear directly in the merge request (Ultimate widget), on Free, the JSON reports can be downloaded
  • Container Scanning analyzes the Docker images built in the same pipeline
  • DAST tests the deployed application, it needs a reachable environment and the dast stage
  • allow_failure: false detects scanner failures, to block on findings, configure merge request approval policies (Ultimate)
  • Auto DevOps enables everything automatically, but manual configuration offers more control
  • These scanners complement the level 4 security module, which covers protecting the pipelines themselves

Knowledge check

Check that the essentials of this lesson are in place. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn