Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

DevSecOps common thread: notes-api from V0 to V11

By updated

Improve this page

The DevSecOps common thread applies the whole course to a single service, notes-api, a small Flask API that stores notes. You evolve it in twelve versions, from V0 to V11: a first pipeline, then a threat model, security gates, an identity without a secret, a signed image that a cluster knows how to refuse, and finally an SLO and DORA metrics, with a side version, V9b, that adds runtime detection on a real machine. Each version is a dsoxlab lab whose five checks judge the result, not the commands typed. It is meant for those who followed the course modules and want to prove it on a complete case, over nearly eleven hours.

What you will learn

  • Build a pipeline that blocks on a secret, an injection, a vulnerable dependency or an image running as root
  • Keep a STRIDE threat model up to date, version after version, with the evidence of each mitigated threat
  • Publish without a stored key, attest the provenance of an archive and verify it before deploying it
  • Make Kubernetes refuse an unsigned image, and contain the pod that runs
  • Record the security decisions: dated exceptions, VEX document, SLO and DORA metrics

How does the common thread work?

The common thread fits in a single repository, which gets hardened from one version to the next. The starting point of a version is the solution of the previous one: what you build in V3 serves in V4, and nothing starts over. The lab repository devsecops-dsoxlab-training carries the twelve versions, their scenarios in French and English, and their encrypted solutions.

The threat model is the backbone. Set up in V1, it names each weakness of notes-api, from the SQL injection of the search to the security group open to the world, and each version moves one or two of them from open to mitigated, with the file that proves it. At the end, thirteen threats out of eighteen are mitigated and one is accepted. The last four stay open, and named: the missing authentication of notes-api and the log of its writes belong to the application, not to its delivery chain.

Each version also cites the NIST SSDF practice it implements, NIST's secure development framework (SP 800-218). The checks play your pipeline with act, which runs GitHub Actions locally, then replay it on booby-trapped copies of the project: a token pasted in the code, a vulnerable lock, a pull request that runs its own code. Your pipeline must pass on the project, and break on the copy.

What do you need on your workstation?

The common thread runs on your workstation, with no cloud account or cluster to rent. You need a Docker that answers, the dsoxlab CLI and the mise tool, which installs the versions pinned by the catalog: act 0.2.89, uv, Python, and from V8 on kind, kubectl, helm and cosign. Only V9b needs a virtual machine, which dsoxlab creates on your workstation with KVM or Incus.

Terminal window
uv tool install dsoxlab
git clone https://github.com/stephrobert/devsecops-dsoxlab-training.git
cd devsecops-dsoxlab-training
mise install
dsoxlab run capstone-v00-pipeline-minimal
dsoxlab check capstone-v00-pipeline-minimal

dsoxlab run prepares the lab's working directory, dsoxlab check runs its five checks, twenty points each. Versions V8 and V9 set up a throwaway one-node kind cluster for the duration of the checks: allow three to four minutes, the first run downloading the images of the cluster and of Kyverno.

What are the twelve versions?

The table gives, for each version, what notes-api gains, the SSDF practice targeted and the course lesson that teaches the concept. Follow it in order: each row assumes the previous ones.

VersionWhat notes-api gainsSSDFLesson that teaches it
V0A pipeline that installs the lock and tests every pushPO.3Secure pipeline
V1A STRIDE threat model, checked by the pipelinePW.1Threat modeling with STRIDE
V2Blocking secrets and SAST, the SQL injection fixedPW.7, PW.5Automated security testing
V3Blocking dependency scanning, dated triageRV.2, PW.4SCA
V4Publishing through OIDC, without a stored keyPO.5OIDC
V5Audited workflows, contained pull requestsPS.1zizmor
V6An unprivileged image, a closed infrastructurePW.6Trivy
V7An SBOM confronted with the lock, an attested provenancePS.2, PS.3SLSA provenance
V8A cluster that refuses the unsigned imagePS.2Verifying artifacts at admission
V9A restricted pod, a closed networkPW.9NetworkPolicies
V9bFalco rules that see the intrusionRV.1Falco
V10Dated exceptions, a published VEXRV.2VEX
V11An SLO that pages, a runbook, the DORA metricsPO.4SLO, SLI and error budgets

Put it into practice

The thirteen labs fall into four stages. Each block below says what the version asks of you and what its checks look at. They read what your pipeline produces, what your image carries or what your cluster admits, never the shape of your YAML alone.

Foundations: a pipeline, a threat model, two gates

