Practice — build a threat model for a CI/CD pipeline
This hands-on exercise brings Module 1 together. You will build a complete threat model for a realistic CI/CD pipeline, applying the Secure SDLC mindset, STRIDE categories, trust-boundary mapping, secrets discipline, and supply-chain awareness covered in the previous lessons. The goal is not a perfect document but a repeatable practice: diagram the system, enumerate what can go wrong, and pair every real threat with a concrete, testable control you could actually implement in a pipeline.
Analogy🏏Cricket
🎮 Think of it like gaming: a boss-fight practice run is not about scoring a flawless victory the first time but about learning the arena — where the traps sit, which attacks hurt most, and how to counter each one — so the routine becomes second nature. This exercise is that practice run for security, bringing the whole module together to build a threat model for a realistic CI/CD pipeline. The aim is a repeatable habit: diagram the system, enumerate what can go wrong, and pair every real threat with a concrete, testable control. This reveals that the value lies in the repeatable method, not in producing one perfect document.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
The target system is a typical pipeline: developers push to a Git repository, a CI service on shared runners builds and tests the code, pulls dependencies from public registries, packages a container image, and deploys it to a cloud environment using a stored deploy token. Your job is to treat this pipeline itself as the asset under attack, not just the application it produces, and to defend every hand-off between its stages.
Analogy🏏Cricket
📷 Think of it like photography: before shooting a complex scene, a photographer studies the whole set — the lights, the backdrop, the reflective surfaces, the paths people walk — because any of them, not just the subject, can ruin the shot. The target here is a typical pipeline: developers push to Git, a CI service on shared runners builds and tests, pulls public dependencies, packages a container, and deploys with a stored token. Your job is to treat the pipeline itself as the asset under attack, not merely the application it produces. This reveals that securing the whole set matters as much as framing the subject in the centre.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
Step 1 — Draw the data-flow diagram. Sketch each stage as a node: developer, Git repository, CI runner, dependency registry, artifact store, and deployment target. Draw arrows for the data and credentials that flow between them, then mark the trust boundaries where control changes hands. Pay special attention to where secrets are read and where untrusted input, such as a pull request from an outside contributor, can influence a privileged build job.
Analogy🏏Cricket
🎵 Think of it like music: an orchestral score lays out every instrument on its own staff and marks exactly where each section enters, so a conductor sees at a glance how the parts connect and where a wrong entry would jar. Step one of the exercise is drawing that score for your pipeline — sketch each stage as a node, developer to Git to runner to registry to artifact store to deployment, draw arrows for the data and credentials flowing between them, and mark the trust boundaries where control changes hands. Note where secrets are read and untrusted input enters. This reveals that a clear map of every part is the sheet music the whole analysis reads from.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
Step 2 — Run a STRIDE pass on each boundary. For every hand-off in your diagram, ask which of the six STRIDE threats apply. Could an attacker spoof a committer, tamper with a dependency, exfiltrate the deploy token, or elevate privilege by triggering a job from an untrusted branch? Record each plausible threat as a short, specific statement rather than a vague worry, so it can later be assigned an owner and a mitigation.
Analogy🏏Cricket
⚽ Think of it like sports: a coach breaking down the opposition does not settle for 'they might score'; they analyse each zone of the pitch and name the specific threat there — a winger who cuts inside, a striker strong in the air, a set-piece routine — so every danger has a marker assigned. Step two runs a STRIDE pass on each boundary in your diagram, asking whether an attacker could spoof a committer, tamper with a dependency, exfiltrate the deploy token, or elevate privilege from an untrusted branch. Record each as a short, specific statement, not a vague worry. This reveals that a threat you can name precisely is one you can actually assign and defend.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
text
# Step 2 output — sample threat register (fill in the rest yourself)IDBoundarySTRIDEThreatstatementT1PR->CIrunnerElevationUntrustedPRtriggersajobwithpushcredentialsT2CIrunner->registryTamperingUnpinneddependencyswappedforamaliciousversionT3RunnersecretstoreInfodisclosureDeploytokenprintedtobuildlogsandleakedT4Git->CISpoofingUnsignedcommitforgesatrustedauthoridentityT5Runner(shared)ElevationOneproject's job reads another project'ssecrets
Step 3 — Map every threat to a testable control. Take each entry in your register and pair it with a specific defence you could verify automatically. Untrusted pull requests should not receive privileged tokens; dependencies should be pinned and verified by digest; secrets should be masked in logs and rotated; commits should be signed; and runners should be ephemeral and isolated per project. Write each control as something a later scan or policy check could confirm was actually applied.
Analogy🏏Cricket
💪 Think of it like fitness: a useful training plan never lists a vague goal like 'get stronger'; it pairs each weakness with a specific, measurable exercise — three sets of a named lift, a timed run — so progress can actually be checked. Step three does the same for your threat register, mapping every entry to a defence you could verify automatically. Untrusted pull requests get no privileged tokens, dependencies are pinned and verified by digest, secrets are masked and rotated, commits are signed, and runners are ephemeral and isolated. This reveals that a control you can automatically test is worth far more than an intention you merely wrote down.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
text
# Step 3 output — controls mapped to threats, each verifiableT1->denysecretstoPR-triggeredjobs;requiremaintainerapprovaltorunT2->pindependenciesbydigest;enforceSBOM+vulnerabilityscangateT3->masksecretsinlogs;useshort-livedOIDCtokens;rotateonscheduleT4->requiresignedcommits;enforcebranchprotectionwithreviewT5->ephemeral,single-tenantrunners;scopetokenstooneprojectpath
Step 4 — Prioritise and review. Rank your controls by the blast radius of the threat they address, tackling anything that could expose production credentials or poison an artifact first. Then critique your own model: did you cover every boundary, and is each control genuinely testable rather than aspirational? Finally, note that a threat model is a living artifact — revisit it whenever the pipeline's design changes, exactly as the Secure SDLC lesson recommended for continuous, lightweight modelling.
Analogy🏏Cricket
🎬 Think of it like movies: before a film locks, editors screen it in priority order, fixing the scenes that would most break the audience's trust first, then critically re-watching the whole cut to check nothing was missed — knowing the edit keeps changing as the story evolves. Step four prioritises your controls by the blast radius of the threat each addresses, tackling anything that could expose production credentials or poison an artifact first, then critiques whether every boundary is covered and each control is genuinely testable. A threat model, like a cut, is a living artifact revisited whenever the design changes. This reveals that ranking by impact and revisiting often keeps the work relevant.
🏏 Showing the Cricket analogy — a Cricket version isn’t available for this concept yet.
Treat the pipeline itself as the asset under attack, not only the app it builds.
Step 1: diagram stages and mark trust boundaries where control changes hands.
Step 2: run STRIDE per boundary and record specific, ownable threat statements.
Step 3: map each threat to a concrete, automatically verifiable control.
Step 4: prioritise by blast radius and treat the model as a living artifact.