Chapter 25Lesson 02~350 minutes

SAST, Secret Detection, Dependency Scanning, Container Scanning, DAST, and Coverage-Guided Security: Guided Hands-On Workflow and Core Operations

Run or safely simulate Free-compatible SAST and secret-detection evidence, inspect raw reports separately from hosted security presentation, compare scanner scopes, and verify analyzer and runner trust.

Hands-onFree pathSAST reportSecret reportCI LintEvidence

Learning objectives

  • Create a disposable project/branch and inspect pipeline configuration before running scanners.
  • Enable only Free-compatible SAST and pipeline secret detection in the required path.
  • Inspect job logs and raw report artifacts without printing all environment variables or real secret material.
  • Use a safe report fixture to compare source, dependency, container, and DAST evidence domains.
  • Verify analyzer/template provenance, runner type, pipeline source, commit SHA, and report scope independently.
Availability baseline — verified 2026-08-22 against current GitLab 19.3 documentation. Basic SAST, pipeline secret detection, and pipeline container scanning run on Free/Premium/Ultimate across GitLab.com, Self-Managed, and Dedicated, and Free/Premium users can download their JSON report artifacts. Rich merge-request security presentation, vulnerability-management workflows, security dashboards, policy enforcement, and several advanced analyzer features are Ultimate. GitLab Dependency Scanning is currently Ultimate. GitLab DAST is currently Ultimate. The curriculum phrase “Coverage-Guided Security” is retained for path/title stability, but GitLab coverage-guided fuzz testing was deprecated in 18.0 and removed in 19.0; it is therefore taught as historical/migration context, not as a runnable GitLab 19.3 feature. Required labs use only SAST/secret-detection raw evidence or safe fixtures and do not require a paid tier.

1. Disposable lab and no-paid learning path

Create a disposable project named gitlab-ch25-security-lab or use a disposable branch in a throwaway project. The required path enables SAST and pipeline secret detection only, because those two can run and produce downloadable reports on GitLab Free. Container scanning is an optional extension if your project already has a tiny disposable image. Dependency scanning and DAST remain read-only/fixture exercises because they are Ultimate.

No real secrets. The lab uses an obviously synthetic token-shaped string solely to exercise a detector. Never paste a live credential into source to “test” secret detection.

2. Preflight: prove the project, commit, pipeline model, and runner

Record state before changing CI:

git switch -c ch25/security-lab
git rev-parse HEAD | tee ch25-base-sha.txt
git status --short
# If a pipeline already exists, inspect only selected fields:
glab api "projects/$PROJECT_ID/pipelines?per_page=5" | jq 'map({id,sha,ref,source,status})'

In GitLab, inspect Build → Pipeline editor → Validate and the project runner list. Required scanner jobs need a Linux/amd64 Docker or Kubernetes executor; GitLab.com hosted runners normally satisfy that, subject to current compute entitlement. If no runner is available, use CI Lint plus the report fixtures later in this lesson.

3. Create a tiny intentionally vulnerable source fixture

This code is deliberately unsafe and must remain in the disposable project only. It demonstrates source-code data flow without running a vulnerable server:

from flask import Flask, request
import subprocess

app = Flask(__name__)

@app.get("/lookup")
def lookup():
    host = request.args.get("host", "localhost")
    # DELIBERATELY VULNERABLE LAB CODE: shell injection sink.
    return subprocess.check_output("nslookup " + host, shell=True, text=True)

Save it as app.py. SAST analyzer selection is language/rule dependent, so do not promise a particular finding count. The goal is to inspect what the current analyzer actually reports for this exact commit.

4. Add an obviously synthetic secret-shaped fixture

Add a text fixture whose value is intentionally nonfunctional:

LAB_ONLY_TOKEN = "glpat-FAKE_DO_NOT_USE_00000000000000000000"

If the current secret rules do not flag this exact synthetic pattern, that is a valid observation—not a reason to use a real token. Use the official-style fixture path below to continue the lesson safely.

5. Enable only the Free-compatible scanners

Use current GitLab-managed job templates. Keep the test stage:

stages:
  - test

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

lab_scope:
  stage: test
  image: alpine:3.22
  script:
    - printf "project=%s\nsha=%s\nsource=%s\n" "$CI_PROJECT_PATH" "$CI_COMMIT_SHA" "$CI_PIPELINE_SOURCE"

Validate the configuration before committing. Do not add Dependency Scanning or DAST templates to the mandatory Free path.

6. Write predictions before execution

Question Prediction to record
SAST target Current branch workspace at the pipeline commit SHA; source files only.
Secret target Committed Git content according to pipeline secret-detection scan mode.
Dependency evidence Not produced by the mandatory pipeline; Ultimate/fixture only.
Container evidence Not produced unless the optional image extension is enabled.
DAST evidence Not produced; there is no running test site and DAST is Ultimate.
Coverage-guided No GitLab 19.3 job; removed product feature.

7. Commit and inspect the scanner jobs

Push the disposable branch and inspect pipeline/job state without printing credentials:

