SDLC and the challenge
How software reaches you, and where it can be poisoned
Written for somebody who does not write software. No jargon that is not explained the first time it appears, and nothing here needs you to know what a compiler is.
The journey, one step at a time
This plays on its own. Press a step to stop it and read.
On the laptop
Somebody writes code on their own machine. This is the cheapest place to catch a mistake: nothing has been shared yet, and fixing it costs a keystroke. It is also the only place a scanner cannot see unless somebody installs one.
Into the repository
The change is pushed to a shared repository, where it joins everything anybody has ever pushed. From this moment a password typed into a file is in the history permanently, and deleting the line later does not remove it. Most leaked credentials found on a real estate are in this history rather than in the current code.
The ingredients arrive
The build downloads the libraries the code says it needs, from public registries: npm for JavaScript, PyPI for Python, Docker Hub for the base image. A typical application pulls in several hundred packages it did not write, each of which pulls in more. Nobody reads them.
On the build server
A build server, Jenkins or GitHub Actions or similar, assembles all of it into one artifact: a container image or a package. The build server holds the credentials for everything downstream, which makes it the single most valuable thing in the picture and the least often examined.
Out to production
The artifact is published and deployed. Whatever went in is now running, including anything nobody looked at. If a password went in at step two, it is now in an image sitting in a registry as well.
People use it
Customers use the running service. They are trusting every one of the steps above, including the several hundred packages nobody read and the base image last rebuilt four years ago. Software supply chain risk is simply the name for that chain of trust.
What most pipelines actually look like
Not because anybody decided to build it this way. It is what you get when a pipeline grows one useful shortcut at a time over five years.
The four things that go wrong
A password in the code
Somebody pastes a database password into a file to get something working, and forgets. Deleting it later does not help: the history keeps every version. On a real estate this is the most common serious finding, and the credential is usually still valid.
A package that is not what it says
Somebody registers a package one letter away from a popular one and waits for a typo. Others take over an abandoned package and publish a new version. Because packages can run code the moment they are installed, this works before anybody has run the application.
A base image nobody has rebuilt
Every container starts from a base image, which is a frozen copy of an operating system. Pin one in 2021 and you are still shipping 2021's known holes today, no matter how good the code on top of it is.
The build server itself
The pipeline holds the keys to everything it deploys to. Its configuration usually lives in the repository, where anyone who can change code can change the pipeline. Compromise it once and you can sign anything.
What a pipeline should look like
The same journey, with something checking at each handover. None of these checks is expensive; the hard part is that they have to be in the path rather than in a report nobody opens.
The tools, and what each one is for
| Check | What it looks for | Where it belongs |
|---|---|---|
| Secret scanning | Passwords, API keys and certificates that ended up in the code. | Before the merge, and continuously over the whole history. |
| SAST static analysis |
Patterns in your own code that let somebody in: injection, unsafe handling of input, weak cryptography. | Before the merge, on the changed files. |
| SCA dependency scanning |
Known holes in the packages you downloaded, and packages that are themselves hostile. | In the build, every build. |
| Container scanning | Known holes in the base image and everything the build installs into it. | In the build, before the image is pushed. |
| IaC scanning infrastructure as code |
Servers, buckets and clusters described in files, configured in a way that leaves them open. | Before the merge, with the code. |
| Pipeline review | The build configuration itself: who can change it, what it trusts, what it can reach. | Before the merge, and continuously. |
| DAST dynamic testing |
Problems that only appear when the thing is actually running. | Against a test deployment, after the build. |
| Signing and SBOM | Proof of what is in the artifact and that nobody changed it after it was built. | At release, attached to the artifact. |
Where Git Code Review fits
It is the continuous pass across everything already committed, rather than a gate on what is arriving. Both are needed and they answer different questions.
A gate answers "should this change go in?"
It looks at what somebody is adding right now, and it can say no. That is the cheapest place to stop a problem, and it is the only place that stops one before it exists. Every organization should have one, and it does nothing at all about the five years of code that went in before you installed it.
This answers "what is already in there?"
Every commit on every branch of every repository, across every host you use, including the credentials somebody deleted in 2019 and the repository nobody has opened since the person who wrote it left. On any estate older than a year that is where most of the risk is sitting, and no gate will ever find it.
It also does the ranking, which is the part that decides whether anybody reads the output: verifying whether a key still works, working out which vulnerable packages the code can actually reach, and saying what each repository holds and who can push to it.