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.
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:
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 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:
The important idea is that each scanner looks at a different layer of risk:
No single scanner covers everything. The value comes from using multiple scanners together.
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.
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:
The exact analyzer selection depends on the project contents. GitLab automatically chooses analyzers based on the repository language and the included SAST template.
The simplest way to enable SAST is to include GitLabâs security template:
include:
- template: Security/SAST.gitlab-ci.ymlThat 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.
To get more value from SAST:
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.
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.
Secret detection commonly finds:
Add the template:
include:
- template: Security/Secret-Detection.gitlab-ci.ymlThis makes secret scanning part of the pipeline so developers get early feedback when something sensitive appears in the codebase.
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.
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.
GitLab dependency scanning can detect vulnerable packages in files such as:
package.json and lock files for Node.jsrequirements.txt, Pipfile.lock, or Poetry files for PythonAdd the template:
include:
- template: Security/Dependency-Scanning.gitlab-ci.ymlYou 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.
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.
Container scanners typically inspect:
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.
Add the template:
include:
- template: Security/Container-Scanning.gitlab-ci.ymlYou 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_SHADynamic 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:
Because DAST scans a live application, it needs a reachable target URL, usually a review app, staging environment, or temporary test deployment.
Add the template:
include:
- template: Security/DAST.gitlab-ci.ymlThen provide a target URL:
dast:
variables:
DAST_WEBSITE: https://staging.example.comDAST is strongest when you already have:
DAST complements SAST. SAST looks at code structure; DAST looks at application behavior.
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.comThat gives you broad GitLab CI security scanning coverage with minimal YAML.
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:
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:
This is important because DevSecOps is not just about detection. It is also about triage and remediation. A mature workflow includes:
GitLab vulnerability management helps organize that lifecycle rather than leaving findings buried in job logs.
Running scans is good. Enforcing policy is better.
Many teams eventually want a rule such as:
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 0This pattern is powerful because it lets you express a business rule: fail the pipeline only when risk crosses an agreed threshold.
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: securityWhat happens here?
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.
Teams sometimes enable every scanner and then ignore the results. Avoid that trap.
A better rollout approach is:
Also remember these practical lessons:
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.
<MCQExercise exercise={{ id: 'gitlab-security-q1', title: 'Question 1: Shift Left Security', description: "Why is shifting security left important in a GitLab CI pipeline?", options: [ { id: 'a', text: 'It delays security checks until after production deployment' }, { id: 'b', text: 'It moves security feedback earlier so issues are cheaper and faster to fix' }, { id: 'c', text: 'It removes the need for code review and testing' }, { id: 'd', text: 'It means only the operations team is responsible for security' }, ], correctId: 'b', explanation: "Shift-left security means running checks earlier in development, such as during branch and merge request pipelines, so vulnerabilities are found before release.", wrongExplanations: { a: 'Shifting left does the opposite: it brings security earlier, not later.', c: 'Security scanning complements code review and testing rather than replacing them.', d: 'DevSecOps spreads security responsibility across development, operations, and security teams.', }, }} />
<MCQExercise exercise={{ id: 'gitlab-security-q2', title: 'Question 2: Scanner Purpose', description: "Which GitLab scanner is designed to find accidentally committed API keys, tokens, and private credentials?", options: [ { id: 'a', text: 'SAST' }, { id: 'b', text: 'Container Scanning' }, { id: 'c', text: 'Secret Detection' }, { id: 'd', text: 'DAST' }, ], correctId: 'c', explanation: "Secret Detection searches the repository for patterns that look like exposed secrets such as API keys, tokens, passwords, and private keys.", wrongExplanations: { a: 'SAST focuses on source-code level security flaws rather than leaked credentials.', b: 'Container Scanning inspects container images for vulnerable OS and package layers.', d: 'DAST tests a running application from the outside rather than searching Git history for secrets.', }, }} />
<MCQExercise exercise={{ id: 'gitlab-security-q3', title: 'Question 3: Enabling Scans', description: "What is the simplest way to enable GitLab SAST in a pipeline?", options: [ { id: 'a', text: 'Add the Security/SAST.gitlab-ci.yml template to include' }, { id: 'b', text: 'Create a manual job called security and leave it empty' }, { id: 'c', text: 'Only install Docker and Git on the runner' }, { id: 'd', text: 'Rename the test stage to sast' }, ], correctId: 'a', explanation: "GitLab provides built-in CI templates for security scanning, and including the SAST template is the fastest way to enable a baseline static analysis scan.", wrongExplanations: { b: 'An empty manual job does not run a security analyzer or generate security reports.', c: 'Runner tooling alone does not enable SAST; the pipeline must include the analyzer job definition.', d: 'Changing a stage name does not activate the SAST scanner.', }, }} />
Secret Detection, because a leaked credential is an immediate and unambiguous problem, and because it finds things regardless of language or framework. Dependency Scanning is a close second, since most vulnerabilities in a typical application arrive through its dependencies rather than its own code. SAST is valuable but noisier, and is best introduced once the team has capacity to triage findings. Adding all five at once usually produces a volume of results nobody acts on.
On critical and high severity findings in new code, yes. On everything, no. A policy of zero findings gets disabled within a month because it blocks unrelated work on pre-existing issues. A practical approach is to fail only on new critical and high vulnerabilities introduced by the change, report the rest without blocking, and work through the existing backlog separately. GitLab's merge request widget shows newly introduced findings specifically, which is what makes this distinction workable.
SAST reads your source code without running it, so it can point at an exact file and line and runs early and fast in the pipeline. DAST attacks a running instance of the application from the outside, so it finds issues that only appear at runtime. Configuration problems, authentication flaws, injection that depends on real request handling, but it needs a deployed environment and takes much longer. They find genuinely different classes of problem and complement each other rather than competing.
No. Git history is permanent, so anyone with repository access can still read the secret from the earlier commit, and any clone already contains it. Treat it as compromised: rotate the credential immediately, then remove it from the current code. Rewriting history to purge it is possible but disruptive and does not help if the repository was ever public or cloned. Rotation is the step that actually restores security; history cleanup is housekeeping.
Run the heavier scanners where they earn their keep rather than on every commit. Secret Detection and Dependency Scanning are fast enough to run everywhere. Container Scanning naturally belongs after the image is built, and DAST needs a deployed environment so it fits on merge requests to the default branch or on a nightly schedule. Using rules to target them appropriately keeps the fast feedback loop fast while still getting the coverage.
Deploy to AWS from GitLab CI using OIDC instead of static keys, then ship to S3 and EC2, push images to ECR, update ECS services and automate Terraform runs.
Trigger downstream pipelines in GitLab with the trigger keyword: pass variables, choose strategy depend or mirror, build child pipelines and use include.
Speed up GitLab CI pipelines with better caching, DAG execution using needs, reusable include templates, YAML anchors, rules and protected deployments.