Chapter 25Lesson 05~365 minutes

Checkpoint Lab — SAST, Secret Detection, Dependency Scanning, Container Scanning, DAST, and Coverage-Guided Security

Use a deliberately vulnerable disposable fixture to predict, produce, triage, break, repair, and independently verify at least two security-evidence types without exposing real secrets or requiring paid features.

CheckpointVulnerable fixtureTwo evidence typesTriageReport repairCleanup

Learning objectives

  • Predict which scanner should detect each planted issue before execution.
  • Produce or simulate two different report types without any real credential or live vulnerable service.
  • Break one report safely, interpret validation failure, and repair the report path/schema rather than hiding the error.
  • Document triage as remediation, false positive, accepted risk, or out-of-scope with evidence.
  • Remove the vulnerable fixture and prove cleanup while retaining only safe learning evidence.
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. Checkpoint mission

Build one disposable security-evidence pipeline around a deliberately vulnerable toy project. Required live scanners are SAST and pipeline secret detection when a suitable Free runner is available; otherwise use CI Lint plus clearly labeled official-style fixtures. Produce or simulate at least two report types, predict scope before execution, break one report safely, repair it, triage evidence, and clean up all vulnerable lab material.

2. Preflight and safety assumptions

  • GitLab 19.3-compatible instance; Free is sufficient for required SAST/secret raw-report learning.
  • Developer-or-higher access to a disposable project/branch.
  • Linux/amd64 Docker/Kubernetes runner if executing GitLab analyzers; no privileged/persistent internal runner is required.
  • No real credentials, production URL, private customer data, or production container image.
  • Dependency Scanning and DAST are fixtures/read-only because they are Ultimate.
  • Coverage-guided fuzz testing is not configured because it was removed in GitLab 19.0.

3. Prediction ledger — complete before mutation

Planted condition Expected scanner/evidence Why
Unsafe shell construction in app.py SAST may report a source finding depending on current language/rules. SAST analyzes source code patterns/data flow.
Synthetic token-shaped string Pipeline secret detection may match; fixture fallback if current rules do not. Secret scanner analyzes committed Git content.
Vulnerable package fixture JSON Dependency-scanning fixture only. GitLab Dependency Scanning is Ultimate; package evidence is distinct from SAST.
Image digest fixture Container-scanning fixture or optional live Free scan. Container scanner analyzes image components, not source tree alone.
Example test URL fixture DAST fixture only. DAST is Ultimate and requires a running target.
Fuzz crash fixture External-fuzzer historical/migration fixture only. GitLab coverage-guided feature was removed in 19.0.

4. Create the disposable source fixture

Create a new branch and two harmless files:

git switch -c ch25/checkpoint
mkdir -p ch25-lab
cat > ch25-lab/app.py <<'PYAPP'
import subprocess

def lookup(user_input):
    # DELIBERATELY VULNERABLE DISPOSABLE FIXTURE.
    return subprocess.check_output("nslookup " + user_input, shell=True, text=True)

LAB_ONLY_TOKEN = "glpat-FAKE_DO_NOT_USE_00000000000000000000"
PYAPP

printf "Chapter 25 security lab only\n" > ch25-lab/README.txt
git add ch25-lab
git commit -m "Add disposable Chapter 25 vulnerable fixture"
FIXTURE_SHA="$(git rev-parse HEAD)"
printf "fixture_sha=%s\n" "$FIXTURE_SHA" | tee ch25-ledger.txt

5. Add the required Free-compatible pipeline configuration

Add the current managed templates and a scope-proof job:

stages:
  - test

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

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

Use CI Lint/Validate before commit. If no runner is available, stop after validating and use the fixture path in Step 9; do not claim a live scan.

6. Execute and capture safe evidence

Commit/push the CI config. After the pipeline runs, record:

git add .gitlab-ci.yml
git commit -m "Enable Free security evidence for Chapter 25 checkpoint"
git push -u origin ch25/checkpoint

# Read-only evidence after obtaining PIPELINE_ID:
glab api "projects/$PROJECT_ID/pipelines/$PIPELINE_ID" | jq '{id,sha,ref,source,status}' > ch25-pipeline.json
glab api "projects/$PROJECT_ID/pipelines/$PIPELINE_ID/jobs" | jq 'map({id,name,status,stage,runner:(.runner.description // null)})' > ch25-jobs.json

Do not print CI_JOB_TOKEN, registry passwords, or all environment variables.

7. Prove the two evidence types

For each SAST and secret-detection job/report available, record a compact ledger:

report_type: sast
pipeline_id: <id>
source_sha: <exact pipeline SHA>
analyzer: <from report/job>
scanner: <from report/job>
report_sha256: <sha256 of downloaded report>
finding_count: <count>

report_type: secret_detection
...

If the synthetic string produces no secret finding, record that outcome honestly and use a labeled valid secret-report fixture for the second evidence type. Never replace the synthetic string with a live credential.

8. Verify scanner scope independently

Before triage, compare the pipeline SHA with git rev-parse and ensure the report file paths belong to the checkpoint fixture. If doing optional container scanning, compare the report image digest to the registry digest. If using a DAST fixture, label the test URL/deployment as simulated.

test "$(jq -r .sha ch25-pipeline.json)" = "$(git rev-parse HEAD)"
printf "scope verified for %s\n" "$(git rev-parse HEAD)"