The first three versions lay the groundwork. V0 writes the pipeline that installs the locked dependencies and runs the tests: the checks verify that a broken test and an out-of-sync uv.lock make it fail. V1 adds the threat model, confronted with the real routes of the application. V2 plugs gitleaks and Semgrep in as blocking gates, and the booby-trapped copy adds a route no test covers: only static analysis can stop it.

The supply chain, from dependency to provenance

The next five versions secure what notes-api consumes and what it ships. V3 makes osv-scanner block and separates what can be fixed from what must be triaged: a vulnerability without a fix gets a dated exception, and a copy whose expiry has passed turns red again. V4 replaces the stored AWS key with an OIDC exchange: the checks evaluate your trust policy against five tokens, and only the one from a push on main, addressed to STS, must pass.

V5 audits the workflows themselves with actionlint and zizmor, and rewrites a welcome workflow that ran the code of pull requests with a write token. V6 analyses the Dockerfile, the Terraform and the built image with Trivy: the checks build your image and verify the user that runs it. V7 produces the image SBOM, confronts it with the lock, and expects a script that refuses an archive whose provenance does not come from the main pipeline.

Deploying and running: the cluster refuses, the pod is contained

Versions V8 and V9 follow notes-api into Kubernetes. V8 writes the Kyverno admission policies of the namespace: the checks set up a kind cluster, an internal registry and Kyverno, then request the admission of a signed image, an unsigned one, one signed with another key and an image from Docker Hub. V9 enforces the restricted Pod Security Standard and NetworkPolicies: the checks measure the real traffic, from the ingress controller, from another namespace and from notes-api itself, which must only go out to DNS.

V9b, a side version, sees what these protections do not tell: when someone got in. On an Ubuntu machine that dsoxlab provisions, notes-api runs in Docker under Falco 0.45.0 and its modern eBPF probe. You write the rules that report a shell opened in its container and a write into its code; the checks trigger these actions themselves, then the normal activity, which must trigger nothing.

Governing and measuring: written decisions, measured reliability

The last two versions deal with what remains once everything blocks. V10 raises the threshold to MEDIUM: what can be fixed is fixed, what is accepted becomes a dated exception with its reason, and a VEX document tells the operators of the previous releases that pip, present in the base image, does not affect notes-api. V11 writes the SLO as a Prometheus alert, the runbook it links to, and the computation of the four DORA metrics. For a 99.5% SLO, the alert pages the on-call person when the error budget burns 14.4 times too fast, that is above 7.2% of errors: the checks verify with promtool that 10% fires it and that 1% does not.

What the checks cannot prove

A green check is only worth what it measures, and the common thread says so. act provides no OIDC token: under act, a job that authenticates to AWS fails the same way with a role and with a static key, as measured with configure-aws-credentials v6.3.0. V4 and V7 therefore judge the configuration of the publication, and run the trust policy and the verification script, without being able to replay the keyless signing itself.

Runtime detection with Falco is not part of V9: it requires a real machine, not a cluster that shares the host kernel. That is why V9b runs on a VM. Finally, vulnerability databases evolve: a dependency that is clean today may carry a CVE tomorrow and make an already passed version fail. That is the expected behaviour of a blocking gate, and the catalog replays its solutions to detect it.

Two pitfalls measured while building the common thread are worth knowing outside the lab. cosign 3.1 signs in the new bundle format by default, which Kyverno 1.19.1 does not find ("no signatures found"): signing takes --new-bundle-format=false. And the python:3.12-slim image of 3 October 2026 already carried a HIGH CVE in libpcre2 fixed in Debian, hence the apt-get upgrade on every build in V6.

Key points

  • The common thread evolves a single service, notes-api, in twelve versions; each starts from the solution of the previous one.
  • The threat model set up in V1 is the backbone: each version handles a threat and cites the file that proves it.
  • The checks judge what the pipeline produces, then replay it on booby-trapped copies that must make it fail.
  • A vulnerability that has a fix gets fixed; one without a fix becomes a dated exception, which raises the alarm again at its expiry.
  • Publishing goes through OIDC, without a stored key, and the cluster refuses any image the platform did not sign.
  • A green check only proves what it measures: act does not replay keyless signing, and Falco requires a real machine, hence the V9b VM.

Knowledge check

Check that the essentials of this lesson are understood. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

FAQ: frequently asked questions

The questions below come up most often before starting the common thread: total duration, Kubernetes prerequisites, tools to install, order of the versions and what the lab checks are worth.

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn