Hands-on DevSecOps training, driven by the
dsoxlab CLI. This repository is the
capstone of the DevSecOps course
on www.stephane-robert.info, written as a common thread: a single project,
notes-api, hardened version after version, one version per module of the
course.
devsecops-dsoxlab-training is a content repository, not an application.
It provides:
- a single project,
notes-api, a small Flask service shipped with its flaws: a concatenated SQL query, a vulnerable dependency, a Dockerfile running as root, an over-permissive Terraform, no pipeline; - labs whose starting point is the solution of the previous one, from V0 (a minimal pipeline) to V11 (the project's delivery metrics);
- automated validation that plays the learner's pipeline with act and judges what it produces, never the commands typed;
- an objective grid, the NIST SSDF: each version cites the practice it implements;
- a score with hints of increasing cost.
The dsoxlab CLI is the single entry point: it sets up a lab, shows the
brief, validates, scores and reports. It lives in its own repository and is
installed separately.
- Python 3.11+ and
uv git- A Docker that answers (
docker info): act plays the learner's pipeline in theubuntu-24.04runner image, pinned by digest. mise, which installs act 0.2.89, uv and Python at the versions ofmise.toml(mise install). Any act release older than 0.2.86 is vulnerable to CVE-2026-34041 and CVE-2026-34042.
No VM or cloud account for the first versions: everything plays on the
learner's machine, in runtime: shell. The versions that need a Kubernetes
cluster or a VM will declare it in their lab.yaml.
dsoxlab is published on PyPI and
installs as a standalone tool:
# 1. Install the dsoxlab CLI (external tool, stays out of this repo)
uv tool install dsoxlab # or: pipx install dsoxlab
# 2. Clone this lab catalog, and install its tools
git clone https://github.com/stephrobert/devsecops-dsoxlab-training.git
cd devsecops-dsoxlab-training
mise install
# 3. Check the contract is valid
dsoxlab validate-structuredsoxlab list-labs # browse the catalog
dsoxlab run capstone-v00-pipeline-minimal # set up the starting state
dsoxlab challenge capstone-v00-pipeline-minimal # read the mission
# ... you work in challenge/work ...
act push # play your pipeline
dsoxlab check capstone-v00-pipeline-minimal # validate and scorerun creates the lab's working directory and copies the declared fixtures
into it. Everything then happens in challenge/work: it is the only place you
modify, and dsoxlab clean removes it.
Stuck? dsoxlab hint <id> reveals a hint, whose cost is deducted from the
score.
git pull # the catalog
uv tool upgrade dsoxlab # the engine
mise install # the pinned toolsThe three evolve separately. A lab that fails after an upgrade of act or of
the runner image is a defect of the catalog: open an issue, the form comes
pre-filled by dsoxlab support --issue.
This catalog is played by dsoxlab, which documents its own contract and
commands. This README does not repeat them, so that it cannot drift when the
engine changes (dsoxlab issue #201):
- the contract:
meta.yml,lab.yaml,lab.<lang>.yaml, the runtimes and their fields: contract; - the commands and the life of a lab, from
runtocheck: commands and playing a lab; - writing a lab, and what
validate-structurechecks: catalog author guide.
What is specific to this catalog: meta.yml carries the order
of the versions, which is the order of play, and it is never reordered.
tests/test_schemas_dsoxlab.py checks
each lab.yaml against the schema published by dsoxlab.
| Runtime | What the lab needs |
|---|---|
shell + act |
a terminal, Docker and the tools of mise.toml. The tests play your pipeline with act, on your project and on copies broken on purpose. |
Validation proves the state, it does not trust the learner. Each lab ships
pytest tests under challenge/tests/ that play the pipeline with act and read
what it produces: the job's result and the lines each step wrote. To see the
pipeline fail when it must fail, a test modifies a copy of the project: a
broken test, an injected secret, a vulnerable dependency. Your project is never
touched.
And a lab is proven both ways: the tests must fail before the work, and
pass after. scripts/verify-solutions.py --deux-sens checks it for every lab,
and scripts/valider-labs.py attests it through the real dsoxlab commands in
validation-labs.json.
The reference solutions live under solution/, encrypted with
ansible-vault: a solution shipped in clear spoils the lab, and git keeps it
forever.
Each version cites, in its scenario, the NIST SSDF practice it implements:
"SSDF practice targeted: PO.3". curriculums.yml holds the grid, its official
source and the date it was last checked, and tests/test_curriculums.py
refuses a code missing from the grid or a label that contradicts it.
check records a score (tests passed over total, minus the cost of the hints
revealed). Hints are base64-encoded in challenge/hints.yaml, so they are
not read by opening the file, and their cost grows with their precision. The
history lives in a SQLite database local to this repository.
A lab's doc_url is the French lesson, because dsoxlab reads it. The links of
the READMEs are computed: scripts/gen_doc_url_en.py reads the site's
translationOf fields, and scripts/gen_catalog.py points the English README
to the translation when it exists. tests/test_liens_par_langue.py refuses a
link in the wrong language.
| Lab (id) | Title | Level | Runtime | Companion guide |
|---|---|---|---|---|
capstone-v00-pipeline-minimal |
V0, a minimal pipeline: notes-api tested on every push | capstone | shell | guide |
capstone-v01-modele-de-menace |
V1, a threat model: STRIDE on notes-api, checked by the pipeline | capstone | shell | guide |
capstone-v02-secrets-et-sast |
V2, blocking secret scanning and SAST: two gates, and the injection fixed | capstone | shell | guide |
capstone-v03-sca-et-triage |
V3, dependency scanning and triage: fix what can be fixed, date what cannot | capstone | shell | guide |
capstone-v04-identite-sans-secret |
V4, an identity without a secret: OIDC instead of a stored key | capstone | shell | guide |
capstone-v05-pipeline-durci |
V5, a hardened pipeline: workflows audited, pull requests contained | capstone | shell | guide |
capstone-v06-image-et-iac |
V6, the image and the infrastructure: what you ship besides the code | capstone | shell | guide |
capstone-v07-sbom-et-provenance |
V7, SBOM and provenance: know what ships, prove where it comes from | capstone | shell | guide |
capstone-v08-admission-signee |
V8, admission: the cluster refuses what the platform did not sign | capstone | shell | guide |
capstone-v09-runtime-cloisonne |
V9, a contained runtime: restricted pods and closed network | capstone | shell | guide |
capstone-v09b-detection-falco |
V9b, runtime detection: Falco watches notes-api on a real machine | capstone | vm | guide |
capstone-v10-vex-et-exceptions |
V10, VEX and dated exceptions: every accepted finding is written down | capstone | shell | guide |
capstone-v11-slo-et-dora |
V11, measuring reliability: an SLO that pages, a runbook, the DORA metrics | capstone | shell | guide |
13 labs, table generated by scripts/gen_catalog.py.
Contributions are welcome: read CONTRIBUTING.md, which describes the real anatomy of a lab in this repository and the golden rule, a lab is proven both ways. The code of conduct applies to all exchanges, and vulnerabilities are reported privately: SECURITY.md.
This content is published under Creative Commons Attribution 4.0 International (CC BY 4.0). You may share and adapt it, including commercially, provided you credit the source.