Products
TRAIODerScannerSonatypeHashiCorp VaultDPA FASTBurp DASTBurp ProfessionalDevSecOps services by TRIDS
Services
DevSecOps implementationApplication security assessmentAppSec & DevSecOps training
Company
About usCareers & internshipsAppSecFestSonatype User GroupPrivacy policyBlogContacts
Language
RUKZENUZ
+7 777 734 7070 Get a consultation

How to Add SAST and SCA to GitLab CI: Stages, Quality Gates and an Example .gitlab-ci.yml

22.10.2026 7 min read AST Cyber Lab

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:

  1. build — compilation and dependency resolution (for SCA, lock files or the dependency graph matter);
  2. test — unit tests and, in parallel, SAST and SCA;
  3. quality gate — checking the reports against defined thresholds;
  4. package / deploy — runs only if the previous stages have passed;
  5. 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:rules runs 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: true on 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:

  1. One to two weeks without blocking. Collect statistics on findings and discuss them with the teams.
  2. Block only new critical findings. Old technical debt should not stop work; block what is added by the change.
  3. Extend the threshold. Then the high level, then licenses and malicious packages.
  4. 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?
Basic SAST with the open analyzers is available in all tiers; advanced features (including Advanced SAST and reports in merge requests) are in Ultimate. Dependency scanning in GitLab is provided in the Ultimate tier. On lower tiers, teams use third-party scanners connected as separate jobs.
Can I use third-party scanners in GitLab CI?
Yes. The run is defined as a regular job with the scanner's image or CLI client, and the result is saved as an artifact. The stage and quality gate principle stays the same.
How do I avoid slowing down the pipeline?
Run SAST and SCA in parallel with the tests, use a dependency cache, limit the scan to changed files in merge requests, and run the full analysis on a schedule.
What about the old vulnerabilities that have already accumulated?
Record them as a baseline, do not block work because of old findings, close them systematically by priority, and apply the quality gate to new changes.

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.

Get a consultation

Sources

Consultation

We protect your applications at every stage of development.

Tell us about your challenge — AST Cyber Lab experts will select a solution, run a demo and help with implementation.