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.
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.
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:
- What exact target did the scanner analyze?
- Is the finding reproducible or plausible in that target?
- Decision: remediate, synthetic test/false positive, accepted risk, or out of scope?
- 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?
Because execution succeeded but the report contract failed validation, so the evidence cannot be trusted/ingested normally.
What should happen if the fake token is not detected?
Record the observation and use a labeled fixture; never substitute a real credential.
Which identity must match before container-scan evidence can support a release?
The exact immutable image digest used by the release/deployment.
Why does the checkpoint keep Dependency Scanning and DAST as fixtures on the mandatory path?
They are currently Ultimate features, while the chapter must remain Free-compatible.
What is the correct response if a genuine secret is found?
Revoke/rotate/disable it first, contain access, then clean source/history/logs and verify.
What does Chapter 26 add next?
SBOM/component inventory, dependency lists, vulnerability-management lifecycle, remediation evidence, and security dashboards.
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:
- GitLab Docs — Application security testing
- GitLab Docs — SAST
- GitLab Docs — SAST analyzers
- GitLab Docs — Pipeline secret detection
- GitLab Docs — Customize pipeline secret detection
- GitLab Docs — Dependency scanning
- GitLab Docs — Container scanning
- GitLab Docs — DAST
- GitLab Docs — Security scanning results
- GitLab Docs — Security report validation
- GitLab Docs — CI/CD artifacts report types
- GitLab Docs — Security scanner integration
- GitLab Docs — Coverage-guided fuzz testing (deprecated)
- GitLab Docs — Deprecations and removals
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.