git add app.py .gitlab-ci.yml
git commit -m "Add Chapter 25 disposable scanner fixture"
git push -u origin ch25/security-lab

# After the pipeline starts:
glab api "projects/$PROJECT_ID/pipelines/$PIPELINE_ID" | jq '{id,sha,ref,source,status}'
glab api "projects/$PROJECT_ID/pipelines/$PIPELINE_ID/jobs" | jq 'map({id,name,status,stage,runner:(.runner.description // null)})'

Expected jobs include one or more SAST analyzer jobs based on the detected language and a secret_detection job. Analyzer job names can evolve; trust the expanded configuration and current job list rather than a stale screenshot.

8. Inspect raw reports separately from the security UI

On Free/Premium, the raw JSON report artifacts are the mandatory evidence surface. Download them through the job artifact UI or authenticated API without echoing credentials. Common conventional filenames include gl-sast-report.json and gl-secret-detection-report.json.

Record only safe summary fields in your evidence ledger: report type/schema version, scanner/analyzer name/version, vulnerability count, file paths, and commit SHA. In real projects, raw secret-detection details can be sensitive; do not paste matched values into tickets or chat.

9. Compare raw evidence with GitLab-hosted presentation

If the project is on Ultimate, inspect the pipeline Security tab, merge-request security report, vulnerability report, and dashboard as appropriate. Compare one finding to the raw JSON. If the project is Free, document that the report exists even when the richer hosted triage surface is unavailable. This distinction prevents “I cannot see the dashboard” from being misdiagnosed as “the scanner did not run.”

10. Safe triage exercise: evidence, not button-clicking

Choose one synthetic result or use this fixture:

{
  "finding": "synthetic command-injection example",
  "scanner": "SAST fixture",
  "source_sha": "<pipeline SHA>",
  "decision": "remediate",
  "reason": "untrusted request data reaches shell=True",
  "proof_after_fix": "new scan + code review"
}

For the fake token, a correct real-world response would be revocation/rotation if it were genuine. Because it is synthetic, record test fixture — no live credential exists. Never practice incident response by creating a real credential and leaking it.

11. Compare scanner scopes with safe fixtures

Evidence item What it proves What it does not prove
SAST report referencing app.py:line A source analyzer matched a rule at that commit/location. That the deployed binary contains the code, or that exploitability is confirmed.
Secret report A configured detector matched repository content. That a detected value is active; if real, validate safely and revoke first.
Dependency fixture: package/version/CVE A dependency/version intersects advisory data. That the code path is reachable/exploitable.
Container fixture: image@sha256 + package/CVE The scanned image digest contains an affected component. That another tag/digest or runtime actually uses it.
DAST fixture: URL + request evidence A running target responded in a way consistent with a vulnerability. That source/dependency scanners cover the same runtime path.

12. Optional Free extension: scan one disposable image by digest-aware identity

If Chapter 23 left a disposable image, enable the current container template and set the exact image coordinate. Record the digest separately from the tag:

include:
  - template: Jobs/Container-Scanning.gitlab-ci.yml

container_scanning:
  variables:
    CS_IMAGE: "$CI_REGISTRY_IMAGE:ch25-lab"

Do not add registry credentials to logs. Verify the digest through the registry before treating the scan report as evidence for a release candidate.

13. No-runner fallback: validate configuration and use labeled report fixtures

If hosted compute is unavailable, complete the learning path by:

  1. Use CI Lint/merged configuration to prove the SAST and secret-detection jobs would be injected.
  2. Use a saved official-style SAST report fixture and secret-detection report fixture labeled SIMULATION — NOT PRODUCED BY THIS PIPELINE.
  3. Compare required scope fields and explain what evidence is missing without execution.

Do not fabricate a successful live scan. A simulation is useful only when its provenance is explicit.

14. Challenge: which surface is correct?

A developer says “Dependency Scanning is missing from our Free pipeline, so I will rename a SAST JSON artifact to gl-dependency-scanning-report.json.” Explain the error.

Expected reasoning: filename does not determine report type, and SAST evidence is not dependency evidence. Dependency Scanning is an Ultimate capability; use an external dependency scanner/SBOM fixture for learning or an appropriate supported integration rather than relabeling evidence.

Knowledge check

Why do you inspect raw SAST/secret reports separately from the GitLab security UI?

If the synthetic token does not trigger current secret rules, should you paste a real token to test the scanner?

What three values should identify a source-scan run?

Can a container scan of :latest be bound safely to a release without a digest?

What does the no-runner fallback prove?

Summary

The guided workflow proves Free-compatible scanner execution and raw evidence without conflating it with Ultimate UI features. It also establishes a repeatable scope ledger for source, secret, dependency, container, and runtime evidence.

Official references

Primary sources used for the current GitLab 19.3 behavior taught in this lesson:

Next lesson

Configuration, Design Choices, and Tradeoffs

Turn scanner evidence into an operating architecture: decide where scanners live, how often they run, what blocks delivery, and how to control trust and cost.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.