SAST and SCA are added to GitLab CI as separate jobs in the test stage: they run on every merge request and produce a report, and a quality gate decides whether to block the merge. Start in "inform only" mode, then enable blocking for critical findings. Below are the pipeline architecture, a sample configuration and common mistakes.
Where SAST and SCA sit in the pipeline
The "shift left" principle means checks run as early as possible, and the result comes back to the developer in the same merge request. A typical sequence of stages:
- build — compilation and dependency resolution (for SCA, lock files or the dependency graph matter);
- test — unit tests and, in parallel, SAST and SCA;
- quality gate — checking the reports against defined thresholds;
- package / deploy — runs only if the previous stages have passed;
- DAST — on a test environment after deployment (a separate topic, see the overview of tool classes).
SAST and SCA are independent, so it pays to run them in parallel — this does not increase pipeline time.
What GitLab provides out of the box
GitLab provides ready-made templates. For SAST, it is enough to include the Jobs/SAST.gitlab-ci.yml template: the jobs run in the test stage by default, and each analyzer produces a JSON report as an artifact. According to the GitLab documentation, basic scanning with the open analyzers is available in all tiers, while the extended GitLab Advanced SAST analyzer, merge request reports and vulnerability management are part of the Ultimate tier. For SCA, GitLab offers dependency scanning: it is available in the Ultimate tier, the recommended modern approach is based on an SBOM in CycloneDX format (the Jobs/Dependency-Scanning.v2.gitlab-ci.yml template), and the former Gemnasium-based analyzer has been deprecated starting with GitLab 17.9. If you use third-party solutions (for example, DerScanner for SAST or Sonatype for SCA), the integration logic is the same: a job runs the scanner, and the result is checked against thresholds.
Sample .gitlab-ci.yml
Below is a general scheme. GitLab templates are included with include lines, and threshold checking is moved into a separate stage. Adapt the names, images and commands to your environment and the tools you use.
stages:
- build
- test
- security-gate
- deploy
include:
# SAST: GitLab's ready-made template; jobs run in the test stage
- template: Jobs/SAST.gitlab-ci.yml
# SCA: SBOM-based dependency scanning (Ultimate tier)
- template: Jobs/Dependency-Scanning.v2.gitlab-ci.yml
inputs:
job_name: "dependency-scanning"
stage: test
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
build:
stage: build
image: node:20
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
unit-tests:
stage: test
image: node:20
script:
- npm ci
- npm test
# Quality gate: check the reports against thresholds
security-gate:
stage: security-gate
image: alpine:3.20
needs:
- job: dependency-scanning
artifacts: true
optional: true
before_script:
- apk add --no-cache jq
script:
- |
# Count Critical and High findings in the dependency report (if the report exists)
if [ -f gl-dependency-scanning-report.json ]; then
COUNT=$(jq '[.vulnerabilities[] | select(.severity=="Critical" or .severity=="High")] | length' gl-dependency-scanning-report.json)
echo "Critical/High findings in dependencies: $COUNT"
if [ "$COUNT" -gt 0 ]; then
echo "Quality gate failed"
exit 1
fi
fi
# At the start of rollout, allow failures so the job only reports
allow_failure: true
deploy:
stage: deploy
script:
- echo "Deploy"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
What matters in this example:
workflow:rulesruns the pipeline on merge requests and on the default branch. By default, as the GitLab documentation notes, SAST runs only in branch pipelines, so checks in merge requests need to be configured explicitly.allow_failure: trueon the quality gate is the "inform only" mode. After the thresholds are calibrated, remove it, and the pipeline will start blocking merges.- If your repository does not store lock files, generate them in the build stage and pass them on as artifacts; otherwise SCA will not see the full dependency graph.
- For Windows runners and some architectures, the GitLab SAST templates are not supported: you need a Linux runner with a Docker or Kubernetes executor.
- Adapt the report-parsing command (
jq) to the actual structure of the report for your GitLab version, or use the built-in policies: in the Ultimate tier, thresholds can be managed through merge request approval rules.
Quality gates: how to set thresholds
A quality gate is a formalized rule that prevents a build from moving forward. The recommended rollout path:
- One to two weeks without blocking. Collect statistics on findings and discuss them with the teams.
- Block only new critical findings. Old technical debt should not stop work; block what is added by the change.
- Extend the threshold. Then the high level, then licenses and malicious packages.
- Process exceptions. Suppressing a finding is allowed only with a justification and an expiry date, agreed with AppSec.
For SCA, a separate set of rules is useful for component policy: banning known malicious packages and critical CVEs with a public exploit, and restricting licenses. Building an SBOM is described in the article "What Is an SBOM".
Common mistakes
- Enabling blocking right away. Teams will see hundreds of old findings, the pipeline turns red for everyone, and the initiative meets resistance. Start with reporting only.
- Scanning only the main branch. The developer learns about the problem after the merge. The result is needed in the merge request.
- No lock files. SCA works with imprecise versions or misses transitive dependencies.
- Ignoring false positives. Without tuned rules and a suppression process, the noise destroys trust in the tool.
- Secrets in configuration. Store scanner tokens in protected CI variables or in a secrets manager (secrets management), not in the repository.
- No owner. If nobody is responsible for reviewing findings, they pile up. Appoint an AppSec owner and Security Champions in the teams.
- SAST that is too slow. A full analysis on every commit slows development; use incremental checks in merge requests and a full analysis on a schedule.
How to verify that everything works
- Add a deliberately vulnerable example (for instance, an SQL injection) to a test branch and confirm that SAST finds it.
- Add a dependency with an old vulnerable version and check the SCA reaction.
- Make sure the reports are saved as artifacts and available to auditors.
- Track metrics: pipeline duration, blocking rate, time to remediation. How to justify developing the program is described in the article "How to Justify an AppSec Budget".
If you need to build the whole process, from choosing tools to training teams, see our DevSecOps implementation service and the article "DevSecOps: Where to Start, a Step-by-Step Plan".
How to organize the work with findings
Automatic scanning delivers results only with a clear process. Agree on a few rules: who reviews new findings (the developer who authored the change, AppSec, or a Security Champion); the deadlines for fixing critical and high vulnerabilities; how exceptions are documented and who approves them; and how results get into the issue tracker. The scan result in a merge request should be readable: the developer sees the file, the line, an explanation and a recommendation. The fewer steps between a finding and its fix, the more value the tool delivers.
For large organizations with dozens of projects, it is convenient to move the shared security configuration into a separate repository and include it via include in all projects. This way, rules and thresholds change in one place, and teams do not copy the configuration by hand.
Frequently asked questions
Do I need GitLab Ultimate for SAST and SCA?
Can I use third-party scanners in GitLab CI?
How do I avoid slowing down the pipeline?
What about the old vulnerabilities that have already accumulated?
Need help adding SAST and SCA to your pipelines? AST Cyber Lab experts can help you choose tools, configure quality gates and train your teams.
Sources
- GitLab Docs: Static Application Security Testing (SAST)
- GitLab Docs: Dependency scanning
- GitLab Docs: Dependency scanning using SBOM