Sign in
← CI/CD: patterns
11 min · medium · Lesson 5 of 5

Security in the pipeline: SAST, SCA, secret scanning

What security checks get built into CI, how they differ, and why scanning is a process, not a single tool.

Why security is a stage in CI

Finding a vulnerability or a leaked token in production is expensive. Finding it in the pipeline, before the deploy, is cheap. That's why security checks get built into CI as an ordinary stage, next to tests and the linter.

Three kinds of checks cover different risks: your own code, someone else's code in your dependencies, and secrets that accidentally ended up in the repository. Where exactly to place them in the pipeline was covered in the "Ideal pipeline" lesson: source checks go right at the start, an image scan goes between building the image and pushing it.

SAST: your code

SAST (Static Application Security Testing) is static analysis of source code for dangerous patterns: SQL injection, calling a shell with a string the user supplied, weak cryptography. The code never runs, so teams run SAST like a linter, on every push.

In GitLab, SAST plugs in through a ready-made template. The main analyzer under the hood is built on Semgrep, and the basic scan is available on every tier:

include:
  - template: Jobs/SAST.gitlab-ci.yml

If you write the job yourself, it looks like any other:

sast:
  stage: check
  script:
    - sh scripts/sast.sh
  allow_failure: true        # not blocking yet, just reporting

Soft checks and blocking checks

When SAST gets turned on for a project with history, it immediately finds dozens of old problems. If you make it blocking on day one, the pipeline turns red for everyone, and the team disables the scanner. That's why allow_failure: true goes on at the start.

What the flag does is spelled out in GitLab's docs: a failed job gets an orange warning, and the pipeline still counts as successful, the commit gets marked passed. The deploy after that pipeline proceeds as usual.

That's where the main trap is. A soft check stops nothing, and the orange badge stops getting noticed over time. Six months later the scanner is still soft, findings keep piling up, and a vulnerable library version rolls out to production without anyone blinking.

That's why allow_failure: true on a scanner is treated as temporary debt with a clear plan:

Usually secret scanning and SCA become blocking first: their findings are concrete (here's the key, here's the version with the CVE) and get fixed in minutes. SAST, with its false positives, stays soft longer.

SCA: someone else's code

SCA (Software Composition Analysis) checks a project's dependencies. Most vulnerabilities come from third-party libraries. SCA reads the dependency list (requirements.txt, package-lock.json, go.sum) and checks the versions against databases of known vulnerabilities.

Example finding: requests versions below 2.32.0 contain CVE-2024-35195. If the first request in a session went out with verify=False, later requests to the same host can skip TLS certificate verification. Fixed in 2.32.0 (GHSA-9wx4-h78v-vm56). The fix is bumping the version in requirements.txt.

Image scanning belongs here too: the base image and the packages installed in it can also have vulnerabilities. The image scanning lesson covers this in more depth.

In GitLab, a ready-made Dependency Scanning exists only on the Ultimate tier. On Free, teams usually run an open-source scanner (Trivy, Grype, pip-audit, npm audit) as a separate job.

Secret scanning: leaked secrets

Secret scanning looks in the repository for what shouldn't be there: tokens, private keys, passwords. A developer commits a key, and the check catches it before it spreads across copies of the repository.

In GitLab this is also a template, available on every tier:

include:
  - template: Jobs/Secret-Detection.gitlab-ci.yml

The job is called secret_detection. By default it only looks at the push's new commits. To scan the entire repository history once (for example, right after connecting it), you set the variable SECRET_DETECTION_HISTORIC_SCAN: "true".

A leaked secret takes more than deleting it

If a key has already made it into a commit, deleting it in the next commit doesn't fix anything. The key stays in the git history, and anyone with a clone can get it with git log -p. A scanner that walks the history will find it too, even though the current files no longer have the key.

GitHub's documentation on leaked secrets says it plainly: deleting the secret from the code, pushing a new commit, or recreating the repository isn't enough. The first step is revoking the secret with whoever issued it and issuing a new one. The order is:

  1. Revoke the old key and issue a new one (rotation).
  2. Put the new key wherever the application reads it from: CI/CD variables, a secrets store. It never goes into the repository.
  3. Deal with the history finding. Rewriting history (git filter-repo) is possible, but it breaks every clone on the team. More often, the revoked key gets marked as resolved in the scanner instead.

In gitleaks, every finding has a fingerprint, a string shaped like <commit>:<file>:<rule>:<line>. A fingerprint recorded in a .gitleaksignore file at the repository root gets skipped by the scanner from then on. You can only skip what's already revoked: an ignore entry without rotation just hides a live key.

A scanner is a process, not one tool

There are plenty of specific scanners (Trivy, Grype, Clair, Semgrep, gitleaks), and they change over time. Don't treat any single one as the final word: their vulnerability databases differ, they have false positives and false negatives, and vendors themselves have had incidents. Treat a scanner as a source of signal, not a verdict:

Common mistakes

What you'll need for the tasks

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 3 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson cicd-advanced-05-security-scanning