Creating a Reusable Execution Environment Using a Software Bill of Materials
Author: Christoph Benjamin Blessing
Introduction
Scientific progress is often hindered by research software not being reusable due to an incomplete specification of the execution environment required to run the software. The Reusable Execution Environment (REE) currently being developed by NFDIxCS aims to address this challenge by acting as a precise specification of the execution environment that is easily defined by the researcher. An REE prototype described in a previous blog post fulfilled its goals in respect to being a precise specification but fell short when it came to ease of definition. In hopes of alleviating this shortcoming we searched for promising existing technologies that were not used in the previous prototype. One interesting technology we came across was the Software Bill of Materials (SBOM). It is a comprehensive list of all software components that make up a particular software system. Originally designed for software licensing and security, SBOMs may also be used to create REEs.
To investigate this possibility we used the following procedure:
- Create a SBOM for a research software project
- Recreate the project's execution environment using the SBOM
Creating a SBOM for a Research Software Project
We accomplished the first step by using Syft, a software tool that can create a SBOM from a file system, container image or archive. It supports a variety of different software ecosystems, one of them being JavaScript which the project we chose to investigate uses.
Recreating the Execution Environment
The second step proved to be more difficult because to our knowledge there exists no tool that can recreate an execution environment from a SBOM. That said such a tool should be theoretically possible if all the information required for recreation is contained in the SBOM.
Understanding the SBOM
The SBOM contained a list of software packages where each package has a name and version. This information was extracted by Syft from the project directory, more specifically from a file called package.json. The authors of the project specified the packages that are required to execute their research software in package.json and then used the Node Package Manager (NPM) to install those packages into the execution environment. In order to recreate the same execution environment we chose to use NPM as well. Unfortunately NPM can not install packages directly from the SBOM but it allows for the installation of packages passed to it on the command line. Therefore our initial idea was to extract the name and version pairs from the SBOM and pass them to NPM for installation.
Initial Recreation Attempt
So we wrote some basic code that extracts the name/version pairs from the SBOM and constructs the NPM command that would install those packages. Running the command yielded an error letting us know that we were trying to install the same package multiple times which is not allowed. After inspecting the command we discovered that the installation command indeed contained two different versions of the same package. We could rule out a bug in our extraction code because the package appeared twice in the SBOM as well. At this point we did not know why our recreation attempt had failed and we chose to do some more investigating.
Investigating the Failure
Here is what we learned: Packages listed in package.json are also called dependencies because the project depends on them to function properly. Additionally each dependency may have its own set of dependencies, forming a hierarchical tree. The following diagram shows an example of such a tree:![]()
Each node in the tree represents a package that has a name and a version. The root node is represented by the research software project and each edge between two nodes represents a dependency relationship where the node that the edge points to is a dependency of the parent node. There are two different types of dependencies:
- Direct dependencies
- Transient dependencies
Packages A and B are direct dependencies of the project package because they are children of the project node in the diagram above. The two different versions of package C and package D are transient dependencies because their respective nodes are not children of the project node, i.e. the project only indirectly depends on them. This knowledge allowed us to figure out why our recreation attempt failed.
Identifying the Problem
Recall that we tried to recreate the execution environment by extracting name/version pairs from the SBOM and passing them to NPM for installation. All packages installed this way will be direct dependencies. Therefore we basically converted the dependency tree shown above into the following one:
![]()
Packages A and B are direct dependencies of the project package, just as before. Unlike before package D and both versions of package C went from being transient dependencies to being direct dependencies. More importantly both versions of package C are now direct dependencies of the same package. Recall that when we tried to recreate the execution environment earlier we got an error because we tried to install two different versions of the same package. Now we know this error was caused by us trying to install originally transient dependencies as direct dependencies.
Filtering Direct Dependencies
So our initial approach for recreating the execution environment from the SBOM had failed but at least we knew the reason for it and could hopefully use it to improve our approach. If the failure was caused by us trying to install originally transient dependencies as direct ones then we should be able to fix it by only installing the direct dependencies. In this case NPM would automatically install the transient dependencies for us. To do this we have to filter the list of packages contained in the SBOM down to just the ones that were direct dependencies in the original execution environment. Unfortunately there does not seem to be any information in the SBOM that would allow for such filtering and therefore we have to conclude that it is not possible to recreate the original execution environment from the SBOM.
Conclusion
Even though we have to conclude that SBOMs are not suitable to create REEs for JavaScript projects that conclusion might change in the future. Syft might implement support for extracting hierarchical data from the dependency tree and storing it in the SBOM. It should also be noted that SBOMs may be used to create REEs for projects that use other ecosystems besides JavaScript. That said the information contained in the SBOM can only ever be as good as the information contained within the project directory. Therefore there is a case to be made to just use this information directly.


