GitLab CI Security Scanning
Learn GitLab CI security scanning with SAST, DAST, secret detection, dependency scanning, container scanning, security reports, and DevSecOps best practices for secure pipelines.
GitLab CI security scanning is one of the fastest ways to bring DevSecOps into a real delivery workflow. Instead of treating security as a separate audit that happens after development is finished, DevSecOps makes security part of the pipeline itself. The same GitLab CI/CD process that builds, tests, and deploys your application can also run static analysis, scan dependencies, inspect container images, detect leaked secrets, and test a running application for common web vulnerabilities.
That approach is often described as shifting security left. The phrase means moving security checks earlier in the software lifecycle, closer to the moment code is written and reviewed. When a vulnerability is discovered during a merge request instead of in production, the cost of fixing it is dramatically lower. The developer still remembers the code change, the blast radius is smaller, and the team can fix the issue before it becomes part of a release.
GitLab is particularly strong here because it does not bolt security onto CI as an afterthought. It provides built-in security analyzers, security report formats, merge request widgets, dashboards, and vulnerability workflows. For teams building modern delivery pipelines, that makes GitLab a practical DevSecOps platform rather than just a CI runner.
What DevSecOps Means in Practice
DevSecOps is not just “run a scanner sometimes.” It is a mindset and an operating model.
In a traditional workflow, development writes code, operations deploys it, and security reviews it near the end. That sequence creates friction. Security findings arrive late, release dates slip, and teams start seeing security as a blocker.
In a DevSecOps workflow:
- developers receive security feedback during coding and review
- pipelines automatically enforce baseline controls
- known vulnerable libraries are detected before release
- accidentally committed secrets are caught quickly
- container images are checked before deployment
- web applications are tested in a running environment
- security teams get centralized visibility without manually reviewing every commit
This is why “shift left” matters. The earlier a problem is identified, the cheaper it is to fix and the less likely it is to reach customers.
GitLab Built-In Security Scanning Overview
GitLab provides a suite of security scanners and report integrations. Availability depends on your GitLab edition and tier. In general, GitLab Ultimate offers the richest security experience, especially for dashboards, governance, and vulnerability management, while some security capabilities and templates are available on Free or Premium as well.
The major scanners you will see in GitLab CI security scanning are:
- SAST for source-code level issues
- Secret Detection for exposed credentials and tokens
- Dependency Scanning for vulnerable third-party packages
- Container Scanning for OS and image vulnerabilities
- DAST for testing a live running application
The important idea is that each scanner looks at a different layer of risk:
- your code
- your secrets
- your dependencies
- your container base image
- your deployed runtime behavior
No single scanner covers everything. The value comes from using multiple scanners together.
SAST: Static Application Security Testing
Static Application Security Testing (SAST) analyzes source code without running the application. It looks for risky patterns, unsafe API usage, insecure coding constructs, and common weaknesses such as injection risks, weak cryptography usage, insecure deserialization, or insecure file handling.
Because SAST runs before deployment, it is one of the purest forms of shift-left security. It catches problems while developers are still working in branches and merge requests.
What SAST scans
A GitLab SAST analyzer inspects the code repository and attempts to identify vulnerabilities in supported languages and frameworks. Depending on analyzer support and configuration, it can inspect languages such as:
- JavaScript and TypeScript
- Python
- Java
- Go
- Ruby
- PHP
- C and C++
- C# and .NET
- Kotlin and others, depending on the analyzer set
The exact analyzer selection depends on the project contents. GitLab automatically chooses analyzers based on the repository language and the included SAST template.
How to enable SAST
The simplest way to enable SAST is to include GitLab’s security template:
include:
- template: Security/SAST.gitlab-ci.yml
That one line is often enough to get an initial scan running. GitLab adds the SAST job definitions for you. In many projects, this is the fastest first win for GitLab DevSecOps adoption.
SAST best practices
To get more value from SAST:
- run it on merge requests, not only default branch pipelines
- review findings as part of code review
- tune exclusions for generated code or vendor folders
- treat high-confidence findings first
- combine SAST with dependency and secret scanning for broader coverage
SAST is excellent for code patterns, but it cannot tell you everything about a running system. That is why GitLab security scanning includes other analyzers too.
Secret Detection
One of the most common CI security failures is not a code vulnerability at all. It is a secret that accidentally got committed to Git: an API key, cloud credential, SSH private key, database password, access token, or webhook secret.
Once a secret lands in Git history, the problem is bigger than deleting it from the latest commit. The credential may already have been cloned, logged, mirrored, or exposed in a merge request. The correct response often includes revoking and rotating the secret.
GitLab Secret Detection looks for known secret patterns and high-risk tokens in the repository history or commit content, depending on configuration.
What secret detection catches
Secret detection commonly finds:
- AWS access keys
- GitLab or GitHub tokens
- Slack, Stripe, or Twilio keys
- generic bearer tokens
- private keys
- passwords embedded in config files
How to enable secret detection
Add the template:
include:
- template: Security/Secret-Detection.gitlab-ci.yml
This makes secret scanning part of the pipeline so developers get early feedback when something sensitive appears in the codebase.
Why this matters
Secret detection is one of the highest-value scanners because it addresses a very common and very dangerous mistake. Even a mature team can accidentally commit a credential. Catching it in CI gives you a chance to stop the merge and rotate the secret before production risk grows.
Dependency Scanning
Modern software depends heavily on open-source libraries. A Node.js application might use dozens or hundreds of transitive packages. A Python service may rely on libraries listed in requirements.txt or poetry.lock. A Java application may pull dependencies through Maven or Gradle. Those libraries save time, but they also introduce supply-chain risk.
Dependency Scanning checks manifest and lock files against vulnerability databases to identify packages with known CVEs or advisories.
What dependency scanning inspects
GitLab dependency scanning can detect vulnerable packages in files such as:
package.jsonand lock files for Node.jsrequirements.txt,Pipfile.lock, or Poetry files for Python- Maven and Gradle descriptors for Java
- Bundler files for Ruby
- Composer files for PHP
How to enable dependency scanning
Add the template:
include:
- template: Security/Dependency-Scanning.gitlab-ci.yml
Why dependency scanning matters
You may write secure application code and still ship a vulnerable package. That is why dependency scanning is a core part of GitLab security reports. It helps answer a critical question: Are we pulling in known vulnerable software?
Dependency scanning is also valuable after a merge. New CVEs are published every day. A package that looked fine last month may become high risk today. Running dependency scans regularly helps you spot those changes.
Container Scanning
If your application is built into Docker images, security does not stop at package.json or requirements.txt. The image itself includes an operating system layer, system packages, libraries, and possibly outdated base images.
Container Scanning examines built container images for OS-level and package-level vulnerabilities.
What container scanning checks
Container scanners typically inspect:
- base image packages such as Alpine, Debian, or Ubuntu packages
- OS libraries like OpenSSL or glibc
- vulnerable installed binaries
- image layer contents relevant to known CVEs
This is especially important when teams use old or unpinned base images. An app can have clean source code and still ship a risky image because the underlying OS layer is outdated.
How to enable container scanning
Add the template:
include:
- template: Security/Container-Scanning.gitlab-ci.yml
You usually also provide the image to scan, often through a variable such as CS_IMAGE.
container_scanning:
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
DAST: Dynamic Application Security Testing
Dynamic Application Security Testing (DAST) works differently from SAST. Instead of scanning source code, it tests a running application from the outside. In other words, DAST behaves more like an attacker probing the deployed app.
This makes DAST useful for finding issues that appear only when the application is running, such as:
- exposed HTTP headers
- authentication flow issues
- reflected XSS behavior
- insecure redirects
- weak TLS or misconfigurations
- endpoints that behave unsafely under malicious input
What DAST needs
Because DAST scans a live application, it needs a reachable target URL, usually a review app, staging environment, or temporary test deployment.
How to enable DAST
Add the template:
include:
- template: Security/DAST.gitlab-ci.yml
Then provide a target URL:
dast:
variables:
DAST_WEBSITE: https://staging.example.com
When to use DAST
DAST is strongest when you already have:
- a stable staging or review environment
- realistic seed data for testing
- isolated infrastructure so scan traffic does not affect production users
DAST complements SAST. SAST looks at code structure; DAST looks at application behavior.
Enabling Multiple GitLab Security Scanners Together
You can combine security templates in a single .gitlab-ci.yml file:
stages:
- test
- security
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
- template: Security/Container-Scanning.gitlab-ci.yml
- template: Security/DAST.gitlab-ci.yml
variables:
CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
DAST_WEBSITE: https://staging.example.com
That gives you broad GitLab CI security scanning coverage with minimal YAML.
Reading Security Reports in the Merge Request UI
One of GitLab’s biggest advantages is not just that the scanners run, but that the results are visible where developers already work.
When a merge request pipeline includes security jobs, GitLab can show findings directly in the merge request UI. Reviewers can see whether a branch introduces new vulnerabilities, which scanner reported them, the severity, and often the affected file or dependency.
That improves adoption because developers do not have to leave the review flow and open a separate security tool. The feedback is attached to the change they are reviewing.
In practice, teams often use the MR security widget to:
- block risky merges
- understand whether a finding is new or existing
- prioritize high and critical issues first
- connect remediation work to the same code review cycle
Security Dashboard and Vulnerability Management
For individual merge requests, the MR widget is enough. For ongoing security operations, you need a broader view. That is where the GitLab Security Dashboard and vulnerability management features matter.
The dashboard helps teams answer questions like:
- How many open vulnerabilities exist across projects?
- Which ones are critical?
- Which findings are new versus previously known?
- Are we trending in the right direction over time?
This is important because DevSecOps is not just about detection. It is also about triage and remediation. A mature workflow includes:
- detecting a finding in CI
- classifying severity and confidence
- deciding whether it is real and actionable
- fixing or mitigating it
- tracking it to closure
GitLab vulnerability management helps organize that lifecycle rather than leaving findings buried in job logs.
Failing the Pipeline on Severity Thresholds
Running scans is good. Enforcing policy is better.
Many teams eventually want a rule such as:
- fail the pipeline if any critical vulnerability is introduced
- fail on high severity findings in protected branches
- allow lower severities but require review
There are several ways to enforce that, depending on your GitLab tier and governance model. At a basic level, you can make security jobs required and use merge request approval or policy features. Another practical approach is to add a custom gate job that reads generated report artifacts and exits non-zero when the threshold is exceeded.
A simplified example for SAST might look like this:
security-gate:
stage: security
image: alpine:3.20
needs:
- job: sast
artifacts: true
before_script:
- apk add --no-cache jq
script:
- |
critical_or_high=$(jq '[.vulnerabilities[] | select(.severity == "Critical" or .severity == "High")] | length' gl-sast-report.json)
echo "High or critical SAST findings: $critical_or_high"
test "$critical_or_high" -eq 0
This pattern is powerful because it lets you express a business rule: fail the pipeline only when risk crosses an agreed threshold.
Practical Example: Node.js Project with SAST and Secret Detection
Suppose you have a Node.js API and want a practical first step into GitLab DevSecOps. Start small: enable SAST and Secret Detection.
Here is a sample .gitlab-ci.yml:
stages:
- install
- test
- security
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
image: node:20
cache:
key:
files:
- package-lock.json
paths:
- .npm/
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
install_dependencies:
stage: install
script:
- npm ci
unit_tests:
stage: test
script:
- npm ci
- npm test
sast:
stage: security
secret_detection:
stage: security
What happens here?
- GitLab runs your normal Node.js install and test jobs.
- The SAST template adds a scanner appropriate for the repository contents.
- The Secret Detection template scans the repository for leaked credentials.
- Findings appear in GitLab security reports and, when supported by your setup, in the merge request security UI.
From there, you can extend the pipeline with dependency scanning and container scanning. The point is not to build a perfect security program on day one. The point is to start getting security feedback automatically in the same pipeline developers already trust.
Tips for Making GitLab Security Scanning Useful
Teams sometimes enable every scanner and then ignore the results. Avoid that trap.
A better rollout approach is:
- start with one or two high-value scanners
- make sure the findings are visible in merge requests
- decide who triages findings
- define a policy for critical and high severity issues
- expand coverage over time
Also remember these practical lessons:
- security jobs should run on merge requests whenever possible
- use protected variables and masked variables for secrets, not hardcoded credentials
- keep base images updated so container scanning results stay manageable
- regularly update dependencies so dependency scanning does not become overwhelming
- use review or staging environments for DAST instead of production
Final Takeaway
GitLab CI security scanning turns security from a late manual review into an automated part of delivery. SAST checks source code, Secret Detection catches leaked credentials, Dependency Scanning finds vulnerable packages, Container Scanning inspects image layers, and DAST probes a running application. Together, they create a practical DevSecOps workflow that helps teams shift security left without leaving the GitLab pipeline experience.
If you are just getting started, enable SAST and Secret Detection first. Once the team gets comfortable with GitLab security reports, add dependency, container, and dynamic scanning. That step-by-step path is how many strong DevSecOps programs begin.
Knowledge Check
Question 1: Shift Left Security
Why is shifting security left important in a GitLab CI pipeline?
Question 2: Scanner Purpose
Which GitLab scanner is designed to find accidentally committed API keys, tokens, and private credentials?
Question 3: Enabling Scans
What is the simplest way to enable GitLab SAST in a pipeline?