Fast and Furious: Doubling Down on SBOM Drift
You can’t turn around these days without running into someone talking about software bill of materials (SBOMs). It’s great news that everyone is directing attention to software supply chain security, especially since SBOMs are the foundation of it all.
For background, the federal Cybersecurity & Infrastructure Security Agency (CISA) defines an SBOM as “a nested inventory, a list of ingredients that make up software components.” Security professionals and developers use SBOMs to gain amazing insight into our software like never before. Knowing what’s in our software is the first step on the journey of security.
But there’s another aspect to SBOMs that’s not being talked about. It’s something known as drift. The idea behind drift is simple: The dependencies included in an application will change during build steps as well as over time. New dependencies might be added and old ones removed or upgraded.
If you generate an SBOM today, it might not be the same SBOM you generate tomorrow. Certain dependencies might add or remove things, or update versions, or even vanish entirely. Sometimes the number of dependencies can change drastically depending on what’s happening.
Like many new topics, it’s much easier to actually show this than it is to explain it. In this case, it’s going to be a lot easier to show life-cycle drift than it is to show time-based drift, but the principles are the same. As your application is developed, the SBOM will often change every step of the way.
The Heavy Lifting
We will use the open source Syft project from Anchore for this particular example. Syft is a tool that can generate an SBOM from your project, application, container and more. It’s easy to use and supports a large number of languages and ecosystems.
To start, let’s create a very small Node.js application that uses a library called Express. This demo was created by running
npx express-generator which takes care of all the setup and includes a few external dependencies to make everything work. If we look at the package.json (which is the file where npm stores dependency details), we can see there are six dependencies in our demo application.
Now we install those dependencies by running
npm install. It takes a few minutes for all of the dependencies to download. Keep in mind that a dependency can also have its own dependencies. Those dependencies also have dependencies. We can end up with many layers of dependencies once it’s all done.
Now if we scan the project with Syft, we see 91 packages after npm install. What just happened? This is one example of SBOM drift in action. We thought our project had six dependencies, but when we installed those dependencies, the number balloons to 91. There’s nothing wrong with this; modern applications do need a lot of libraries to do the heavy lifting for us. The problem is when we don’t realize what’s happening.
If I’m only looking at the package.json dependencies, I only see six. I could have incorrectly assumed I was only using six additional dependencies. But the running application clearly needs a lot more.
Watch the Drift
Now the next step in our application is to build a container where it will deploy and run in production. You probably already figured out that when we put our application in a container, the number of dependencies will also increase. For the sake of this example, we will use the node:15 container image. It’s been named
express-test:latest so that’s what we will tell Syft to scan.
772! That’s a lot more than 91.
Over the course of our development, we went from six dependencies to 91, and now 772. This is the idea of SBOM drift. As we develop our software, there will be changes. Sometimes those changes are big; sometimes they are small. Keeping track of those changes can give us insight into what’s happening in our environment.
If we aren’t creating and observing SBOMs, we may still think we have six dependencies. If we only scan the project at build time, we would think we have 91 dependencies. The purpose of this example is to show that it’s important to build SBOMs every step of the way. Just creating an SBOM during the first build will miss things, and just scanning an SBOM at the very end doesn’t tell us where or why a dependency was introduced.
The Attack Surface
There is one other really important angle to all of this, and it involves picking our dependencies. All the things we include in our application and containers can be a part of what security folks call the “attack surface.”
The basic idea is that the more dependencies you include, the larger your attack surface. It is generally accepted that a small attack surface is better than a large one.
What if we try a smaller container image? Alpine Linux is legendary for its small size. Let’s see what happens if we use that.
It shows 341 packages, which is still a lot of dependencies, though substantially less than the first container we built that had over 700. One way we could think about this is our dependency attack surface is nearly cut in half. Now instead of needing to track and manage 772 dependencies, I have to track and manage 341.
Measure Twice, Cut Once
This example just scratches the surface by showing a simple example of why SBOM drift is an important metric during software development and deployment. If we don’t measure what’s happening to our software in many different places, we can be blind to what’s happening in our environment.
The old saying “measure twice, cut once” is as true in the software world as it is in the real world. We’re at the earliest stages of SBOM management, but concepts like SBOM drift are easy to understand, and the potential benefit is equally simple.
Having insight into our environments helps us make better decisions during the software build process. That, in turn, lends itself to a stronger security posture that can withstand software supply chain security infractions over time.