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.
The catalog is described by data, not code, which keeps the dsoxlab engine
domain-agnostic:
meta.yml, at the root, declares the repository's identity and the order of the versions, which is the order of play: it is never reordered;lab.yaml, per lab, declares itsskills, itslevel, itsruntime(type, fixtures), itsdistros, itsdoc_urland avalidationblock. Alab.fr.yamloverrides thetitleand thedescriptionin French, and nothing else.
dsoxlab validate-structure checks the whole contract, and
tests/test_schemas_dsoxlab.py checks each lab.yaml against the schema
published by dsoxlab.
dsoxlab list-labs # browse the catalog
dsoxlab show <id> # a lab's metadata and state
dsoxlab run <id> # set up the starting state
dsoxlab challenge <id> # read the mission, no step-by-step
dsoxlab hint <id> # reveal a hint (deducted from the score)
dsoxlab check <id> # run the tests, compute and record the score
dsoxlab clean <id> # remove the working directory
dsoxlab progress # progress per section, average score| 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.