DevSecOps Level: Intermediate

Shift-Left Security in GitLab CI with Gitleaks and Trivy

A practical, copy-paste GitLab CI setup that catches leaked secrets and vulnerable container images before they reach production.

2 min read

Integrating security scanning into CI/CD pipelines was one of the changes with the biggest payoff in my career: on one team it reduced vulnerabilities reaching production by 60%. The trick is not the tools themselves — it is wiring them in so they help developers instead of blocking them.

The two cheapest wins

If you only add two security jobs to your pipeline, make them these:

Job Catches Typical runtime
Gitleaks API keys, passwords and tokens committed to Git seconds
Trivy Known CVEs in OS packages and app dependencies inside the image < 1 minute

Step 1 — Secret detection with Gitleaks

Secrets must be caught before the image is built, so this runs in the first stage:

stages: [secrets, build, scan, deploy]

gitleaks:
  stage: secrets
  image:
    name: zricethezav/gitleaks:v8.18.4
    entrypoint: [""]
  script:
    - gitleaks detect --source . --redact --verbose
        --report-format json --report-path gitleaks-report.json
  artifacts:
    when: always
    paths: [gitleaks-report.json]
    expire_in: 1 week

--redact keeps the secret itself out of the job log — otherwise your scanner becomes the leak.

Step 2 — Image scanning with Trivy

Scan the exact image you just pushed, and export results in GitLab’s format so findings appear in the merge request:

trivy:
  stage: scan
  image:
    name: aquasec/trivy:0.56.2
    entrypoint: [""]
  variables:
    IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    TRIVY_USERNAME: $CI_REGISTRY_USER
    TRIVY_PASSWORD: $CI_REGISTRY_PASSWORD
    TRIVY_CACHE_DIR: .trivycache/
  script:
    # 1) Full report for the GitLab security widget (never fails the job)
    - trivy image --exit-code 0 --format template
        --template "@/contrib/gitlab.tpl"
        --output gl-container-scanning-report.json "$IMAGE"
    # 2) Quality gate: fail only on fixable CRITICAL issues
    - trivy image --exit-code 1 --severity CRITICAL --ignore-unfixed "$IMAGE"
  cache:
    paths: [.trivycache/]
  artifacts:
    reports:
      container_scanning: gl-container-scanning-report.json

Make the gate fair

A security gate that fails on everything gets disabled within a week. A few rules that kept ours alive:

  • Fail only on what developers can fix. --ignore-unfixed skips CVEs with no available patch.
  • Start with CRITICAL, then tighten. Add HIGH once the backlog is under control.
  • Allow documented exceptions. A .trivyignore file in the repo, reviewed like code, beats a disabled job.
  • Cache the vulnerability DB. TRIVY_CACHE_DIR plus GitLab cache turns a 40-second download into a no-op.

What’s next

Secrets and image CVEs are the floor, not the ceiling. The next layers are SAST with SonarQube, DAST with OWASP ZAP against a review environment, and aggregating everything in DefectDojo so security has a single dashboard. I’ll cover those in follow-up posts.