9. Paid/no-runner comparison fixtures

Create a local evidence matrix without pretending paid scanners ran:

dependency_scanning:
  status: SIMULATION_ONLY_ULTIMATE_FEATURE
  target: package example 1.0
  evidence: synthetic advisory fixture
container_scanning:
  status: OPTIONAL_LIVE_OR_FIXTURE
  target: registry.example.invalid/app@sha256:aaaaaaaa...
dast:
  status: SIMULATION_ONLY_ULTIMATE_FEATURE
  target: https://test.example.invalid/ch25
coverage_guided:
  status: REMOVED_FROM_GITLAB_19_0
  migration: standalone maintained fuzzer

10. Intentionally break one report path/schema

Create a separate diagnostic job on the disposable branch that uploads malformed SAST JSON. Do not replace the real SAST job:

broken_sast_fixture:
  stage: test
  image: alpine:3.22
  script:
    - printf '{"vulnerabilities": []}\n' > broken-sast.json
  artifacts:
    reports:
      sast: broken-sast.json

Predict: the shell job can succeed but GitLab should reject/flag the Secure report during validation. Preserve the pipeline message and report checksum.

11. Diagnose and repair without hiding the cause

Interpret the failure as a schema/integration defect. The repair is to remove the hand-crafted malformed job or replace it with a current-schema validated fixture/analyzer output. Do not convert it to a plain artifact just to make the pipeline look green.

After repair, rerun and record that the report-validation error is gone. Keep a text note describing the original error rather than retaining sensitive/raw broken content indefinitely.

12. Triage two findings/evidence records

For each evidence record, answer:

  1. What exact target did the scanner analyze?
  2. Is the finding reproducible or plausible in that target?
  3. Decision: remediate, synthetic test/false positive, accepted risk, or out of scope?
  4. What independent evidence proves the remediation?

For the shell-injection fixture, remediation is to avoid shell-string construction and pass validated arguments without shell=True. For the synthetic token, record that it is a nonfunctional lab string; if the finding represented a real token, revocation/rotation would precede source cleanup.

13. Remove the vulnerable source behavior and rescan

Replace the deliberately unsafe call with a safe argument-vector pattern in the disposable fixture:

import subprocess

def lookup(user_input):
    # Still validate allowed hostname syntax in real code.
    return subprocess.check_output(["nslookup", user_input], text=True)

LAB_ONLY_TOKEN = "synthetic-lab-marker-no-credential"

Commit, push, and compare the new pipeline/report to the previous exact finding identity. A disappearing finding is supporting evidence; code review and scope verification remain necessary.

14. Cleanup vulnerable lab resources safely

Before deletion, keep only a safe evidence summary: pipeline/job IDs, commit SHAs, analyzer names/versions, report checksums, validation status, and triage decisions. Do not retain raw matched secret values or sensitive requests unnecessarily.

git switch main
git branch -D ch25/checkpoint
if ! git push origin --delete ch25/checkpoint; then
  echo "Remote branch cleanup failed; inspect protection/permissions." >&2
  exit 1
fi
# If the entire disposable project was created only for this lab, delete it in the UI only after checking it contains no valuable data.

Project deletion is destructive. Never script deletion of an ambiguous project ID. For a shared throwaway project, removing the branch/fixtures is enough.

15. Final verification checklist

  • Two evidence types were live-produced or explicitly labeled as simulation.
  • Each evidence item is bound to exact project/pipeline/SHA or image/URL identity.
  • No real secret, production target, privileged runner, or production registry was used.
  • Malformed report failure was preserved, interpreted, and repaired without hiding it.
  • Source remediation was rescanned or fixture-verified.
  • Ultimate-only Dependency Scanning/DAST were not presented as Free live features.
  • Coverage-guided fuzz testing was identified as removed in GitLab 19.0.
  • Vulnerable lab branch/resources were removed and cleanup verified.

16. What Chapter 25 adds to the production GitLab operating model

You can now bind security evidence to the same source/artifact identities used by releases: scanners run in explicit trust boundaries, raw reports have schema/provenance, tier-gated presentation is separated from evidence generation, findings receive reasoned triage, and absent findings are never promoted into a claim of security. Chapter 26 will turn SBOMs, dependency lists, vulnerability state, remediation, and dashboards into the next layer of that operating model.

Knowledge check

Why is a successful SAST job with a malformed report still a failed security-evidence path?

What should happen if the fake token is not detected?

Which identity must match before container-scan evidence can support a release?

Why does the checkpoint keep Dependency Scanning and DAST as fixtures on the mandatory path?

What is the correct response if a genuine secret is found?

What does Chapter 26 add next?

Summary

The checkpoint produced a defensible scanner-evidence chain without real secrets or paid dependencies: predict scope, run or simulate honestly, bind reports to exact identities, diagnose schema failures, triage findings, remediate, rescan, and clean up. That is the operational foundation for DevSecOps evidence—not a green-security-checkbox mindset.

Official references

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

Next chapter

Chapter 26 — SBOMs, Dependency Lists, Vulnerability Management, Remediation, and Security Dashboards

Move from individual scanner reports to component inventory, persistent vulnerability state, remediation workflows, and portfolio-level security visibility.

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.